summaryrefslogtreecommitdiffstats
path: root/meta/recipes-connectivity/dhcp/dhcp/dhcp-xen-checksum.patch
blob: 62c279db1293fb98ae10c3048ecc1208d79fa796 (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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
dhcp-client: Ignore partial checksums

dhclient will fail to get an IP address if run inside a guest when traffic is
flowing over a virtual network interface.  The user will see the error
message:

  5 bad udp checksums in 5 packets
  No DHCPOFFERS received.
  Unable to obtain a lease on first try.  Exiting.
  Failed to bring up eth0.

This is because Linux only uses partial checksums for packets that go over
virtual network interfaces and dhclient does not like this.

  See linux kernel commit 78ea85f17b15390e30d8b47488ec7b6cf0790663
  ("net: skbuff: improve comment on checksumming")

An application can detect this behaviour by checking for the
TP_STATUS_CSUMNOTREADY flag in the tp_status field.

  See linux kernel commit 8dc4194474159660d7f37c495e3fc3f10d0db8cc
  ("Add optional checksum computation for recvmsg")

An extra parameter is added to decode_udp_ip_header() in dhclient to indicate
whether or not dhclient should ignore partial checksums.  This is used
when the TP_STATUS_CSUMNOTREADY bit is set by the guest kernel.

This fix has been included in Fedora and Ubuntu, however it has not yet been
accepted by ISC upstream.  Likely because it is specific to behaviour in Linux
and other UNIX variants do not seem to be affected.

The patch was imported from the dhcp source RPM in Fedora 21
  (http://pkgs.fedoraproject.org/cgit/dhcp.git/tree/dhcp-xen-checksum.patch?h=f21)

Originally contributed to fedora-cvs-commit by David Cantrell on Jan 30 2007
  (https://www.redhat.com/archives/fedora-cvs-commits/2007-January/msg01442.html)

Submitted to dhcp-bugs@isc.org - [ISC-Bugs #22806] - by Michael S. Tsirkin
  (http://comments.gmane.org/gmane.comp.emulators.kvm.devel/65236)
  (https://lists.isc.org/pipermail/dhcp-hackers/2010-April/001835.html)

Upstream-Status: Submitted [dhcp-bugs@isc.org]
Signed-off-by: Rob Woolley <rob.woolley@windriver.com>
--
 common/bpf.c     |    2 -
 common/dlpi.c    |    2 -
 common/lpf.c     |   83 +++++++++++++++++++++++++++++++++++++++++--------------
 common/nit.c     |    2 -
 common/packet.c  |    4 +-
 common/upf.c     |    2 -
 includes/dhcpd.h |    2 -
 7 files changed, 70 insertions(+), 27 deletions(-)

diff --git a/common/bpf.c b/common/bpf.c
--- a/common/bpf.c
+++ b/common/bpf.c
@@ -481,7 +481,7 @@ ssize_t receive_packet (interface, buf,
 		/* Decode the IP and UDP headers... */
 		offset = decode_udp_ip_header(interface, interface->rbuf,
 					       interface->rbuf_offset,
-  					       from, hdr.bh_caplen, &paylen);
+  					       from, hdr.bh_caplen, &paylen, 0);
 
 		/* If the IP or UDP checksum was bad, skip the packet... */
 		if (offset < 0) {
diff --git a/common/dlpi.c b/common/dlpi.c
--- a/common/dlpi.c
+++ b/common/dlpi.c
@@ -691,7 +691,7 @@ ssize_t receive_packet (interface, buf,
 	length -= offset;
 #endif
 	offset = decode_udp_ip_header (interface, dbuf, bufix,
-				       from, length, &paylen);
+				       from, length, &paylen, 0);
 
 	/*
 	 * If the IP or UDP checksum was bad, skip the packet...
diff --git a/common/lpf.c b/common/lpf.c
--- a/common/lpf.c
+++ b/common/lpf.c
@@ -29,14 +29,15 @@
 
 #include "dhcpd.h"
 #if defined (USE_LPF_SEND) || defined (USE_LPF_RECEIVE)
+#include <sys/socket.h>
 #include <sys/uio.h>
 #include <errno.h>
 
 #include <asm/types.h>
 #include <linux/filter.h>
 #include <linux/if_ether.h>
+#include <linux/if_packet.h>
 #include <netinet/in_systm.h>
-#include <net/if_packet.h>
 #include "includes/netinet/ip.h"
 #include "includes/netinet/udp.h"
 #include "includes/netinet/if_ether.h"
@@ -51,6 +52,19 @@
 /* Reinitializes the specified interface after an address change.   This
    is not required for packet-filter APIs. */
 
+#ifndef PACKET_AUXDATA
+#define PACKET_AUXDATA 8
+
+struct tpacket_auxdata
+{
+	__u32		tp_status;
+	__u32		tp_len;
+	__u32		tp_snaplen;
+	__u16		tp_mac;
+	__u16		tp_net;
+};
+#endif
+
 #ifdef USE_LPF_SEND
 void if_reinitialize_send (info)
 	struct interface_info *info;
@@ -73,10 +87,14 @@ int if_register_lpf (info)
 	struct interface_info *info;
 {
 	int sock;
-	struct sockaddr sa;
+	union {
+		struct sockaddr_ll ll;
+		struct sockaddr common;
+	} sa;
+	struct ifreq ifr;
 
 	/* Make an LPF socket. */
-	if ((sock = socket(PF_PACKET, SOCK_PACKET,
+	if ((sock = socket(PF_PACKET, SOCK_RAW,
 			   htons((short)ETH_P_ALL))) < 0) {
 		if (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT ||
 		    errno == ESOCKTNOSUPPORT || errno == EPFNOSUPPORT ||
@@ -91,11 +109,17 @@ int if_register_lpf (info)
 		log_fatal ("Open a socket for LPF: %m");
 	}
 
+	memset (&ifr, 0, sizeof ifr);
+	strncpy (ifr.ifr_name, (const char *)info -> ifp, sizeof ifr.ifr_name);
+	ifr.ifr_name[IFNAMSIZ-1] = '\0';
+	if (ioctl (sock, SIOCGIFINDEX, &ifr))
+		log_fatal ("Failed to get interface index: %m");
+
 	/* Bind to the interface name */
 	memset (&sa, 0, sizeof sa);
-	sa.sa_family = AF_PACKET;
-	strncpy (sa.sa_data, (const char *)info -> ifp, sizeof sa.sa_data);
-	if (bind (sock, &sa, sizeof sa)) {
+	sa.ll.sll_family = AF_PACKET;
+	sa.ll.sll_ifindex = ifr.ifr_ifindex;
+	if (bind (sock, &sa.common, sizeof sa)) {
 		if (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT ||
 		    errno == ESOCKTNOSUPPORT || errno == EPFNOSUPPORT ||
 		    errno == EAFNOSUPPORT || errno == EINVAL) {
@@ -177,9 +201,18 @@ static void lpf_gen_filter_setup (struct
 void if_register_receive (info)
 	struct interface_info *info;
 {
+	int val;
+
 	/* Open a LPF device and hang it on this interface... */
 	info -> rfdesc = if_register_lpf (info);
 
+	val = 1;
+	if (setsockopt (info -> rfdesc, SOL_PACKET, PACKET_AUXDATA, &val,
+			sizeof val) < 0) {
+		if (errno != ENOPROTOOPT)
+			log_fatal ("Failed to set auxiliary packet data: %m");
+	}
+
 #if defined (HAVE_TR_SUPPORT)
 	if (info -> hw_address.hbuf [0] == HTYPE_IEEE802)
 		lpf_tr_filter_setup (info);
@@ -301,7 +334,6 @@ ssize_t send_packet (interface, packet,
 	double hh [16];
 	double ih [1536 / sizeof (double)];
 	unsigned char *buf = (unsigned char *)ih;
-	struct sockaddr_pkt sa;
 	int result;
 	int fudge;
 
@@ -322,17 +354,7 @@ ssize_t send_packet (interface, packet,
 				(unsigned char *)raw, len);
 	memcpy (buf + ibufp, raw, len);
 
-	/* For some reason, SOCK_PACKET sockets can't be connected,
-	   so we have to do a sentdo every time. */
-	memset (&sa, 0, sizeof sa);
-	sa.spkt_family = AF_PACKET;
-	strncpy ((char *)sa.spkt_device,
-		 (const char *)interface -> ifp, sizeof sa.spkt_device);
-	sa.spkt_protocol = htons(ETH_P_IP);
-
-	result = sendto (interface -> wfdesc,
-			 buf + fudge, ibufp + len - fudge, 0, 
-			 (const struct sockaddr *)&sa, sizeof sa);
+	result = write (interface -> wfdesc, buf + fudge, ibufp + len - fudge);
 	if (result < 0)
 		log_error ("send_packet: %m");
 	return result;
@@ -349,14 +371,35 @@ ssize_t receive_packet (interface, buf,
 {
 	int length = 0;
 	int offset = 0;
+	int nocsum = 0;
 	unsigned char ibuf [1536];
 	unsigned bufix = 0;
 	unsigned paylen;
+	unsigned char cmsgbuf[CMSG_LEN(sizeof(struct tpacket_auxdata))];
+	struct iovec iov = {
+		.iov_base = ibuf,
+		.iov_len = sizeof ibuf,
+	};
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+		.msg_control = cmsgbuf,
+		.msg_controllen = sizeof(cmsgbuf),
+	};
+	struct cmsghdr *cmsg;
 
-	length = read (interface -> rfdesc, ibuf, sizeof ibuf);
+	length = recvmsg (interface -> rfdesc, &msg, 0);
 	if (length <= 0)
 		return length;
 
+	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+		if (cmsg->cmsg_level == SOL_PACKET &&
+		    cmsg->cmsg_type == PACKET_AUXDATA) {
+			struct tpacket_auxdata *aux = (void *)CMSG_DATA(cmsg);
+			nocsum = aux->tp_status & TP_STATUS_CSUMNOTREADY;
+		}
+	}
+
 	bufix = 0;
 	/* Decode the physical header... */
 	offset = decode_hw_header (interface, ibuf, bufix, hfrom);
@@ -373,7 +416,7 @@ ssize_t receive_packet (interface, buf,
 
 	/* Decode the IP and UDP headers... */
 	offset = decode_udp_ip_header (interface, ibuf, bufix, from,
-				       (unsigned)length, &paylen);
+				       (unsigned)length, &paylen, nocsum);
 
 	/* If the IP or UDP checksum was bad, skip the packet... */
 	if (offset < 0)
diff --git a/common/nit.c b/common/nit.c
--- a/common/nit.c
+++ b/common/nit.c
@@ -363,7 +363,7 @@ ssize_t receive_packet (interface, buf,
 
 	/* Decode the IP and UDP headers... */
 	offset = decode_udp_ip_header (interface, ibuf, bufix,
-				       from, length, &paylen);
+				       from, length, &paylen, 0);
 
 	/* If the IP or UDP checksum was bad, skip the packet... */
 	if (offset < 0)
diff --git a/common/packet.c b/common/packet.c
--- a/common/packet.c
+++ b/common/packet.c
@@ -226,7 +226,7 @@ ssize_t
 decode_udp_ip_header(struct interface_info *interface,
 		     unsigned char *buf, unsigned bufix,
 		     struct sockaddr_in *from, unsigned buflen,
-		     unsigned *rbuflen)
+		     unsigned *rbuflen, int nocsum)
 {
   unsigned char *data;
   struct ip ip;
@@ -337,7 +337,7 @@ decode_udp_ip_header(struct interface_in
 					   8, IPPROTO_UDP + ulen))));
 
   udp_packets_seen++;
-  if (usum && usum != sum) {
+  if (!nocsum && usum && usum != sum) {
 	  udp_packets_bad_checksum++;
 	  if (udp_packets_seen > 4 &&
 	      (udp_packets_seen / udp_packets_bad_checksum) < 2) {
diff --git a/common/upf.c b/common/upf.c
--- a/common/upf.c
+++ b/common/upf.c
@@ -314,7 +314,7 @@ ssize_t receive_packet (interface, buf,
 
 	/* Decode the IP and UDP headers... */
 	offset = decode_udp_ip_header (interface, ibuf, bufix,
-				       from, length, &paylen);
+				       from, length, &paylen, 0);
 
 	/* If the IP or UDP checksum was bad, skip the packet... */
 	if (offset < 0)
diff --git a/includes/dhcpd.h b/includes/dhcpd.h
--- a/includes/dhcpd.h
+++ b/includes/dhcpd.h
@@ -2857,7 +2857,7 @@ ssize_t decode_hw_header (struct interfa
 			  unsigned, struct hardware *);
 ssize_t decode_udp_ip_header (struct interface_info *, unsigned char *,
 			      unsigned, struct sockaddr_in *,
-			      unsigned, unsigned *);
+			      unsigned, unsigned *, int);
 
 /* ethernet.c */
 void assemble_ethernet_header (struct interface_info *, unsigned char *,
-- 
1.8.1.2