xref: /freebsd/crypto/krb5/src/lib/apputils/udppktinfo.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  * Copyright 2016 by the Massachusetts Institute of Technology.
4  * All Rights Reserved.
5  *
6  * Export of this software from the United States of America may
7  * require a specific license from the United States Government.
8  * It is the responsibility of any person or organization contemplating
9  * export to obtain such a license before exporting.
10  *
11  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
12  * distribute this software and its documentation for any purpose and
13  * without fee is hereby granted, provided that the above copyright
14  * notice appear in all copies and that both that copyright notice and
15  * this permission notice appear in supporting documentation, and that
16  * the name of M.I.T. not be used in advertising or publicity pertaining
17  * to distribution of the software without specific, written prior
18  * permission.  Furthermore if you modify this software you must label
19  * your software as modified software and not distribute it in such a
20  * fashion that it might be confused with the original M.I.T. software.
21  * M.I.T. makes no representations about the suitability of
22  * this software for any purpose.  It is provided "as is" without express
23  * or implied warranty.
24  */
25 
26 /* macOS requires this define for IPV6_PKTINFO. */
27 #define __APPLE_USE_RFC_3542
28 
29 #include "udppktinfo.h"
30 
31 #include <netinet/in.h>
32 #include <sys/socket.h>
33 
34 #if defined(IP_PKTINFO) && defined(HAVE_STRUCT_IN_PKTINFO)
35 #define HAVE_IP_PKTINFO
36 #endif
37 
38 #if defined(IPV6_PKTINFO) && defined(HAVE_STRUCT_IN6_PKTINFO)
39 #define HAVE_IPV6_PKTINFO
40 #endif
41 
42 #if defined(HAVE_IP_PKTINFO) || defined(IP_SENDSRCADDR) ||      \
43     defined(HAVE_IPV6_PKTINFO)
44 #define HAVE_PKTINFO_SUPPORT
45 #endif
46 
47 /* Use RFC 3542 API below, but fall back from IPV6_RECVPKTINFO to IPV6_PKTINFO
48  * for RFC 2292 implementations. */
49 #if !defined(IPV6_RECVPKTINFO) && defined(IPV6_PKTINFO)
50 #define IPV6_RECVPKTINFO IPV6_PKTINFO
51 #endif
52 
53 /* Parallel, though not standardized. */
54 #if !defined(IP_RECVPKTINFO) && defined(IP_PKTINFO)
55 #define IP_RECVPKTINFO IP_PKTINFO
56 #endif /* IP_RECVPKTINFO */
57 
58 #if defined(CMSG_SPACE) && defined(HAVE_STRUCT_CMSGHDR) &&      \
59     defined(HAVE_PKTINFO_SUPPORT)
60 union pktinfo {
61 #ifdef HAVE_STRUCT_IN6_PKTINFO
62     struct in6_pktinfo pi6;
63 #endif
64 #ifdef HAVE_STRUCT_IN_PKTINFO
65     struct in_pktinfo pi4;
66 #endif
67 #ifdef IP_RECVDSTADDR
68     struct in_addr iaddr;
69 #endif
70     char c;
71 };
72 #endif /* HAVE_IPV6_PKTINFO && HAVE_STRUCT_CMSGHDR && HAVE_PKTINFO_SUPPORT */
73 
74 #ifdef HAVE_IP_PKTINFO
75 
76 #define set_ipv4_pktinfo set_ipv4_recvpktinfo
77 static inline krb5_error_code
set_ipv4_recvpktinfo(int sock)78 set_ipv4_recvpktinfo(int sock)
79 {
80     int sockopt = 1;
81     return setsockopt(sock, IPPROTO_IP, IP_RECVPKTINFO, &sockopt,
82                       sizeof(sockopt));
83 }
84 
85 #elif defined(IP_RECVDSTADDR) /* HAVE_IP_PKTINFO */
86 
87 #define set_ipv4_pktinfo set_ipv4_recvdstaddr
88 static inline krb5_error_code
set_ipv4_recvdstaddr(int sock)89 set_ipv4_recvdstaddr(int sock)
90 {
91     int sockopt = 1;
92     return setsockopt(sock, IPPROTO_IP, IP_RECVDSTADDR, &sockopt,
93                       sizeof(sockopt));
94 }
95 
96 #else /* HAVE_IP_PKTINFO || IP_RECVDSTADDR */
97 #define set_ipv4_pktinfo(s) EINVAL
98 #endif /* HAVE_IP_PKTINFO || IP_RECVDSTADDR */
99 
100 #ifdef HAVE_IPV6_PKTINFO
101 
102 #define set_ipv6_pktinfo set_ipv6_recvpktinfo
103 static inline krb5_error_code
set_ipv6_recvpktinfo(int sock)104 set_ipv6_recvpktinfo(int sock)
105 {
106     int sockopt = 1;
107     return setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &sockopt,
108                       sizeof(sockopt));
109 }
110 
111 #else /* HAVE_IPV6_PKTINFO */
112 #define set_ipv6_pktinfo(s) EINVAL
113 #endif /* HAVE_IPV6_PKTINFO */
114 
115 /*
116  * Set pktinfo option on a socket. Takes a socket and the socket address family
117  * as arguments.
118  *
119  * Returns 0 on success, EINVAL if pktinfo is not supported for the address
120  * family.
121  */
122 krb5_error_code
set_pktinfo(int sock,int family)123 set_pktinfo(int sock, int family)
124 {
125     switch (family) {
126     case AF_INET:
127         return set_ipv4_pktinfo(sock);
128     case AF_INET6:
129         return set_ipv6_pktinfo(sock);
130     default:
131         return EINVAL;
132     }
133 }
134 
135 #if defined(HAVE_PKTINFO_SUPPORT) && defined(CMSG_SPACE)
136 
137 /*
138  * Check if a socket is bound to a wildcard address.
139  * Returns 1 if it is, 0 if it's bound to a specific address, or -1 on error
140  * with errno set to the error.
141  */
142 static int
is_socket_bound_to_wildcard(int sock)143 is_socket_bound_to_wildcard(int sock)
144 {
145     struct sockaddr_storage bound_addr;
146     socklen_t bound_addr_len = sizeof(bound_addr);
147     struct sockaddr *sa = ss2sa(&bound_addr);
148 
149     if (getsockname(sock, sa, &bound_addr_len) < 0)
150         return -1;
151 
152     if (!sa_is_inet(sa)) {
153         errno = EINVAL;
154         return -1;
155     }
156 
157     return sa_is_wildcard(sa);
158 }
159 
160 #ifdef HAVE_IP_PKTINFO
161 
162 static inline struct in_pktinfo *
cmsg2pktinfo(struct cmsghdr * cmsgptr)163 cmsg2pktinfo(struct cmsghdr *cmsgptr)
164 {
165     return (struct in_pktinfo *)(void *)CMSG_DATA(cmsgptr);
166 }
167 
168 #define check_cmsg_v4_pktinfo check_cmsg_ip_pktinfo
169 static int
check_cmsg_ip_pktinfo(struct cmsghdr * cmsgptr,struct sockaddr * to,socklen_t * tolen,aux_addressing_info * auxaddr)170 check_cmsg_ip_pktinfo(struct cmsghdr *cmsgptr, struct sockaddr *to,
171                       socklen_t *tolen, aux_addressing_info *auxaddr)
172 {
173     struct in_pktinfo *pktinfo;
174 
175     if (cmsgptr->cmsg_level == IPPROTO_IP &&
176         cmsgptr->cmsg_type == IP_PKTINFO &&
177         *tolen >= sizeof(struct sockaddr_in)) {
178 
179         memset(to, 0, sizeof(struct sockaddr_in));
180         pktinfo = cmsg2pktinfo(cmsgptr);
181         sa2sin(to)->sin_addr = pktinfo->ipi_addr;
182         sa2sin(to)->sin_family = AF_INET;
183         *tolen = sizeof(struct sockaddr_in);
184         return 1;
185     }
186     return 0;
187 }
188 
189 #elif defined(IP_RECVDSTADDR) /* HAVE_IP_PKTINFO */
190 
191 static inline struct in_addr *
cmsg2sin(struct cmsghdr * cmsgptr)192 cmsg2sin(struct cmsghdr *cmsgptr)
193 {
194     return (struct in_addr *)(void *)CMSG_DATA(cmsgptr);
195 }
196 
197 #define check_cmsg_v4_pktinfo check_cmsg_ip_recvdstaddr
198 static int
check_cmsg_ip_recvdstaddr(struct cmsghdr * cmsgptr,struct sockaddr * to,socklen_t * tolen,aux_addressing_info * auxaddr)199 check_cmsg_ip_recvdstaddr(struct cmsghdr *cmsgptr, struct sockaddr *to,
200                           socklen_t *tolen, aux_addressing_info * auxaddr)
201 {
202     if (cmsgptr->cmsg_level == IPPROTO_IP &&
203         cmsgptr->cmsg_type == IP_RECVDSTADDR &&
204         *tolen >= sizeof(struct sockaddr_in)) {
205         struct in_addr *sin_addr;
206 
207         memset(to, 0, sizeof(struct sockaddr_in));
208         sin_addr = cmsg2sin(cmsgptr);
209         sa2sin(to)->sin_addr = *sin_addr;
210         sa2sin(to)->sin_family = AF_INET;
211         *tolen = sizeof(struct sockaddr_in);
212         return 1;
213     }
214     return 0;
215 }
216 
217 #else /* HAVE_IP_PKTINFO || IP_RECVDSTADDR */
218 #define check_cmsg_v4_pktinfo(c, t, l, a) 0
219 #endif /* HAVE_IP_PKTINFO || IP_RECVDSTADDR */
220 
221 #ifdef HAVE_IPV6_PKTINFO
222 
223 static inline struct in6_pktinfo *
cmsg2pktinfo6(struct cmsghdr * cmsgptr)224 cmsg2pktinfo6(struct cmsghdr *cmsgptr)
225 {
226     return (struct in6_pktinfo *)(void *)CMSG_DATA(cmsgptr);
227 }
228 
229 #define check_cmsg_v6_pktinfo check_cmsg_ipv6_pktinfo
230 static int
check_cmsg_ipv6_pktinfo(struct cmsghdr * cmsgptr,struct sockaddr * to,socklen_t * tolen,aux_addressing_info * auxaddr)231 check_cmsg_ipv6_pktinfo(struct cmsghdr *cmsgptr, struct sockaddr *to,
232                         socklen_t *tolen, aux_addressing_info *auxaddr)
233 {
234     struct in6_pktinfo *pktinfo;
235 
236     if (cmsgptr->cmsg_level == IPPROTO_IPV6 &&
237         cmsgptr->cmsg_type == IPV6_PKTINFO &&
238         *tolen >= sizeof(struct sockaddr_in6)) {
239 
240         memset(to, 0, sizeof(struct sockaddr_in6));
241         pktinfo = cmsg2pktinfo6(cmsgptr);
242         sa2sin6(to)->sin6_addr = pktinfo->ipi6_addr;
243         sa2sin6(to)->sin6_family = AF_INET6;
244         *tolen = sizeof(struct sockaddr_in6);
245         auxaddr->ipv6_ifindex = pktinfo->ipi6_ifindex;
246         return 1;
247     }
248     return 0;
249 }
250 #else /* HAVE_IPV6_PKTINFO */
251 #define check_cmsg_v6_pktinfo(c, t, l, a) 0
252 #endif /* HAVE_IPV6_PKTINFO */
253 
254 static int
check_cmsg_pktinfo(struct cmsghdr * cmsgptr,struct sockaddr * to,socklen_t * tolen,aux_addressing_info * auxaddr)255 check_cmsg_pktinfo(struct cmsghdr *cmsgptr, struct sockaddr *to,
256                    socklen_t *tolen, aux_addressing_info *auxaddr)
257 {
258     return check_cmsg_v4_pktinfo(cmsgptr, to, tolen, auxaddr) ||
259            check_cmsg_v6_pktinfo(cmsgptr, to, tolen, auxaddr);
260 }
261 
262 /*
263  * Receive a message from a socket.
264  *
265  * Arguments:
266  *  sock
267  *  buf     - The buffer to store the message in.
268  *  len     - buf length
269  *  flags
270  *  from    - Set to the address that sent the message
271  *  fromlen
272  *  to      - Set to the address that the message was sent to if possible.
273  *            May not be set in certain cases such as if pktinfo support is
274  *            missing. May be NULL.
275  *  tolen
276  *  auxaddr - Miscellaneous address information.
277  *
278  * Returns 0 on success, otherwise an error code.
279  */
280 krb5_error_code
recv_from_to(int sock,void * buf,size_t len,int flags,struct sockaddr * from,socklen_t * fromlen,struct sockaddr * to,socklen_t * tolen,aux_addressing_info * auxaddr)281 recv_from_to(int sock, void *buf, size_t len, int flags,
282              struct sockaddr *from, socklen_t * fromlen,
283              struct sockaddr *to, socklen_t * tolen,
284              aux_addressing_info *auxaddr)
285 
286 {
287     int r;
288     struct iovec iov;
289     char cmsg[CMSG_SPACE(sizeof(union pktinfo))];
290     struct cmsghdr *cmsgptr;
291     struct msghdr msg;
292 
293     /* Don't use pktinfo if the socket isn't bound to a wildcard address. */
294     r = is_socket_bound_to_wildcard(sock);
295     if (r < 0)
296         return errno;
297 
298     if (!to || !tolen || !r)
299         return recvfrom(sock, buf, len, flags, from, fromlen);
300 
301     /* Clobber with something recognizable in case we can't extract the address
302      * but try to use it anyways. */
303     memset(to, 0x40, *tolen);
304 
305     iov.iov_base = buf;
306     iov.iov_len = len;
307     memset(&msg, 0, sizeof(msg));
308     msg.msg_name = from;
309     msg.msg_namelen = *fromlen;
310     msg.msg_iov = &iov;
311     msg.msg_iovlen = 1;
312     msg.msg_control = cmsg;
313     msg.msg_controllen = sizeof(cmsg);
314 
315     r = recvmsg(sock, &msg, flags);
316     if (r < 0)
317         return r;
318     *fromlen = msg.msg_namelen;
319 
320     /*
321      * On Darwin (and presumably all *BSD with KAME stacks), CMSG_FIRSTHDR
322      * doesn't check for a non-zero controllen.  RFC 3542 recommends making
323      * this check, even though the (new) spec for CMSG_FIRSTHDR says it's
324      * supposed to do the check.
325      */
326     if (msg.msg_controllen) {
327         cmsgptr = CMSG_FIRSTHDR(&msg);
328         while (cmsgptr) {
329             if (check_cmsg_pktinfo(cmsgptr, to, tolen, auxaddr))
330                 return r;
331             cmsgptr = CMSG_NXTHDR(&msg, cmsgptr);
332         }
333     }
334     /* No info about destination addr was available.  */
335     *tolen = 0;
336     return r;
337 }
338 
339 #ifdef HAVE_IP_PKTINFO
340 
341 #define set_msg_from_ipv4 set_msg_from_ip_pktinfo
342 static krb5_error_code
set_msg_from_ip_pktinfo(struct msghdr * msg,struct cmsghdr * cmsgptr,struct sockaddr * from,socklen_t fromlen,aux_addressing_info * auxaddr)343 set_msg_from_ip_pktinfo(struct msghdr *msg, struct cmsghdr *cmsgptr,
344                         struct sockaddr *from, socklen_t fromlen,
345                         aux_addressing_info *auxaddr)
346 {
347     struct in_pktinfo *p = cmsg2pktinfo(cmsgptr);
348     const struct sockaddr_in *from4 = sa2sin(from);
349 
350     if (fromlen != sizeof(struct sockaddr_in))
351         return EINVAL;
352     cmsgptr->cmsg_level = IPPROTO_IP;
353     cmsgptr->cmsg_type = IP_PKTINFO;
354     cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
355     p->ipi_spec_dst = from4->sin_addr;
356 
357     msg->msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo));
358     return 0;
359 }
360 
361 #elif defined(IP_SENDSRCADDR) /* HAVE_IP_PKTINFO */
362 
363 #define set_msg_from_ipv4 set_msg_from_ip_sendsrcaddr
364 static krb5_error_code
set_msg_from_ip_sendsrcaddr(struct msghdr * msg,struct cmsghdr * cmsgptr,struct sockaddr * from,socklen_t fromlen,aux_addressing_info * auxaddr)365 set_msg_from_ip_sendsrcaddr(struct msghdr *msg, struct cmsghdr *cmsgptr,
366                             struct sockaddr *from, socklen_t fromlen,
367                             aux_addressing_info *auxaddr)
368 {
369     struct in_addr *sin_addr = cmsg2sin(cmsgptr);
370     const struct sockaddr_in *from4 = sa2sin(from);
371     if (fromlen != sizeof(struct sockaddr_in))
372         return EINVAL;
373     cmsgptr->cmsg_level = IPPROTO_IP;
374     cmsgptr->cmsg_type = IP_SENDSRCADDR;
375     cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
376     msg->msg_controllen = CMSG_SPACE(sizeof(struct in_addr));
377     *sin_addr = from4->sin_addr;
378     return 0;
379 }
380 
381 #else /* HAVE_IP_PKTINFO || IP_SENDSRCADDR */
382 #define set_msg_from_ipv4(m, c, f, l, a) EINVAL
383 #endif /* HAVE_IP_PKTINFO || IP_SENDSRCADDR */
384 
385 #ifdef HAVE_IPV6_PKTINFO
386 
387 #define set_msg_from_ipv6 set_msg_from_ipv6_pktinfo
388 static krb5_error_code
set_msg_from_ipv6_pktinfo(struct msghdr * msg,struct cmsghdr * cmsgptr,struct sockaddr * from,socklen_t fromlen,aux_addressing_info * auxaddr)389 set_msg_from_ipv6_pktinfo(struct msghdr *msg, struct cmsghdr *cmsgptr,
390                           struct sockaddr *from, socklen_t fromlen,
391                           aux_addressing_info *auxaddr)
392 {
393     struct in6_pktinfo *p = cmsg2pktinfo6(cmsgptr);
394     const struct sockaddr_in6 *from6 = sa2sin6(from);
395 
396     if (fromlen != sizeof(struct sockaddr_in6))
397         return EINVAL;
398     cmsgptr->cmsg_level = IPPROTO_IPV6;
399     cmsgptr->cmsg_type = IPV6_PKTINFO;
400     cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
401 
402     p->ipi6_addr = from6->sin6_addr;
403     /*
404      * Because of the possibility of asymmetric routing, we
405      * normally don't want to specify an interface.  However,
406      * macOS doesn't like sending from a link-local address
407      * (which can come up in testing at least, if you wind up
408      * with a "foo.local" name) unless we do specify the
409      * interface.
410      */
411     if (IN6_IS_ADDR_LINKLOCAL(&from6->sin6_addr))
412         p->ipi6_ifindex = auxaddr->ipv6_ifindex;
413     /* otherwise, already zero */
414 
415     msg->msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
416     return 0;
417 }
418 
419 #else /* HAVE_IPV6_PKTINFO */
420 #define set_msg_from_ipv6(m, c, f, l, a) EINVAL
421 #endif /* HAVE_IPV6_PKTINFO */
422 
423 static krb5_error_code
set_msg_from(int family,struct msghdr * msg,struct cmsghdr * cmsgptr,struct sockaddr * from,socklen_t fromlen,aux_addressing_info * auxaddr)424 set_msg_from(int family, struct msghdr *msg, struct cmsghdr *cmsgptr,
425              struct sockaddr *from, socklen_t fromlen,
426              aux_addressing_info *auxaddr)
427 {
428     switch (family) {
429     case AF_INET:
430         return set_msg_from_ipv4(msg, cmsgptr, from, fromlen, auxaddr);
431     case AF_INET6:
432         return set_msg_from_ipv6(msg, cmsgptr, from, fromlen, auxaddr);
433     }
434 
435     return EINVAL;
436 }
437 
438 /*
439  * Send a message to an address.
440  *
441  * Arguments:
442  *  sock
443  *  buf     - The message to send.
444  *  len     - buf length
445  *  flags
446  *  to      - The address to send the message to.
447  *  tolen
448  *  from    - The address to attempt to send the message from. May be NULL.
449  *  fromlen
450  *  auxaddr - Miscellaneous address information.
451  *
452  * Returns 0 on success, otherwise an error code.
453  */
454 krb5_error_code
send_to_from(int sock,void * buf,size_t len,int flags,const struct sockaddr * to,socklen_t tolen,struct sockaddr * from,socklen_t fromlen,aux_addressing_info * auxaddr)455 send_to_from(int sock, void *buf, size_t len, int flags,
456              const struct sockaddr *to, socklen_t tolen, struct sockaddr *from,
457              socklen_t fromlen, aux_addressing_info *auxaddr)
458 {
459     int r;
460     struct iovec iov;
461     struct msghdr msg;
462     struct cmsghdr *cmsgptr;
463     char cbuf[CMSG_SPACE(sizeof(union pktinfo))];
464 
465     /* Don't use pktinfo if the socket isn't bound to a wildcard address. */
466     r = is_socket_bound_to_wildcard(sock);
467     if (r < 0)
468         return errno;
469 
470     if (from == NULL || fromlen == 0 || from->sa_family != to->sa_family || !r)
471         goto use_sendto;
472 
473     iov.iov_base = buf;
474     iov.iov_len = len;
475     /* Truncation?  */
476     if (iov.iov_len != len)
477         return EINVAL;
478     memset(cbuf, 0, sizeof(cbuf));
479     memset(&msg, 0, sizeof(msg));
480     msg.msg_name = (void *)to;
481     msg.msg_namelen = tolen;
482     msg.msg_iov = &iov;
483     msg.msg_iovlen = 1;
484     msg.msg_control = cbuf;
485     /* CMSG_FIRSTHDR needs a non-zero controllen, or it'll return NULL on
486      * Linux. */
487     msg.msg_controllen = sizeof(cbuf);
488     cmsgptr = CMSG_FIRSTHDR(&msg);
489     msg.msg_controllen = 0;
490 
491     if (set_msg_from(from->sa_family, &msg, cmsgptr, from, fromlen, auxaddr))
492         goto use_sendto;
493     return sendmsg(sock, &msg, flags);
494 
495 use_sendto:
496     return sendto(sock, buf, len, flags, to, tolen);
497 }
498 
499 #else /* HAVE_PKTINFO_SUPPORT && CMSG_SPACE */
500 
501 krb5_error_code
recv_from_to(int sock,void * buf,size_t len,int flags,struct sockaddr * from,socklen_t * fromlen,struct sockaddr * to,socklen_t * tolen,aux_addressing_info * auxaddr)502 recv_from_to(int sock, void *buf, size_t len, int flags,
503              struct sockaddr *from, socklen_t *fromlen,
504              struct sockaddr *to, socklen_t *tolen,
505              aux_addressing_info *auxaddr)
506 {
507     if (to && tolen) {
508         /* Clobber with something recognizable in case we try to use the
509          * address. */
510         memset(to, 0x40, *tolen);
511         *tolen = 0;
512     }
513 
514     return recvfrom(sock, buf, len, flags, from, fromlen);
515 }
516 
517 krb5_error_code
send_to_from(int sock,void * buf,size_t len,int flags,const struct sockaddr * to,socklen_t tolen,struct sockaddr * from,socklen_t fromlen,aux_addressing_info * auxaddr)518 send_to_from(int sock, void *buf, size_t len, int flags,
519              const struct sockaddr *to, socklen_t tolen,
520              struct sockaddr *from, socklen_t fromlen,
521              aux_addressing_info *auxaddr)
522 {
523     return sendto(sock, buf, len, flags, to, tolen);
524 }
525 
526 #endif /* HAVE_PKTINFO_SUPPORT && CMSG_SPACE */
527