xref: /illumos-gate/usr/src/lib/libdscp/libdscp.c (revision 25cf1a30)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <unistd.h>
31 #include <string.h>
32 #include <ctype.h>
33 #include <errno.h>
34 #include <fcntl.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <sys/ioctl.h>
38 #include <sys/socket.h>
39 #include <sys/sockio.h>
40 #include <net/if.h>
41 #include <net/pfkeyv2.h>
42 #include <netinet/in.h>
43 #include <arpa/inet.h>
44 #include <libdscp.h>
45 
46 /*
47  * Define the file containing the configured DSCP interface name
48  */
49 #define	DSCP_CONFIGFILE		"/var/run/dscp.ifname"
50 
51 /*
52  * Forward declarations
53  */
54 static int get_ifname(char *);
55 static int convert_ipv6(struct sockaddr_in6 *, uint32_t *);
56 static int convert_ipv4(struct sockaddr_in *,
57     struct sockaddr_in6 *, int *);
58 
59 /*
60  * dscpBind()
61  *
62  *	Properly bind a socket to the local DSCP address.
63  *	Optionally bind it to a specific port.
64  */
65 int
dscpBind(int domain_id,int sockfd,int port)66 dscpBind(int domain_id, int sockfd, int port)
67 {
68 	int			len;
69 	int			len6;
70 	int			error;
71 	struct sockaddr_in	addr;
72 	struct sockaddr_in6	addr6;
73 
74 	/* Check arguments */
75 	if ((sockfd < 0) || (port >= IPPORT_RESERVED)) {
76 		return (DSCP_ERROR_INVALID);
77 	}
78 
79 	/* Get the local DSCP address used to communicate with the SP */
80 	error = dscpAddr(domain_id, DSCP_ADDR_LOCAL,
81 	    (struct sockaddr *)&addr, &len);
82 
83 	if (error != DSCP_OK) {
84 		return (error);
85 	}
86 
87 	/*
88 	 * If the caller specified a port, then update the socket address
89 	 * to also specify the same port.
90 	 */
91 	if (port != 0) {
92 		addr.sin_port = htons(port);
93 	}
94 
95 	/*
96 	 * Bind the socket.
97 	 *
98 	 * EINVAL means it is already bound.
99 	 * EAFNOSUPPORT means try again using IPv6.
100 	 */
101 	if (bind(sockfd, (struct sockaddr *)&addr, len) < 0) {
102 
103 		if (errno == EINVAL) {
104 			return (DSCP_ERROR_ALREADY);
105 		}
106 
107 		if (errno != EAFNOSUPPORT) {
108 			return (DSCP_ERROR);
109 		}
110 
111 		if (convert_ipv4(&addr, &addr6, &len6) < 0) {
112 			return (DSCP_ERROR);
113 		}
114 
115 		if (bind(sockfd, (struct sockaddr *)&addr6, len6) < 0) {
116 			if (errno == EINVAL) {
117 				return (DSCP_ERROR_ALREADY);
118 			}
119 			return (DSCP_ERROR);
120 		}
121 	}
122 
123 	return (DSCP_OK);
124 }
125 
126 /*
127  * dscpSecure()
128  *
129  *	Enable DSCP security mechanisms on a socket.
130  *
131  *	DSCP uses the IPSec AH (Authentication Headers) protocol with
132  *	the SHA-1 algorithm.
133  */
134 /*ARGSUSED*/
135 int
dscpSecure(int domain_id,int sockfd)136 dscpSecure(int domain_id, int sockfd)
137 {
138 	ipsec_req_t	opt;
139 
140 	/* Check arguments */
141 	if (sockfd < 0) {
142 		return (DSCP_ERROR_INVALID);
143 	}
144 
145 	/*
146 	 * Construct a socket option argument that specifies the protocols
147 	 * and algorithms required for DSCP's use of IPSec.
148 	 */
149 	(void) memset(&opt, 0, sizeof (opt));
150 	opt.ipsr_ah_req = IPSEC_PREF_REQUIRED;
151 	opt.ipsr_esp_req = IPSEC_PREF_NEVER;
152 	opt.ipsr_self_encap_req = IPSEC_PREF_NEVER;
153 	opt.ipsr_auth_alg = SADB_AALG_MD5HMAC;
154 
155 	/*
156 	 * Set the socket option that enables IPSec usage upon the socket,
157 	 * using the socket option argument constructed above.
158 	 */
159 	if (setsockopt(sockfd, IPPROTO_IP, IP_SEC_OPT, (const char *)&opt,
160 	    sizeof (opt)) < 0) {
161 		return (DSCP_ERROR);
162 	}
163 
164 	return (DSCP_OK);
165 }
166 
167 /*
168  * dscpAuth()
169  *
170  *	Test whether a connection should be accepted or refused.
171  *	The address of the connection request is compared against
172  *	the remote address of the specified DSCP link.
173  */
174 /*ARGSUSED*/
175 int
dscpAuth(int domain_id,struct sockaddr * saddr,int len)176 dscpAuth(int domain_id, struct sockaddr *saddr, int len)
177 {
178 	int			dlen;
179 	struct sockaddr		daddr;
180 	struct sockaddr_in	*sin;
181 	struct sockaddr_in6	*sin6;
182 	uint32_t		spaddr;
183 	uint32_t		reqaddr;
184 
185 	/* Check arguments */
186 	if (saddr == NULL) {
187 		return (DSCP_ERROR_INVALID);
188 	}
189 
190 	/*
191 	 * Get the remote IP address associated with the SP.
192 	 */
193 	if (dscpAddr(0, DSCP_ADDR_REMOTE, &daddr, &dlen) != DSCP_OK) {
194 		return (DSCP_ERROR_DB);
195 	}
196 
197 	/*
198 	 * Convert the request's address to a 32-bit integer.
199 	 *
200 	 * This may require a conversion if the caller is
201 	 * using an IPv6 socket.
202 	 */
203 	switch (saddr->sa_family) {
204 	case AF_INET:
205 		/* LINTED E_BAD_PTR_CAST_ALIGN */
206 		sin = (struct sockaddr_in *)saddr;
207 		reqaddr = ntohl(*((uint32_t *)&(sin->sin_addr)));
208 		break;
209 	case AF_INET6:
210 		/* LINTED E_BAD_PTR_CAST_ALIGN */
211 		sin6 = (struct sockaddr_in6 *)saddr;
212 		if (convert_ipv6(sin6, &reqaddr) < 0) {
213 			return (DSCP_ERROR);
214 		}
215 		break;
216 	default:
217 		return (DSCP_ERROR);
218 	}
219 
220 	/*
221 	 * Convert the SP's address to a 32-bit integer.
222 	 */
223 	/* LINTED E_BAD_PTR_CAST_ALIGN */
224 	sin = (struct sockaddr_in *)&daddr;
225 	spaddr = ntohl(*((uint32_t *)&(sin->sin_addr)));
226 
227 	/*
228 	 * Compare the addresses.  Reject if they don't match.
229 	 */
230 	if (reqaddr != spaddr) {
231 		return (DSCP_ERROR_REJECT);
232 	}
233 
234 	return (DSCP_OK);
235 }
236 
237 /*
238  * dscpAddr()
239  *
240  *	Get the addresses associated with a specific DSCP link.
241  */
242 /*ARGSUSED*/
243 int
dscpAddr(int domain_id,int which,struct sockaddr * saddr,int * lenp)244 dscpAddr(int domain_id, int which, struct sockaddr *saddr, int *lenp)
245 {
246 	int			error;
247 	int			sockfd;
248 	uint64_t		flags;
249 	char			ifname[LIFNAMSIZ];
250 	struct lifreq		lifr;
251 
252 	/* Check arguments */
253 	if (((saddr == NULL) || (lenp == NULL)) ||
254 	    ((which != DSCP_ADDR_LOCAL) && (which != DSCP_ADDR_REMOTE))) {
255 		return (DSCP_ERROR_INVALID);
256 	}
257 
258 	/*
259 	 * Get the DSCP interface name.
260 	 */
261 	if (get_ifname(ifname) != 0) {
262 		return (DSCP_ERROR_DB);
263 	}
264 
265 	/*
266 	 * Open a socket.
267 	 */
268 	if ((sockfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
269 		return (DSCP_ERROR_DB);
270 	}
271 
272 	/*
273 	 * Get the interface flags.
274 	 */
275 	(void) memset(&lifr, 0, sizeof (lifr));
276 	(void) strncpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name));
277 	if (ioctl(sockfd, SIOCGLIFFLAGS, (char *)&lifr) < 0) {
278 		(void) close(sockfd);
279 		return (DSCP_ERROR_DB);
280 	}
281 	flags = lifr.lifr_flags;
282 
283 	/*
284 	 * The interface must be a PPP link using IPv4.
285 	 */
286 	if (((flags & IFF_IPV4) == 0) ||
287 	    ((flags & IFF_POINTOPOINT) == 0)) {
288 		(void) close(sockfd);
289 		return (DSCP_ERROR_DB);
290 	}
291 
292 	/*
293 	 * Get the local or remote address, depending upon 'which'.
294 	 */
295 	(void) strncpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name));
296 	if (which == DSCP_ADDR_LOCAL) {
297 		error = ioctl(sockfd, SIOCGLIFADDR, (char *)&lifr);
298 	} else {
299 		error = ioctl(sockfd, SIOCGLIFDSTADDR, (char *)&lifr);
300 	}
301 	if (error < 0) {
302 		(void) close(sockfd);
303 		return (DSCP_ERROR_DB);
304 	}
305 
306 	/*
307 	 * Copy the sockaddr value back to the caller.
308 	 */
309 	(void) memset(saddr, 0, sizeof (struct sockaddr));
310 	(void) memcpy(saddr, &lifr.lifr_addr, sizeof (struct sockaddr_in));
311 	*lenp = sizeof (struct sockaddr_in);
312 
313 	(void) close(sockfd);
314 	return (DSCP_OK);
315 }
316 
317 /*
318  * dscpIdent()
319  *
320  *	Determine the domain of origin associated with a sockaddr.
321  *	(Map a sockaddr to a domain ID.)
322  *
323  *	In the Solaris version, the remote socket address should always
324  *	be the SP.  A call to dscpAuth() is used to confirm this, and
325  *	then DSCP_IDENT_SP is returned as a special domain ID.
326  */
327 int
dscpIdent(struct sockaddr * saddr,int len,int * domainp)328 dscpIdent(struct sockaddr *saddr, int len, int *domainp)
329 {
330 	int	error;
331 
332 	/* Check arguments */
333 	if ((saddr == NULL) || (domainp == NULL)) {
334 		return (DSCP_ERROR_INVALID);
335 	}
336 
337 	/* Confirm that the address is the SP */
338 	error = dscpAuth(0, saddr, len);
339 	if (error != DSCP_OK) {
340 		if (error == DSCP_ERROR_REJECT) {
341 			return (DSCP_ERROR);
342 		}
343 		return (error);
344 	}
345 
346 	*domainp = DSCP_IDENT_SP;
347 	return (DSCP_OK);
348 }
349 
350 /*
351  * get_ifname()
352  *
353  *	Retrieve the interface name used by DSCP.
354  *	It should be available from a file in /var/run.
355  *
356  *	Returns: 0 upon success, -1 upon failure.
357  */
358 static int
get_ifname(char * ifname)359 get_ifname(char *ifname)
360 {
361 	int		i;
362 	int		fd;
363 	int		len;
364 	int		size;
365 	int		count;
366 	int		end;
367 	int		begin;
368 	struct stat	stbuf;
369 
370 	/*
371 	 * Initialize the interface name.
372 	 */
373 	(void) memset(ifname, 0, LIFNAMSIZ);
374 
375 	/*
376 	 * Test for a a valid configuration file.
377 	 */
378 	if ((stat(DSCP_CONFIGFILE, &stbuf) < 0) ||
379 	    (S_ISREG(stbuf.st_mode) == 0) ||
380 	    (stbuf.st_size > LIFNAMSIZ)) {
381 		return (-1);
382 	}
383 
384 	/*
385 	 * Open the configuration file and read its contents
386 	 */
387 
388 	if ((fd = open(DSCP_CONFIGFILE, O_RDONLY)) < 0) {
389 		return (-1);
390 	}
391 
392 	count = 0;
393 	size = stbuf.st_size;
394 	do {
395 		i = read(fd, &ifname[count], size - count);
396 		if (i <= 0) {
397 			(void) close(fd);
398 			return (-1);
399 		}
400 		count += i;
401 	} while (count < size);
402 
403 	(void) close(fd);
404 
405 	/*
406 	 * Analyze the interface name that was just read,
407 	 * and clean it up as necessary.  The result should
408 	 * be a simple NULL terminated string such as "sppp0"
409 	 * with no extra whitespace or other characters.
410 	 */
411 
412 	/* Detect the beginning of the interface name */
413 	for (begin = -1, i = 0; i < size; i++) {
414 		if (isalnum(ifname[i]) != 0) {
415 			begin = i;
416 			break;
417 		}
418 	}
419 
420 	/* Fail if no such beginning was found */
421 	if (begin < 0) {
422 		return (-1);
423 	}
424 
425 	/* Detect the end of the interface name */
426 	for (end = size - 1, i = begin; i < size; i++) {
427 		if (isalnum(ifname[i]) == 0) {
428 			end = i;
429 			break;
430 		}
431 	}
432 
433 	/* Compute the length of the name */
434 	len = end - begin;
435 
436 	/* Remove leading whitespace */
437 	if (begin > 0) {
438 		(void) memmove(ifname, &ifname[begin], len);
439 	}
440 
441 	/* Clear out any remaining garbage */
442 	if (len < size) {
443 		(void) memset(&ifname[len], 0, size - len);
444 	}
445 
446 	return (0);
447 }
448 
449 /*
450  * convert_ipv6()
451  *
452  *	Converts an IPv6 socket address into an equivalent IPv4
453  *	address.  The conversion is to a 32-bit integer because
454  *	that is sufficient for how libdscp uses IPv4 addresses.
455  *
456  *	The IPv4 address is additionally converted from network
457  *	byte order to host byte order.
458  *
459  *	Returns:	0 upon success, with 'addrp' updated.
460  *			-1 upon failure, with 'addrp' undefined.
461  */
462 static int
convert_ipv6(struct sockaddr_in6 * addr6,uint32_t * addrp)463 convert_ipv6(struct sockaddr_in6 *addr6, uint32_t *addrp)
464 {
465 	uint32_t		addr;
466 	char			*ipv4str;
467 	char			ipv6str[INET6_ADDRSTRLEN];
468 
469 	/*
470 	 * Convert the IPv6 address into a string.
471 	 */
472 	if (inet_ntop(AF_INET6, &addr6->sin6_addr, ipv6str,
473 	    sizeof (ipv6str)) == NULL) {
474 		return (-1);
475 	}
476 
477 	/*
478 	 * Use the IPv6 string to construct an IPv4 string.
479 	 */
480 	if ((ipv4str = strrchr(ipv6str, ':')) != NULL) {
481 		ipv4str++;
482 	} else {
483 		return (-1);
484 	}
485 
486 	/*
487 	 * Convert the IPv4 string into a 32-bit integer.
488 	 */
489 	if (inet_pton(AF_INET, ipv4str, &addr) <= 0) {
490 		return (-1);
491 	}
492 
493 	*addrp = ntohl(addr);
494 	return (0);
495 }
496 
497 /*
498  * convert_ipv4()
499  *
500  *	Convert an IPv4 socket address into an equivalent IPv6 address.
501  *
502  *	Returns:	0 upon success, with 'addr6' and 'lenp' updated.
503  *			-1 upon failure, with 'addr6' and 'lenp' undefined.
504  */
505 static int
convert_ipv4(struct sockaddr_in * addr,struct sockaddr_in6 * addr6,int * lenp)506 convert_ipv4(struct sockaddr_in *addr, struct sockaddr_in6 *addr6, int *lenp)
507 {
508 	int			len;
509 	uint32_t		ipv4addr;
510 	char			ipv4str[INET_ADDRSTRLEN];
511 	char			ipv6str[INET6_ADDRSTRLEN];
512 
513 	/*
514 	 * Convert the IPv4 socket address into a string.
515 	 */
516 	ipv4addr = *((uint32_t *)&(addr->sin_addr));
517 	if (inet_ntop(AF_INET, &ipv4addr, ipv4str, sizeof (ipv4str)) == NULL) {
518 		return (-1);
519 	}
520 
521 	/*
522 	 * Use the IPv4 string to construct an IPv6 string.
523 	 */
524 	len = snprintf(ipv6str, INET6_ADDRSTRLEN, "::ffff:%s", ipv4str);
525 	if (len >= INET6_ADDRSTRLEN) {
526 		return (-1);
527 	}
528 
529 	/*
530 	 * Convert the IPv6 string to an IPv6 socket address.
531 	 */
532 	(void) memset(addr6, 0, sizeof (*addr6));
533 	addr6->sin6_family = AF_INET6;
534 	addr6->sin6_port = addr->sin_port;
535 	if (inet_pton(AF_INET6, ipv6str, &addr6->sin6_addr) <= 0) {
536 		return (-1);
537 	}
538 
539 	*lenp = sizeof (struct sockaddr_in6);
540 
541 	return (0);
542 }
543