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_in * to,aux_addressing_info * auxaddr)170 check_cmsg_ip_pktinfo(struct cmsghdr *cmsgptr, struct sockaddr_in *to,
171 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 memset(to, 0, sizeof(*to));
178 pktinfo = cmsg2pktinfo(cmsgptr);
179 to->sin_addr = pktinfo->ipi_addr;
180 to->sin_family = AF_INET;
181 return 1;
182 }
183 return 0;
184 }
185
186 #elif defined(IP_RECVDSTADDR) /* HAVE_IP_PKTINFO */
187
188 static inline struct in_addr *
cmsg2sin(struct cmsghdr * cmsgptr)189 cmsg2sin(struct cmsghdr *cmsgptr)
190 {
191 return (struct in_addr *)(void *)CMSG_DATA(cmsgptr);
192 }
193
194 #define check_cmsg_v4_pktinfo check_cmsg_ip_recvdstaddr
195 static int
check_cmsg_ip_recvdstaddr(struct cmsghdr * cmsgptr,struct sockaddr_in * to,aux_addressing_info * auxaddr)196 check_cmsg_ip_recvdstaddr(struct cmsghdr *cmsgptr, struct sockaddr_in *to,
197 aux_addressing_info *auxaddr)
198 {
199 struct in_addr *sin_addr;
200
201 if (cmsgptr->cmsg_level == IPPROTO_IP &&
202 cmsgptr->cmsg_type == IP_RECVDSTADDR) {
203 memset(to, 0, sizeof(*to));
204 sin_addr = cmsg2sin(cmsgptr);
205 to->sin_addr = *sin_addr;
206 to->sin_family = AF_INET;
207 return 1;
208 }
209 return 0;
210 }
211
212 #else /* HAVE_IP_PKTINFO || IP_RECVDSTADDR */
213 #define check_cmsg_v4_pktinfo(c, t, l, a) 0
214 #endif /* HAVE_IP_PKTINFO || IP_RECVDSTADDR */
215
216 #ifdef HAVE_IPV6_PKTINFO
217
218 static inline struct in6_pktinfo *
cmsg2pktinfo6(struct cmsghdr * cmsgptr)219 cmsg2pktinfo6(struct cmsghdr *cmsgptr)
220 {
221 return (struct in6_pktinfo *)(void *)CMSG_DATA(cmsgptr);
222 }
223
224 #define check_cmsg_v6_pktinfo check_cmsg_ipv6_pktinfo
225 static int
check_cmsg_ipv6_pktinfo(struct cmsghdr * cmsgptr,struct sockaddr_in6 * to,aux_addressing_info * auxaddr)226 check_cmsg_ipv6_pktinfo(struct cmsghdr *cmsgptr, struct sockaddr_in6 *to,
227 aux_addressing_info *auxaddr)
228 {
229 struct in6_pktinfo *pktinfo;
230
231 if (cmsgptr->cmsg_level == IPPROTO_IPV6 &&
232 cmsgptr->cmsg_type == IPV6_PKTINFO) {
233 memset(to, 0, sizeof(*to));
234 pktinfo = cmsg2pktinfo6(cmsgptr);
235 to->sin6_addr = pktinfo->ipi6_addr;
236 to->sin6_family = AF_INET6;
237 auxaddr->ipv6_ifindex = pktinfo->ipi6_ifindex;
238 return 1;
239 }
240 return 0;
241 }
242 #else /* HAVE_IPV6_PKTINFO */
243 #define check_cmsg_v6_pktinfo(c, t, l, a) 0
244 #endif /* HAVE_IPV6_PKTINFO */
245
246 static int
check_cmsg_pktinfo(struct cmsghdr * cmsgptr,struct sockaddr_storage * to,aux_addressing_info * auxaddr)247 check_cmsg_pktinfo(struct cmsghdr *cmsgptr, struct sockaddr_storage *to,
248 aux_addressing_info *auxaddr)
249 {
250 return check_cmsg_v4_pktinfo(cmsgptr, ss2sin(to), auxaddr) ||
251 check_cmsg_v6_pktinfo(cmsgptr, ss2sin6(to), auxaddr);
252 }
253
254 /*
255 * Receive a message from a socket.
256 *
257 * Arguments:
258 * sock
259 * buf - The buffer to store the message in.
260 * len - buf length
261 * flags
262 * from - Set to the address that sent the message
263 * to - Set to the address that the message was sent to if possible.
264 * May not be set in certain cases such as if pktinfo support is
265 * missing. May be NULL.
266 * auxaddr - Miscellaneous address information.
267 *
268 * Returns 0 on success, otherwise an error code.
269 */
270 krb5_error_code
recv_from_to(int sock,void * buf,size_t len,int flags,struct sockaddr_storage * from,struct sockaddr_storage * to,aux_addressing_info * auxaddr)271 recv_from_to(int sock, void *buf, size_t len, int flags,
272 struct sockaddr_storage *from, struct sockaddr_storage *to,
273 aux_addressing_info *auxaddr)
274
275 {
276 int r;
277 struct iovec iov;
278 char cmsg[CMSG_SPACE(sizeof(union pktinfo))];
279 struct cmsghdr *cmsgptr;
280 struct msghdr msg;
281 socklen_t fromlen = sizeof(*from);
282
283 /* Don't use pktinfo if the socket isn't bound to a wildcard address. */
284 r = is_socket_bound_to_wildcard(sock);
285 if (r < 0)
286 return errno;
287
288 if (to == NULL || !r)
289 return recvfrom(sock, buf, len, flags, ss2sa(from), &fromlen);
290
291 /* Clobber with something recognizable in case we can't extract the address
292 * but try to use it anyways. */
293 memset(to, 0x40, sizeof(*to));
294 to->ss_family = AF_UNSPEC;
295
296 iov.iov_base = buf;
297 iov.iov_len = len;
298 memset(&msg, 0, sizeof(msg));
299 msg.msg_name = ss2sa(from);
300 msg.msg_namelen = sizeof(*from);
301 msg.msg_iov = &iov;
302 msg.msg_iovlen = 1;
303 msg.msg_control = cmsg;
304 msg.msg_controllen = sizeof(cmsg);
305
306 r = recvmsg(sock, &msg, flags);
307 if (r < 0)
308 return r;
309
310 /*
311 * On Darwin (and presumably all *BSD with KAME stacks), CMSG_FIRSTHDR
312 * doesn't check for a non-zero controllen. RFC 3542 recommends making
313 * this check, even though the (new) spec for CMSG_FIRSTHDR says it's
314 * supposed to do the check.
315 */
316 if (msg.msg_controllen) {
317 cmsgptr = CMSG_FIRSTHDR(&msg);
318 while (cmsgptr) {
319 if (check_cmsg_pktinfo(cmsgptr, to, auxaddr))
320 return r;
321 cmsgptr = CMSG_NXTHDR(&msg, cmsgptr);
322 }
323 }
324 /* No info about destination addr was available. */
325 return r;
326 }
327
328 #ifdef HAVE_IP_PKTINFO
329
330 #define set_msg_from_ipv4 set_msg_from_ip_pktinfo
331 static krb5_error_code
set_msg_from_ip_pktinfo(struct msghdr * msg,struct cmsghdr * cmsgptr,const struct sockaddr_in * from,aux_addressing_info * auxaddr)332 set_msg_from_ip_pktinfo(struct msghdr *msg, struct cmsghdr *cmsgptr,
333 const struct sockaddr_in *from,
334 aux_addressing_info *auxaddr)
335 {
336 struct in_pktinfo *p = cmsg2pktinfo(cmsgptr);
337
338 cmsgptr->cmsg_level = IPPROTO_IP;
339 cmsgptr->cmsg_type = IP_PKTINFO;
340 cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
341 p->ipi_spec_dst = from->sin_addr;
342
343 msg->msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo));
344 return 0;
345 }
346
347 #elif defined(IP_SENDSRCADDR) /* HAVE_IP_PKTINFO */
348
349 #define set_msg_from_ipv4 set_msg_from_ip_sendsrcaddr
350 static krb5_error_code
set_msg_from_ip_sendsrcaddr(struct msghdr * msg,struct cmsghdr * cmsgptr,const struct sockaddr_in * from,aux_addressing_info * auxaddr)351 set_msg_from_ip_sendsrcaddr(struct msghdr *msg, struct cmsghdr *cmsgptr,
352 const struct sockaddr_in *from,
353 aux_addressing_info *auxaddr)
354 {
355 struct in_addr *sin_addr = cmsg2sin(cmsgptr);
356
357 cmsgptr->cmsg_level = IPPROTO_IP;
358 cmsgptr->cmsg_type = IP_SENDSRCADDR;
359 cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
360 msg->msg_controllen = CMSG_SPACE(sizeof(struct in_addr));
361 *sin_addr = from->sin_addr;
362 return 0;
363 }
364
365 #else /* HAVE_IP_PKTINFO || IP_SENDSRCADDR */
366 #define set_msg_from_ipv4(m, c, f, l, a) EINVAL
367 #endif /* HAVE_IP_PKTINFO || IP_SENDSRCADDR */
368
369 #ifdef HAVE_IPV6_PKTINFO
370
371 #define set_msg_from_ipv6 set_msg_from_ipv6_pktinfo
372 static krb5_error_code
set_msg_from_ipv6_pktinfo(struct msghdr * msg,struct cmsghdr * cmsgptr,const struct sockaddr_in6 * from,aux_addressing_info * auxaddr)373 set_msg_from_ipv6_pktinfo(struct msghdr *msg, struct cmsghdr *cmsgptr,
374 const struct sockaddr_in6 *from,
375 aux_addressing_info *auxaddr)
376 {
377 struct in6_pktinfo *p = cmsg2pktinfo6(cmsgptr);
378
379 cmsgptr->cmsg_level = IPPROTO_IPV6;
380 cmsgptr->cmsg_type = IPV6_PKTINFO;
381 cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
382
383 p->ipi6_addr = from->sin6_addr;
384 /*
385 * Because of the possibility of asymmetric routing, we
386 * normally don't want to specify an interface. However,
387 * macOS doesn't like sending from a link-local address
388 * (which can come up in testing at least, if you wind up
389 * with a "foo.local" name) unless we do specify the
390 * interface.
391 */
392 if (IN6_IS_ADDR_LINKLOCAL(&from->sin6_addr))
393 p->ipi6_ifindex = auxaddr->ipv6_ifindex;
394 /* otherwise, already zero */
395
396 msg->msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
397 return 0;
398 }
399
400 #else /* HAVE_IPV6_PKTINFO */
401 #define set_msg_from_ipv6(m, c, f, l, a) EINVAL
402 #endif /* HAVE_IPV6_PKTINFO */
403
404 static krb5_error_code
set_msg_from(struct msghdr * msg,struct cmsghdr * cmsgptr,const struct sockaddr * from,aux_addressing_info * auxaddr)405 set_msg_from(struct msghdr *msg, struct cmsghdr *cmsgptr,
406 const struct sockaddr *from, aux_addressing_info *auxaddr)
407 {
408 switch (from->sa_family) {
409 case AF_INET:
410 return set_msg_from_ipv4(msg, cmsgptr, sa2sin(from), auxaddr);
411 case AF_INET6:
412 return set_msg_from_ipv6(msg, cmsgptr, sa2sin6(from), auxaddr);
413 }
414
415 return EINVAL;
416 }
417
418 /*
419 * Send a message to an address.
420 *
421 * Arguments:
422 * sock
423 * buf - The message to send.
424 * len - buf length
425 * flags
426 * to - The address to send the message to.
427 * from - The address to attempt to send the message from. May be NULL.
428 * auxaddr - Miscellaneous address information.
429 *
430 * Returns 0 on success, otherwise an error code.
431 */
432 krb5_error_code
send_to_from(int sock,void * buf,size_t len,int flags,const struct sockaddr * to,const struct sockaddr * from,aux_addressing_info * auxaddr)433 send_to_from(int sock, void *buf, size_t len, int flags,
434 const struct sockaddr *to, const struct sockaddr *from,
435 aux_addressing_info *auxaddr)
436 {
437 int r;
438 struct iovec iov;
439 struct msghdr msg;
440 struct cmsghdr *cmsgptr;
441 char cbuf[CMSG_SPACE(sizeof(union pktinfo))];
442
443 /* Don't use pktinfo if the socket isn't bound to a wildcard address. */
444 r = is_socket_bound_to_wildcard(sock);
445 if (r < 0)
446 return errno;
447
448 if (from == NULL || from->sa_family != to->sa_family || !r)
449 goto use_sendto;
450
451 iov.iov_base = buf;
452 iov.iov_len = len;
453 /* Truncation? */
454 if (iov.iov_len != len)
455 return EINVAL;
456 memset(cbuf, 0, sizeof(cbuf));
457 memset(&msg, 0, sizeof(msg));
458 msg.msg_name = (void *)to;
459 msg.msg_namelen = sa_socklen(to);
460 msg.msg_iov = &iov;
461 msg.msg_iovlen = 1;
462 msg.msg_control = cbuf;
463 /* CMSG_FIRSTHDR needs a non-zero controllen, or it'll return NULL on
464 * Linux. */
465 msg.msg_controllen = sizeof(cbuf);
466 cmsgptr = CMSG_FIRSTHDR(&msg);
467 msg.msg_controllen = 0;
468
469 if (set_msg_from(&msg, cmsgptr, from, auxaddr))
470 goto use_sendto;
471 return sendmsg(sock, &msg, flags);
472
473 use_sendto:
474 return sendto(sock, buf, len, flags, to, sa_socklen(to));
475 }
476
477 #else /* HAVE_PKTINFO_SUPPORT && CMSG_SPACE */
478
479 krb5_error_code
recv_from_to(int sock,void * buf,size_t len,int flags,struct sockaddr_storage * from,struct sockaddr_storage * to,aux_addressing_info * auxaddr)480 recv_from_to(int sock, void *buf, size_t len, int flags,
481 struct sockaddr_storage *from, struct sockaddr_storage *to,
482 aux_addressing_info *auxaddr)
483 {
484 socklen_t fromlen = sizeof(*from);
485
486 if (to != NULL) {
487 /* Clobber with something recognizable in case we try to use the
488 * address. */
489 memset(to, 0x40, sizeof(*to));
490 to->ss_family = AF_UNSPEC;
491 }
492
493 return recvfrom(sock, buf, len, flags, ss2sa(from), &fromlen);
494 }
495
496 krb5_error_code
send_to_from(int sock,void * buf,size_t len,int flags,const struct sockaddr * to,const struct sockaddr * from,aux_addressing_info * auxaddr)497 send_to_from(int sock, void *buf, size_t len, int flags,
498 const struct sockaddr *to, const struct sockaddr *from,
499 aux_addressing_info *auxaddr)
500 {
501 return sendto(sock, buf, len, flags, to, sa_socklen(to));
502 }
503
504 #endif /* HAVE_PKTINFO_SUPPORT && CMSG_SPACE */
505