aboutsummaryrefslogtreecommitdiff
path: root/libbb/udp_io.c
blob: 68355e6c4002ad3b1b027832e9facdd4864b9cb3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/* vi: set sw=4 ts=4: */
/*
 * Utility routines.
 *
 * Copyright (C) 2007 Denys Vlasenko
 *
 * Licensed under GPLv2, see file LICENSE in this source tree.
 */
#include "libbb.h"

#if defined(IPV6_PKTINFO) && !defined(IPV6_RECVPKTINFO)
# define IPV6_RECVPKTINFO IPV6_PKTINFO
#endif

/*
 * This asks kernel to let us know dst addr/port of incoming packets
 * We don't check for errors here. Not supported == won't be used
 */
void FAST_FUNC
socket_want_pktinfo(int fd UNUSED_PARAM)
{
#ifdef IP_PKTINFO
	setsockopt_1(fd, IPPROTO_IP, IP_PKTINFO);
#endif
#if ENABLE_FEATURE_IPV6 && defined(IPV6_RECVPKTINFO)
	setsockopt_1(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO);
#endif
}


ssize_t FAST_FUNC
send_to_from(int fd, void *buf, size_t len, int flags,
		const struct sockaddr *to,
		const struct sockaddr *from,
		socklen_t tolen)
{
#ifndef IP_PKTINFO
	(void)from; /* suppress "unused from" warning */
	return sendto(fd, buf, len, flags, to, tolen);
#else
	struct iovec iov[1];
	struct msghdr msg;
	union {
		char cmsg[CMSG_SPACE(sizeof(struct in_pktinfo))];
# if ENABLE_FEATURE_IPV6 && defined(IPV6_PKTINFO)
		char cmsg6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
# endif
	} u;
	struct cmsghdr* cmsgptr;

	if (from->sa_family != AF_INET
# if ENABLE_FEATURE_IPV6
	 && from->sa_family != AF_INET6
# endif
	) {
		/* ANY local address */
		return sendto(fd, buf, len, flags, to, tolen);
	}

	/* man recvmsg and man cmsg is needed to make sense of code below */

	iov[0].iov_base = buf;
	iov[0].iov_len = len;

	memset(&u, 0, sizeof(u));

	memset(&msg, 0, sizeof(msg));
	msg.msg_name = (void *)(struct sockaddr *)to; /* or compiler will annoy us */
	msg.msg_namelen = tolen;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;
	msg.msg_control = &u;
	msg.msg_controllen = sizeof(u);
	msg.msg_flags = flags;

	cmsgptr = CMSG_FIRSTHDR(&msg);
	/*
	 * Users report that to->sa_family can be AF_INET6 too,
	 * if "to" was acquired by recv_from_to(). IOW: recv_from_to()
	 * was seen showing IPv6 "from" even when the destination
	 * of received packet (our local address) was IPv4.
	 */
	if (/* to->sa_family == AF_INET && */ from->sa_family == AF_INET) {
		struct in_pktinfo *pktptr;
		cmsgptr->cmsg_level = IPPROTO_IP;
		cmsgptr->cmsg_type = IP_PKTINFO;
		cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
		pktptr = (struct in_pktinfo *)(CMSG_DATA(cmsgptr));
		/*pktptr->ipi_ifindex = 0; -- already done by memset(u...) */
		/* In general, CMSG_DATA() can be unaligned, but in this case
		 * we know for sure it is sufficiently aligned:
		 * CMSG_FIRSTHDR simply returns &u above,
		 * and CMSG_DATA returns &u + size_t + int + int.
		 * Thus direct assignment is ok:
		 */
		pktptr->ipi_spec_dst = ((struct sockaddr_in*)from)->sin_addr;
	}
# if ENABLE_FEATURE_IPV6 && defined(IPV6_PKTINFO)
	else if (/* to->sa_family == AF_INET6 && */ from->sa_family == AF_INET6) {
		struct in6_pktinfo *pktptr;
		cmsgptr->cmsg_level = IPPROTO_IPV6;
		cmsgptr->cmsg_type = IPV6_PKTINFO;
		cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
		pktptr = (struct in6_pktinfo *)(CMSG_DATA(cmsgptr));
		/* pktptr->ipi6_ifindex = 0; -- already done by memset(u...) */
		pktptr->ipi6_addr = ((struct sockaddr_in6*)from)->sin6_addr;
	}
# endif
	msg.msg_controllen = cmsgptr->cmsg_len;

	return sendmsg(fd, &msg, flags);
#endif
}

