route: Fix compile warning with glibc
[project/relayd.git] / dhcp.c
1 /*
2 * Copyright (C) 2010 Felix Fietkau <nbd@openwrt.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License v2 as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
16 */
17
18 #define _GNU_SOURCE
19 #include <sys/socket.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <errno.h>
23 #include <unistd.h>
24 #include <fcntl.h>
25
26 #include "relayd.h"
27
28 struct ip_packet {
29 struct ether_header eth;
30 struct iphdr iph;
31 } __packed;
32
33
34 enum {
35 DHCP_OPTION_ROUTER = 0x03,
36 DHCP_OPTION_ROUTES = 0x79,
37 DHCP_OPTION_END = 0xff,
38 };
39
40 struct dhcp_option {
41 uint8_t code;
42 uint8_t len;
43 uint8_t data[];
44 };
45
46 struct dhcp_header {
47 uint8_t op, htype, hlen, hops;
48 uint32_t xit;
49 uint16_t secs, flags;
50 struct in_addr ciaddr, yiaddr, siaddr, giaddr;
51 unsigned char chaddr[16];
52 unsigned char sname[64];
53 unsigned char file[128];
54 uint32_t cookie;
55 uint8_t option_data[];
56 } __packed;
57
58 static uint16_t
59 chksum(uint16_t sum, const uint8_t *data, uint16_t len)
60 {
61 const uint8_t *last;
62 uint16_t t;
63
64 last = data + len - 1;
65
66 while(data < last) {
67 t = (data[0] << 8) + data[1];
68 sum += t;
69 if(sum < t)
70 sum++;
71 data += 2;
72 }
73
74 if(data == last) {
75 t = (data[0] << 8) + 0;
76 sum += t;
77 if(sum < t)
78 sum++;
79 }
80
81 return sum;
82 }
83
84 static void
85 parse_dhcp_options(struct relayd_host *host, struct dhcp_header *dhcp, int len)
86 {
87 uint8_t *end = (uint8_t *) dhcp + len;
88 struct dhcp_option *opt = (void *)dhcp->option_data;
89 static const uint8_t dest[4] = { 0, 0, 0, 0 };
90
91 while((uint8_t *) opt + sizeof(*opt) < end) {
92 if ((uint8_t *) opt + opt->len > end ||
93 (uint8_t *) opt + sizeof(*opt) > end )
94 break;
95
96 opt = (void *) &opt->data[opt->len];
97 if ((uint8_t *) opt + sizeof(*opt) > end )
98 break;
99 switch(opt->code) {
100 case DHCP_OPTION_ROUTER:
101 DPRINTF(2, "Found a DHCP router option, len=%d\n", opt->len);
102 if (!memcmp(opt->data, host->ipaddr, 4))
103 relayd_add_host_route(host, dest, 0);
104 else
105 relayd_add_pending_route(opt->data, dest, 0, 10000);
106 break;
107 case DHCP_OPTION_ROUTES:
108 DPRINTF(2, "Found a DHCP static routes option, len=%d\n", opt->len);
109 break;
110 case DHCP_OPTION_END:
111 opt = (void *) end;
112 continue;
113 default:
114 DPRINTF(3, "Skipping unknown DHCP option %02x\n", opt->code);
115 continue;
116 }
117
118 }
119 }
120
121 bool relayd_handle_dhcp_packet(struct relayd_interface *rif, void *data, int len, bool forward, bool parse)
122 {
123 struct ip_packet *pkt = data;
124 struct udphdr *udp;
125 struct dhcp_header *dhcp;
126 struct relayd_host *host;
127 int udplen;
128 uint16_t sum;
129
130 if (pkt->eth.ether_type != htons(ETH_P_IP))
131 return false;
132
133 if (pkt->iph.version != 4)
134 return false;
135
136 if (pkt->iph.protocol != IPPROTO_UDP)
137 return false;
138
139 udp = (void *) ((char *) &pkt->iph + (pkt->iph.ihl << 2));
140 dhcp = (void *) (udp + 1);
141
142 if ((uint8_t *)udp + sizeof(*udp) > (uint8_t *)data + len ||
143 (uint8_t *)dhcp + sizeof(*dhcp) > (uint8_t *)data + len)
144 return false;
145
146 udplen = ntohs(udp->len);
147 if (udplen > len - ((char *) udp - (char *) data))
148 return false;
149
150 if (udp->dest != htons(67) && udp->source != htons(67))
151 return false;
152
153 if (dhcp->op != 1 && dhcp->op != 2)
154 return false;
155
156 if (!forward)
157 return true;
158
159 if (dhcp->op == 2) {
160 host = relayd_refresh_host(rif, pkt->eth.ether_shost, (void *) &pkt->iph.saddr);
161 if (host && parse)
162 parse_dhcp_options(host, dhcp, udplen - sizeof(struct udphdr));
163 }
164
165 DPRINTF(2, "%s: handling DHCP %s\n", rif->ifname, (dhcp->op == 1 ? "request" : "response"));
166
167 dhcp->flags |= htons(DHCP_FLAG_BROADCAST);
168
169 udp->check = 0;
170 sum = udplen + IPPROTO_UDP;
171 sum = chksum(sum, (void *) &pkt->iph.saddr, 8);
172 sum = chksum(sum, (void *) udp, udplen);
173 if (sum == 0)
174 sum = 0xffff;
175
176 udp->check = htons(~sum);
177
178 relayd_forward_bcast_packet(rif, data, len);
179
180 return true;
181 }
182
183