reset mac addresses when relaying arp requests
[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 #include <sys/socket.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <errno.h>
22 #include <unistd.h>
23 #include <fcntl.h>
24
25 #include "relayd.h"
26
27 struct ip_packet {
28         struct ether_header eth;
29         struct iphdr iph;
30 } __packed;
31
32
33 enum {
34         DHCP_OPTION_ROUTER = 0x03,
35         DHCP_OPTION_ROUTES = 0x79,
36         DHCP_OPTION_END = 0xff,
37 };
38
39 struct dhcp_option {
40         uint8_t code;
41         uint8_t len;
42         uint8_t data[];
43 };
44
45 struct dhcp_header {
46         uint8_t op, htype, hlen, hops;
47         uint32_t xit;
48         uint16_t secs, flags;
49         struct in_addr ciaddr, yiaddr, siaddr, giaddr;
50         unsigned char chaddr[16];
51         unsigned char sname[64];
52         unsigned char file[128];
53         uint32_t cookie;
54         uint8_t option_data[];
55 } __packed;
56
57 static uint16_t
58 chksum(uint16_t sum, const uint8_t *data, uint16_t len)
59 {
60         const uint8_t *last;
61         uint16_t t;
62
63         last = data + len - 1;
64
65         while(data < last) {
66                 t = (data[0] << 8) + data[1];
67                 sum += t;
68                 if(sum < t)
69                         sum++;
70                 data += 2;
71         }
72
73         if(data == last) {
74                 t = (data[0] << 8) + 0;
75                 sum += t;
76                 if(sum < t)
77                         sum++;
78         }
79
80         return sum;
81 }
82
83 static void
84 parse_dhcp_options(struct relayd_host *host, struct dhcp_header *dhcp, int len)
85 {
86         uint8_t *end = (uint8_t *) dhcp + len;
87         struct dhcp_option *opt = (void *)dhcp->option_data;
88         static const uint8_t dest[4] = { 0, 0, 0, 0 };
89
90         while((uint8_t *) opt < end) {
91                 if ((uint8_t *) opt + opt->len > end)
92                         break;
93
94                 opt = (void *) &opt->data[opt->len];
95                 switch(opt->code) {
96                 case DHCP_OPTION_ROUTER:
97                         DPRINTF(2, "Found a DHCP router option, len=%d\n", opt->len);
98                         if (!memcmp(opt->data, host->ipaddr, 4))
99                                 relayd_add_host_route(host, dest, 0);
100                         else
101                                 relayd_add_pending_route(opt->data, dest, 0, 10000);
102                         break;
103                 case DHCP_OPTION_ROUTES:
104                         DPRINTF(2, "Found a DHCP static routes option, len=%d\n", opt->len);
105                         break;
106                 case DHCP_OPTION_END:
107                         opt = (void *) end;
108                         continue;
109                 default:
110                         DPRINTF(3, "Skipping unknown DHCP option %02x\n", opt->code);
111                         continue;
112                 }
113
114         }
115 }
116
117 bool relayd_handle_dhcp_packet(struct relayd_interface *rif, void *data, int len, bool forward)
118 {
119         struct ip_packet *pkt = data;
120         struct udphdr *udp;
121         struct dhcp_header *dhcp;
122         struct relayd_host *host;
123         int udplen;
124         uint16_t sum;
125
126         if (pkt->eth.ether_type != htons(ETH_P_IP))
127                 return false;
128
129         if (pkt->iph.version != 4)
130                 return false;
131
132         if (pkt->iph.protocol != IPPROTO_UDP)
133                 return false;
134
135         udp = (void *) ((char *) &pkt->iph + (pkt->iph.ihl << 2));
136         dhcp = (void *) (udp + 1);
137
138         udplen = ntohs(udp->len);
139         if (udplen > len - ((char *) udp - (char *) data))
140                 return false;
141
142         if (udp->dest != htons(67) && udp->source != htons(67))
143                 return false;
144
145         if (dhcp->op != 1 && dhcp->op != 2)
146                 return false;
147
148         if (!forward)
149                 return true;
150
151         if (dhcp->op == 2) {
152                 host = relayd_refresh_host(rif, pkt->eth.ether_shost, (void *) &pkt->iph.saddr);
153                 if (host)
154                         parse_dhcp_options(host, dhcp, udplen - sizeof(struct udphdr));
155         }
156
157         DPRINTF(2, "%s: handling DHCP %s\n", rif->ifname, (dhcp->op == 1 ? "request" : "response"));
158
159         dhcp->flags |= htons(DHCP_FLAG_BROADCAST);
160
161         udp->check = 0;
162         sum = udplen + IPPROTO_UDP;
163         sum = chksum(sum, (void *) &pkt->iph.saddr, 8);
164         sum = chksum(sum, (void *) udp, udplen);
165         if (sum == 0)
166                 sum = 0xffff;
167
168         udp->check = htons(~sum);
169
170         relayd_forward_bcast_packet(rif, data, len);
171
172         return true;
173 }
174
175