diff options
Diffstat (limited to 'usr/src/lib/libdscp/libdscp.c')
-rw-r--r-- | usr/src/lib/libdscp/libdscp.c | 542 |
1 files changed, 542 insertions, 0 deletions
diff --git a/usr/src/lib/libdscp/libdscp.c b/usr/src/lib/libdscp/libdscp.c new file mode 100644 index 0000000000..63c49383d0 --- /dev/null +++ b/usr/src/lib/libdscp/libdscp.c @@ -0,0 +1,542 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <net/if.h> +#include <net/pfkeyv2.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <libdscp.h> + +/* + * Define the file containing the configured DSCP interface name + */ +#define DSCP_CONFIGFILE "/var/run/dscp.ifname" + +/* + * Forward declarations + */ +static int get_ifname(char *); +static int convert_ipv6(struct sockaddr_in6 *, uint32_t *); +static int convert_ipv4(struct sockaddr_in *, + struct sockaddr_in6 *, int *); + +/* + * dscpBind() + * + * Properly bind a socket to the local DSCP address. + * Optionally bind it to a specific port. + */ +int +dscpBind(int domain_id, int sockfd, int port) +{ + int len; + int len6; + int error; + struct sockaddr_in addr; + struct sockaddr_in6 addr6; + + /* Check arguments */ + if ((sockfd < 0) || (port >= IPPORT_RESERVED)) { + return (DSCP_ERROR_INVALID); + } + + /* Get the local DSCP address used to communicate with the SP */ + error = dscpAddr(domain_id, DSCP_ADDR_LOCAL, + (struct sockaddr *)&addr, &len); + + if (error != DSCP_OK) { + return (error); + } + + /* + * If the caller specified a port, then update the socket address + * to also specify the same port. + */ + if (port != 0) { + addr.sin_port = htons(port); + } + + /* + * Bind the socket. + * + * EINVAL means it is already bound. + * EAFNOSUPPORT means try again using IPv6. + */ + if (bind(sockfd, (struct sockaddr *)&addr, len) < 0) { + + if (errno == EINVAL) { + return (DSCP_ERROR_ALREADY); + } + + if (errno != EAFNOSUPPORT) { + return (DSCP_ERROR); + } + + if (convert_ipv4(&addr, &addr6, &len6) < 0) { + return (DSCP_ERROR); + } + + if (bind(sockfd, (struct sockaddr *)&addr6, len6) < 0) { + if (errno == EINVAL) { + return (DSCP_ERROR_ALREADY); + } + return (DSCP_ERROR); + } + } + + return (DSCP_OK); +} + +/* + * dscpSecure() + * + * Enable DSCP security mechanisms on a socket. + * + * DSCP uses the IPSec AH (Authentication Headers) protocol with + * the SHA-1 algorithm. + */ +/*ARGSUSED*/ +int +dscpSecure(int domain_id, int sockfd) +{ + ipsec_req_t opt; + + /* Check arguments */ + if (sockfd < 0) { + return (DSCP_ERROR_INVALID); + } + + /* + * Construct a socket option argument that specifies the protocols + * and algorithms required for DSCP's use of IPSec. + */ + (void) memset(&opt, 0, sizeof (opt)); + opt.ipsr_ah_req = IPSEC_PREF_REQUIRED; + opt.ipsr_esp_req = IPSEC_PREF_NEVER; + opt.ipsr_self_encap_req = IPSEC_PREF_NEVER; + opt.ipsr_auth_alg = SADB_AALG_MD5HMAC; + + /* + * Set the socket option that enables IPSec usage upon the socket, + * using the socket option argument constructed above. + */ + if (setsockopt(sockfd, IPPROTO_IP, IP_SEC_OPT, (const char *)&opt, + sizeof (opt)) < 0) { + return (DSCP_ERROR); + } + + return (DSCP_OK); +} + +/* + * dscpAuth() + * + * Test whether a connection should be accepted or refused. + * The address of the connection request is compared against + * the remote address of the specified DSCP link. + */ +/*ARGSUSED*/ +int +dscpAuth(int domain_id, struct sockaddr *saddr, int len) +{ + int dlen; + struct sockaddr daddr; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + uint32_t spaddr; + uint32_t reqaddr; + + /* Check arguments */ + if (saddr == NULL) { + return (DSCP_ERROR_INVALID); + } + + /* + * Get the remote IP address associated with the SP. + */ + if (dscpAddr(0, DSCP_ADDR_REMOTE, &daddr, &dlen) != DSCP_OK) { + return (DSCP_ERROR_DB); + } + + /* + * Convert the request's address to a 32-bit integer. + * + * This may require a conversion if the caller is + * using an IPv6 socket. + */ + switch (saddr->sa_family) { + case AF_INET: + /* LINTED E_BAD_PTR_CAST_ALIGN */ + sin = (struct sockaddr_in *)saddr; + reqaddr = ntohl(*((uint32_t *)&(sin->sin_addr))); + break; + case AF_INET6: + /* LINTED E_BAD_PTR_CAST_ALIGN */ + sin6 = (struct sockaddr_in6 *)saddr; + if (convert_ipv6(sin6, &reqaddr) < 0) { + return (DSCP_ERROR); + } + break; + default: + return (DSCP_ERROR); + } + + /* + * Convert the SP's address to a 32-bit integer. + */ + /* LINTED E_BAD_PTR_CAST_ALIGN */ + sin = (struct sockaddr_in *)&daddr; + spaddr = ntohl(*((uint32_t *)&(sin->sin_addr))); + + /* + * Compare the addresses. Reject if they don't match. + */ + if (reqaddr != spaddr) { + return (DSCP_ERROR_REJECT); + } + + return (DSCP_OK); +} + +/* + * dscpAddr() + * + * Get the addresses associated with a specific DSCP link. + */ +/*ARGSUSED*/ +int +dscpAddr(int domain_id, int which, struct sockaddr *saddr, int *lenp) +{ + int error; + int sockfd; + uint64_t flags; + char ifname[LIFNAMSIZ]; + struct lifreq lifr; + + /* Check arguments */ + if (((saddr == NULL) || (lenp == NULL)) || + ((which != DSCP_ADDR_LOCAL) && (which != DSCP_ADDR_REMOTE))) { + return (DSCP_ERROR_INVALID); + } + + /* + * Get the DSCP interface name. + */ + if (get_ifname(ifname) != 0) { + return (DSCP_ERROR_DB); + } + + /* + * Open a socket. + */ + if ((sockfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { + return (DSCP_ERROR_DB); + } + + /* + * Get the interface flags. + */ + (void) memset(&lifr, 0, sizeof (lifr)); + (void) strncpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); + if (ioctl(sockfd, SIOCGLIFFLAGS, (char *)&lifr) < 0) { + (void) close(sockfd); + return (DSCP_ERROR_DB); + } + flags = lifr.lifr_flags; + + /* + * The interface must be a PPP link using IPv4. + */ + if (((flags & IFF_IPV4) == 0) || + ((flags & IFF_POINTOPOINT) == 0)) { + (void) close(sockfd); + return (DSCP_ERROR_DB); + } + + /* + * Get the local or remote address, depending upon 'which'. + */ + (void) strncpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); + if (which == DSCP_ADDR_LOCAL) { + error = ioctl(sockfd, SIOCGLIFADDR, (char *)&lifr); + } else { + error = ioctl(sockfd, SIOCGLIFDSTADDR, (char *)&lifr); + } + if (error < 0) { + (void) close(sockfd); + return (DSCP_ERROR_DB); + } + + /* + * Copy the sockaddr value back to the caller. + */ + (void) memset(saddr, 0, sizeof (struct sockaddr)); + (void) memcpy(saddr, &lifr.lifr_addr, sizeof (struct sockaddr_in)); + *lenp = sizeof (struct sockaddr_in); + + (void) close(sockfd); + return (DSCP_OK); +} + +/* + * dscpIdent() + * + * Determine the domain of origin associated with a sockaddr. + * (Map a sockaddr to a domain ID.) + * + * In the Solaris version, the remote socket address should always + * be the SP. A call to dscpAuth() is used to confirm this, and + * then DSCP_IDENT_SP is returned as a special domain ID. + */ +int +dscpIdent(struct sockaddr *saddr, int len, int *domainp) +{ + int error; + + /* Check arguments */ + if ((saddr == NULL) || (domainp == NULL)) { + return (DSCP_ERROR_INVALID); + } + + /* Confirm that the address is the SP */ + error = dscpAuth(0, saddr, len); + if (error != DSCP_OK) { + if (error == DSCP_ERROR_REJECT) { + return (DSCP_ERROR); + } + return (error); + } + + *domainp = DSCP_IDENT_SP; + return (DSCP_OK); +} + +/* + * get_ifname() + * + * Retrieve the interface name used by DSCP. + * It should be available from a file in /var/run. + * + * Returns: 0 upon success, -1 upon failure. + */ +static int +get_ifname(char *ifname) +{ + int i; + int fd; + int len; + int size; + int count; + int end; + int begin; + struct stat stbuf; + + /* + * Initialize the interface name. + */ + (void) memset(ifname, 0, LIFNAMSIZ); + + /* + * Test for a a valid configuration file. + */ + if ((stat(DSCP_CONFIGFILE, &stbuf) < 0) || + (S_ISREG(stbuf.st_mode) == 0) || + (stbuf.st_size > LIFNAMSIZ)) { + return (-1); + } + + /* + * Open the configuration file and read its contents + */ + + if ((fd = open(DSCP_CONFIGFILE, O_RDONLY)) < 0) { + return (-1); + } + + count = 0; + size = stbuf.st_size; + do { + i = read(fd, &ifname[count], size - count); + if (i <= 0) { + (void) close(fd); + return (-1); + } + count += i; + } while (count < size); + + (void) close(fd); + + /* + * Analyze the interface name that was just read, + * and clean it up as necessary. The result should + * be a simple NULL terminated string such as "sppp0" + * with no extra whitespace or other characters. + */ + + /* Detect the beginning of the interface name */ + for (begin = -1, i = 0; i < size; i++) { + if (isalnum(ifname[i]) != 0) { + begin = i; + break; + } + } + + /* Fail if no such beginning was found */ + if (begin < 0) { + return (-1); + } + + /* Detect the end of the interface name */ + for (end = size - 1, i = begin; i < size; i++) { + if (isalnum(ifname[i]) == 0) { + end = i; + break; + } + } + + /* Compute the length of the name */ + len = end - begin; + + /* Remove leading whitespace */ + if (begin > 0) { + (void) memmove(ifname, &ifname[begin], len); + } + + /* Clear out any remaining garbage */ + if (len < size) { + (void) memset(&ifname[len], 0, size - len); + } + + return (0); +} + +/* + * convert_ipv6() + * + * Converts an IPv6 socket address into an equivalent IPv4 + * address. The conversion is to a 32-bit integer because + * that is sufficient for how libdscp uses IPv4 addresses. + * + * The IPv4 address is additionally converted from network + * byte order to host byte order. + * + * Returns: 0 upon success, with 'addrp' updated. + * -1 upon failure, with 'addrp' undefined. + */ +static int +convert_ipv6(struct sockaddr_in6 *addr6, uint32_t *addrp) +{ + uint32_t addr; + char *ipv4str; + char ipv6str[INET6_ADDRSTRLEN]; + + /* + * Convert the IPv6 address into a string. + */ + if (inet_ntop(AF_INET6, &addr6->sin6_addr, ipv6str, + sizeof (ipv6str)) == NULL) { + return (-1); + } + + /* + * Use the IPv6 string to construct an IPv4 string. + */ + if ((ipv4str = strrchr(ipv6str, ':')) != NULL) { + ipv4str++; + } else { + return (-1); + } + + /* + * Convert the IPv4 string into a 32-bit integer. + */ + if (inet_pton(AF_INET, ipv4str, &addr) <= 0) { + return (-1); + } + + *addrp = ntohl(addr); + return (0); +} + +/* + * convert_ipv4() + * + * Convert an IPv4 socket address into an equivalent IPv6 address. + * + * Returns: 0 upon success, with 'addr6' and 'lenp' updated. + * -1 upon failure, with 'addr6' and 'lenp' undefined. + */ +static int +convert_ipv4(struct sockaddr_in *addr, struct sockaddr_in6 *addr6, int *lenp) +{ + int len; + uint32_t ipv4addr; + char ipv4str[INET_ADDRSTRLEN]; + char ipv6str[INET6_ADDRSTRLEN]; + + /* + * Convert the IPv4 socket address into a string. + */ + ipv4addr = *((uint32_t *)&(addr->sin_addr)); + if (inet_ntop(AF_INET, &ipv4addr, ipv4str, sizeof (ipv4str)) == NULL) { + return (-1); + } + + /* + * Use the IPv4 string to construct an IPv6 string. + */ + len = snprintf(ipv6str, INET6_ADDRSTRLEN, "::ffff:%s", ipv4str); + if (len >= INET6_ADDRSTRLEN) { + return (-1); + } + + /* + * Convert the IPv6 string to an IPv6 socket address. + */ + (void) memset(addr6, 0, sizeof (*addr6)); + addr6->sin6_family = AF_INET6; + addr6->sin6_port = addr->sin_port; + if (inet_pton(AF_INET6, ipv6str, &addr6->sin6_addr) <= 0) { + return (-1); + } + + *lenp = sizeof (struct sockaddr_in6); + + return (0); +} |