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