a144346dd2ab8422f981bccb1c5e697407eddf34
[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 struct dhcp_header {
33         uint8_t op, htype, hlen, hops;
34         uint32_t xit;
35         uint16_t secs, flags;
36         struct in_addr ciaddr, yiaddr, siaddr, giaddr;
37         unsigned char chaddr[16];
38         unsigned char sname[64];
39         unsigned char file[128];
40 } __packed;
41
42 static uint16_t
43 chksum(uint16_t sum, const uint8_t *data, uint16_t len)
44 {
45         const uint8_t *last;
46         uint16_t t;
47
48         last = data + len - 1;
49
50         while(data < last) {
51                 t = (data[0] << 8) + data[1];
52                 sum += t;
53                 if(sum < t)
54                         sum++;
55                 data += 2;
56         }
57
58         if(data == last) {
59                 t = (data[0] << 8) + 0;
60                 sum += t;
61                 if(sum < t)
62                         sum++;
63         }
64
65         return sum;
66 }
67
68 bool relayd_handle_dhcp_packet(struct relayd_interface *rif, void *data, int len, bool forward)
69 {
70         struct ip_packet *pkt = data;
71         struct udphdr *udp;
72         struct dhcp_header *dhcp;
73         int udplen;
74         uint16_t sum;
75
76         if (pkt->eth.ether_type != htons(ETH_P_IP))
77                 return false;
78
79         if (pkt->iph.version != 4)
80                 return false;
81
82         if (pkt->iph.protocol != IPPROTO_UDP)
83                 return false;
84
85         udp = (void *) ((char *) &pkt->iph + (pkt->iph.ihl << 2));
86         dhcp = (void *) (udp + 1);
87
88         udplen = ntohs(udp->len);
89         if (udplen > len - ((char *) udp - (char *) data))
90                 return false;
91
92         if (udp->dest != htons(67) && udp->source != htons(67))
93                 return false;
94
95         if (dhcp->op != 1 && dhcp->op != 2)
96                 return false;
97
98         if (!forward)
99                 return true;
100
101         if (dhcp->op == 2)
102                 relayd_refresh_host(rif, pkt->eth.ether_shost, (void *) &pkt->iph.saddr);
103
104         DPRINTF(2, "%s: handling DHCP %s\n", rif->ifname, (dhcp->op == 1 ? "request" : "response"));
105
106         dhcp->flags |= htons(DHCP_FLAG_BROADCAST);
107
108         udp->check = 0;
109         sum = udplen + IPPROTO_UDP;
110         sum = chksum(sum, (void *) &pkt->iph.saddr, 8);
111         sum = chksum(sum, (void *) udp, udplen);
112         if (sum == 0)
113                 sum = 0xffff;
114
115         udp->check = htons(~sum);
116
117         relayd_forward_bcast_packet(rif, data, len);
118
119         return true;
120 }
121
122