/* NB: this will never set port# in 'to'!
 * _Only_ IP/IPv6 address part of 'to' is _maybe_ modified.
 * Typical usage is to preinit 'to' with "default" value
 * before calling recv_from_to(). */
ssize_t FAST_FUNC
recv_from_to(int fd, void *buf, size_t len, int flags,
		struct sockaddr *from, struct sockaddr *to,
		socklen_t sa_size)
{
#ifndef IP_PKTINFO
	(void)to; /* suppress "unused to" warning */
	return recvfrom(fd, buf, len, flags, from, &sa_size);
#else
	/* man recvmsg and man cmsg is needed to make sense of code below */
	struct iovec iov[1];
	union {
		char cmsg[CMSG_SPACE(sizeof(struct in_pktinfo))];
# if ENABLE_FEATURE_IPV6 && defined(IPV6_PKTINFO)
		char cmsg6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
# endif
	} u;
	struct cmsghdr *cmsgptr;
	struct msghdr msg;
	ssize_t recv_length;

	iov[0].iov_base = buf;
	iov[0].iov_len = len;

	memset(&msg, 0, sizeof(msg));
	msg.msg_name = (struct sockaddr *)from;
	msg.msg_namelen = sa_size;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;
	msg.msg_control = &u;
	msg.msg_controllen = sizeof(u);

	recv_length = recvmsg(fd, &msg, flags);
	if (recv_length < 0)
		return recv_length;

# define to4 ((struct sockaddr_in*)to)
# define to6 ((struct sockaddr_in6*)to)
	/* Here we try to retrieve destination IP and memorize it */
	for (cmsgptr = CMSG_FIRSTHDR(&msg);
			cmsgptr != NULL;
			cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)
	) {
		if (cmsgptr->cmsg_level == IPPROTO_IP
		 && cmsgptr->cmsg_type == IP_PKTINFO
		) {
			const int IPI_ADDR_OFF = offsetof(struct in_pktinfo, ipi_addr);
			to->sa_family = AF_INET;
			/*# define pktinfo(cmsgptr) ( (struct in_pktinfo*)(CMSG_DATA(cmsgptr)) )*/
			/*to4->sin_addr = pktinfo(cmsgptr)->ipi_addr; - may be unaligned */
			memcpy(&to4->sin_addr, (char*)(CMSG_DATA(cmsgptr)) + IPI_ADDR_OFF, sizeof(to4->sin_addr));
			/*to4->sin_port = 123; - this data is not supplied by kernel */
			break;
		}
# if ENABLE_FEATURE_IPV6 && defined(IPV6_PKTINFO)
		if (cmsgptr->cmsg_level == IPPROTO_IPV6
		 && cmsgptr->cmsg_type == IPV6_PKTINFO
		) {
			const int IPI6_ADDR_OFF = offsetof(struct in6_pktinfo, ipi6_addr);
			to->sa_family = AF_INET6;
			/*#  define pktinfo(cmsgptr) ( (struct in6_pktinfo*)(CMSG_DATA(cmsgptr)) )*/
			/*to6->sin6_addr = pktinfo(cmsgptr)->ipi6_addr; - may be unaligned */
			memcpy(&to6->sin6_addr, (char*)(CMSG_DATA(cmsgptr)) + IPI6_ADDR_OFF, sizeof(to6->sin6_addr));
			/*to6->sin6_port = 123; */
			break;
		}
# endif
	}
	return recv_length;
#endif
}