udhcpd: reduce stack usage by ~700 bytes. +28 bytes code size
[oweals/busybox.git] / networking / udhcp / packet.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Packet ops
4  *
5  * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
6  *
7  * Licensed under GPLv2, see file LICENSE in this source tree.
8  */
9 #include <netinet/in.h>
10 #if (defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined _NEWLIB_VERSION
11 # include <netpacket/packet.h>
12 # include <net/ethernet.h>
13 #else
14 # include <asm/types.h>
15 # include <linux/if_packet.h>
16 # include <linux/if_ether.h>
17 #endif
18
19 #include "common.h"
20 #include "dhcpd.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         udhcp_add_simple_option(packet, 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 = udhcp_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 - OPT_DATA] == (uint8_t)strlen(broken_vendors[i])
118                                  && strncmp((char*)vendor, broken_vendors[i], vendor[OPT_LEN - OPT_DATA]) == 0
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 - OPT_DATA] == (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_nip, int source_port,
171                 uint32_t dest_nip, int dest_port, const uint8_t *dest_arp,
172                 int ifindex)
173 {
174         struct sockaddr_ll dest_sll;
175         struct ip_udp_dhcp_packet packet;
176         unsigned padding;
177         int fd;
178         int result = -1;
179         const char *msg;
180
181         fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
182         if (fd < 0) {
183                 msg = "socket(%s)";
184                 goto ret_msg;
185         }
186
187         memset(&dest_sll, 0, sizeof(dest_sll));
188         memset(&packet, 0, offsetof(struct ip_udp_dhcp_packet, data));
189         packet.data = *dhcp_pkt; /* struct copy */
190
191         dest_sll.sll_family = AF_PACKET;
192         dest_sll.sll_protocol = htons(ETH_P_IP);
193         dest_sll.sll_ifindex = ifindex;
194         dest_sll.sll_halen = 6;
195         memcpy(dest_sll.sll_addr, dest_arp, 6);
196
197         if (bind(fd, (struct sockaddr *)&dest_sll, sizeof(dest_sll)) < 0) {
198                 msg = "bind(%s)";
199                 goto ret_close;
200         }
201
202         /* We were sending full-sized DHCP packets (zero padded),
203          * but some badly configured servers were seen dropping them.
204          * Apparently they drop all DHCP packets >576 *ethernet* octets big,
205          * whereas they may only drop packets >576 *IP* octets big
206          * (which for typical Ethernet II means 590 octets: 6+6+2 + 576).
207          *
208          * In order to work with those buggy servers,
209          * we truncate packets after end option byte.
210          */
211         padding = DHCP_OPTIONS_BUFSIZE - 1 - udhcp_end_option(packet.data.options);
212
213         packet.ip.protocol = IPPROTO_UDP;
214         packet.ip.saddr = source_nip;
215         packet.ip.daddr = dest_nip;
216         packet.udp.source = htons(source_port);
217         packet.udp.dest = htons(dest_port);
218         /* size, excluding IP header: */
219         packet.udp.len = htons(UDP_DHCP_SIZE - padding);
220         /* for UDP checksumming, ip.len is set to UDP packet len */
221         packet.ip.tot_len = packet.udp.len;
222         packet.udp.check = udhcp_checksum(&packet, IP_UDP_DHCP_SIZE - padding);
223         /* but for sending, it is set to IP packet len */
224         packet.ip.tot_len = htons(IP_UDP_DHCP_SIZE - padding);
225         packet.ip.ihl = sizeof(packet.ip) >> 2;
226         packet.ip.version = IPVERSION;
227         packet.ip.ttl = IPDEFTTL;
228         packet.ip.check = udhcp_checksum(&packet.ip, sizeof(packet.ip));
229
230         udhcp_dump_packet(dhcp_pkt);
231         result = sendto(fd, &packet, IP_UDP_DHCP_SIZE - padding, /*flags:*/ 0,
232                         (struct sockaddr *) &dest_sll, sizeof(dest_sll));
233         msg = "sendto";
234  ret_close:
235         close(fd);
236         if (result < 0) {
237  ret_msg:
238                 bb_perror_msg(msg, "PACKET");
239         }
240         return result;
241 }
242
243 /* Let the kernel do all the work for packet generation */
244 int FAST_FUNC udhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt,
245                 uint32_t source_nip, int source_port,
246                 uint32_t dest_nip, int dest_port)
247 {
248         struct sockaddr_in client;
249         unsigned padding;
250         int fd;
251         int result = -1;
252         const char *msg;
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_nip;
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_nip;
274         if (connect(fd, (struct sockaddr *)&client, sizeof(client)) == -1) {
275                 msg = "connect";
276                 goto ret_close;
277         }
278
279         udhcp_dump_packet(dhcp_pkt);
280
281         padding = DHCP_OPTIONS_BUFSIZE - 1 - udhcp_end_option(dhcp_pkt->options);
282         result = safe_write(fd, dhcp_pkt, DHCP_SIZE - padding);
283         msg = "write";
284  ret_close:
285         close(fd);
286         if (result < 0) {
287  ret_msg:
288                 bb_perror_msg(msg, "UDP");
289         }
290         return result;
291 }