84d83098cd7f9ea1cefb016857a77c31ac20f1cd
[oweals/busybox.git] / networking / udhcp / packet.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * packet.c -- packet ops
4  * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
5  *
6  * Licensed under GPLv2, see file LICENSE in this tarball for details.
7  */
8 #include <netinet/in.h>
9 #if (defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined _NEWLIB_VERSION
10 #include <netpacket/packet.h>
11 #include <net/ethernet.h>
12 #else
13 #include <asm/types.h>
14 #include <linux/if_packet.h>
15 #include <linux/if_ether.h>
16 #endif
17
18 #include "common.h"
19 #include "dhcpd.h"
20 #include "options.h"
21
22 void FAST_FUNC udhcp_init_header(struct dhcp_packet *packet, char type)
23 {
24         memset(packet, 0, sizeof(*packet));
25         packet->op = BOOTREQUEST; /* if client to a server */
26         switch (type) {
27         case DHCPOFFER:
28         case DHCPACK:
29         case DHCPNAK:
30                 packet->op = BOOTREPLY; /* if server to client */
31         }
32         packet->htype = 1; /* ethernet */
33         packet->hlen = 6;
34         packet->cookie = htonl(DHCP_MAGIC);
35         if (DHCP_END != 0)
36                 packet->options[0] = DHCP_END;
37         add_simple_option(packet->options, DHCP_MESSAGE_TYPE, type);
38 }
39
40 #if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 2
41 void FAST_FUNC udhcp_dump_packet(struct dhcp_packet *packet)
42 {
43         char buf[sizeof(packet->chaddr)*2 + 1];
44
45         if (dhcp_verbose < 2)
46                 return;
47
48         bb_info_msg(
49                 //" op %x"
50                 //" htype %x"
51                 " hlen %x"
52                 //" hops %x"
53                 " xid %x"
54                 //" secs %x"
55                 //" flags %x"
56                 " ciaddr %x"
57                 " yiaddr %x"
58                 " siaddr %x"
59                 " giaddr %x"
60                 //" chaddr %s"
61                 //" sname %s"
62                 //" file %s"
63                 //" cookie %x"
64                 //" options %s"
65                 //, packet->op
66                 //, packet->htype
67                 , packet->hlen
68                 //, packet->hops
69                 , packet->xid
70                 //, packet->secs
71                 //, packet->flags
72                 , packet->ciaddr
73                 , packet->yiaddr
74                 , packet->siaddr_nip
75                 , packet->gateway_nip
76                 //, packet->chaddr[16]
77                 //, packet->sname[64]
78                 //, packet->file[128]
79                 //, packet->cookie
80                 //, packet->options[]
81         );
82         *bin2hex(buf, (void *) packet->chaddr, sizeof(packet->chaddr)) = '\0';
83         bb_info_msg(" chaddr %s", buf);
84 }
85 #endif
86
87 /* Read a packet from socket fd, return -1 on read error, -2 on packet error */
88 int FAST_FUNC udhcp_recv_kernel_packet(struct dhcp_packet *packet, int fd)
89 {
90         int bytes;
91         unsigned char *vendor;
92
93         memset(packet, 0, sizeof(*packet));
94         bytes = safe_read(fd, packet, sizeof(*packet));
95         if (bytes < 0) {
96                 log1("Packet read error, ignoring");
97                 return bytes; /* returns -1 */
98         }
99
100         if (packet->cookie != htonl(DHCP_MAGIC)) {
101                 bb_info_msg("Packet with bad magic, ignoring");
102                 return -2;
103         }
104         log1("Received a packet");
105         udhcp_dump_packet(packet);
106
107         if (packet->op == BOOTREQUEST) {
108                 vendor = get_option(packet, DHCP_VENDOR);
109                 if (vendor) {
110 #if 0
111                         static const char broken_vendors[][8] = {
112                                 "MSFT 98",
113                                 ""
114                         };
115                         int i;
116                         for (i = 0; broken_vendors[i][0]; i++) {
117                                 if (vendor[OPT_LEN - 2] == (uint8_t)strlen(broken_vendors[i])
118                                  && !strncmp((char*)vendor, broken_vendors[i], vendor[OPT_LEN - 2])
119                                 ) {
120                                         log1("Broken client (%s), forcing broadcast replies",
121                                                 broken_vendors[i]);
122                                         packet->flags |= htons(BROADCAST_FLAG);
123                                 }
124                         }
125 #else
126                         if (vendor[OPT_LEN - 2] == (uint8_t)(sizeof("MSFT 98")-1)
127                          && memcmp(vendor, "MSFT 98", sizeof("MSFT 98")-1) == 0
128                         ) {
129                                 log1("Broken client (%s), forcing broadcast replies", "MSFT 98");
130                                 packet->flags |= htons(BROADCAST_FLAG);
131                         }
132 #endif
133                 }
134         }
135
136         return bytes;
137 }
138
139 uint16_t FAST_FUNC udhcp_checksum(void *addr, int count)
140 {
141         /* Compute Internet Checksum for "count" bytes
142          * beginning at location "addr".
143          */
144         int32_t sum = 0;
145         uint16_t *source = (uint16_t *) addr;
146
147         while (count > 1)  {
148                 /*  This is the inner loop */
149                 sum += *source++;
150                 count -= 2;
151         }
152
153         /*  Add left-over byte, if any */
154         if (count > 0) {
155                 /* Make sure that the left-over byte is added correctly both
156                  * with little and big endian hosts */
157                 uint16_t tmp = 0;
158                 *(uint8_t*)&tmp = *(uint8_t*)source;
159                 sum += tmp;
160         }
161         /*  Fold 32-bit sum to 16 bits */
162         while (sum >> 16)
163                 sum = (sum & 0xffff) + (sum >> 16);
164
165         return ~sum;
166 }
167
168 /* Construct a ip/udp header for a packet, send packet */
169 int FAST_FUNC udhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt,
170                 uint32_t source_ip, int source_port,
171                 uint32_t dest_ip, int dest_port, const uint8_t *dest_arp,
172                 int ifindex)
173 {
174         struct sockaddr_ll dest;
175         struct ip_udp_dhcp_packet packet;
176         int fd;
177         int result = -1;
178         const char *msg;
179
180         enum {
181                 IP_UPD_DHCP_SIZE = sizeof(struct ip_udp_dhcp_packet) - CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS,
182                 UPD_DHCP_SIZE    = IP_UPD_DHCP_SIZE - offsetof(struct ip_udp_dhcp_packet, udp),
183         };
184
185         fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
186         if (fd < 0) {
187                 msg = "socket(%s)";
188                 goto ret_msg;
189         }
190
191         memset(&dest, 0, sizeof(dest));
192         memset(&packet, 0, sizeof(packet));
193         packet.data = *dhcp_pkt; /* struct copy */
194
195         dest.sll_family = AF_PACKET;
196         dest.sll_protocol = htons(ETH_P_IP);
197         dest.sll_ifindex = ifindex;
198         dest.sll_halen = 6;
199         memcpy(dest.sll_addr, dest_arp, 6);
200         if (bind(fd, (struct sockaddr *)&dest, sizeof(dest)) < 0) {
201                 msg = "bind(%s)";
202                 goto ret_close;
203         }
204
205         packet.ip.protocol = IPPROTO_UDP;
206         packet.ip.saddr = source_ip;
207         packet.ip.daddr = dest_ip;
208         packet.udp.source = htons(source_port);
209         packet.udp.dest = htons(dest_port);
210         /* size, excluding IP header: */
211         packet.udp.len = htons(UPD_DHCP_SIZE);
212         /* for UDP checksumming, ip.len is set to UDP packet len */
213         packet.ip.tot_len = packet.udp.len;
214         packet.udp.check = udhcp_checksum(&packet, IP_UPD_DHCP_SIZE);
215         /* but for sending, it is set to IP packet len */
216         packet.ip.tot_len = htons(IP_UPD_DHCP_SIZE);
217         packet.ip.ihl = sizeof(packet.ip) >> 2;
218         packet.ip.version = IPVERSION;
219         packet.ip.ttl = IPDEFTTL;
220         packet.ip.check = udhcp_checksum(&packet.ip, sizeof(packet.ip));
221
222         /* Currently we send full-sized DHCP packets (zero padded).
223          * If you need to change this: last byte of the packet is
224          * packet.data.options[end_option(packet.data.options)]
225          */
226         udhcp_dump_packet(dhcp_pkt);
227         result = sendto(fd, &packet, IP_UPD_DHCP_SIZE, 0,
228                                 (struct sockaddr *) &dest, sizeof(dest));
229         msg = "sendto";
230  ret_close:
231         close(fd);
232         /* FIXME: and if result >= 0 but != IP_UPD_DHCP_SIZE? */
233         if (result < 0) {
234  ret_msg:
235                 bb_perror_msg(msg, "PACKET");
236         }
237         return result;
238 }
239
240 /* Let the kernel do all the work for packet generation */
241 int FAST_FUNC udhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt,
242                 uint32_t source_ip, int source_port,
243                 uint32_t dest_ip, int dest_port)
244 {
245         struct sockaddr_in client;
246         int fd;
247         int result = -1;
248         const char *msg;
249
250         enum {
251                 DHCP_SIZE = sizeof(struct dhcp_packet) - CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS,
252         };
253
254         fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
255         if (fd < 0) {
256                 msg = "socket(%s)";
257                 goto ret_msg;
258         }
259         setsockopt_reuseaddr(fd);
260
261         memset(&client, 0, sizeof(client));
262         client.sin_family = AF_INET;
263         client.sin_port = htons(source_port);
264         client.sin_addr.s_addr = source_ip;
265         if (bind(fd, (struct sockaddr *)&client, sizeof(client)) == -1) {
266                 msg = "bind(%s)";
267                 goto ret_close;
268         }
269
270         memset(&client, 0, sizeof(client));
271         client.sin_family = AF_INET;
272         client.sin_port = htons(dest_port);
273         client.sin_addr.s_addr = dest_ip;
274         if (connect(fd, (struct sockaddr *)&client, sizeof(client)) == -1) {
275                 msg = "connect";
276                 goto ret_close;
277         }
278
279         /* Currently we send full-sized DHCP packets (see above) */
280         udhcp_dump_packet(dhcp_pkt);
281         result = safe_write(fd, dhcp_pkt, DHCP_SIZE);
282         msg = "write";
283  ret_close:
284         close(fd);
285         /* FIXME: and if result >= 0 but != DHCP_SIZE? */
286         if (result < 0) {
287  ret_msg:
288                 bb_perror_msg(msg, "UDP");
289         }
290         return result;
291 }