OpenSolaris_b135/lib/libdscp/libdscp.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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);
}