xref: /openbsd-src/usr.sbin/dhcrelay/packet.c (revision 22c60a6bb555fc9852c456dbd832cc83f94e6274)
1*22c60a6bSreyk /*	$OpenBSD: packet.c,v 1.14 2017/04/05 14:40:56 reyk Exp $	*/
248be18b4Shenning 
348be18b4Shenning /* Packet assembly code, originally contributed by Archie Cobbs. */
448be18b4Shenning 
548be18b4Shenning /*
648be18b4Shenning  * Copyright (c) 1995, 1996, 1999 The Internet Software Consortium.
748be18b4Shenning  * All rights reserved.
848be18b4Shenning  *
948be18b4Shenning  * Redistribution and use in source and binary forms, with or without
1048be18b4Shenning  * modification, are permitted provided that the following conditions
1148be18b4Shenning  * are met:
1248be18b4Shenning  *
1348be18b4Shenning  * 1. Redistributions of source code must retain the above copyright
1448be18b4Shenning  *    notice, this list of conditions and the following disclaimer.
1548be18b4Shenning  * 2. Redistributions in binary form must reproduce the above copyright
1648be18b4Shenning  *    notice, this list of conditions and the following disclaimer in the
1748be18b4Shenning  *    documentation and/or other materials provided with the distribution.
1848be18b4Shenning  * 3. Neither the name of The Internet Software Consortium nor the names
1948be18b4Shenning  *    of its contributors may be used to endorse or promote products derived
2048be18b4Shenning  *    from this software without specific prior written permission.
2148be18b4Shenning  *
2248be18b4Shenning  * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
2348be18b4Shenning  * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
2448be18b4Shenning  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
2548be18b4Shenning  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
2648be18b4Shenning  * DISCLAIMED.  IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
2748be18b4Shenning  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2848be18b4Shenning  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2948be18b4Shenning  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
3048be18b4Shenning  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
3148be18b4Shenning  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
3248be18b4Shenning  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
3348be18b4Shenning  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3448be18b4Shenning  * SUCH DAMAGE.
3548be18b4Shenning  *
3648be18b4Shenning  * This software has been written for the Internet Software Consortium
3748be18b4Shenning  * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
3848be18b4Shenning  * Enterprises.  To learn more about the Internet Software Consortium,
3948be18b4Shenning  * see ``http://www.vix.com/isc''.  To learn more about Vixie
4048be18b4Shenning  * Enterprises, see ``http://www.vix.com''.
4148be18b4Shenning  */
4248be18b4Shenning 
43f70ef60cSkrw #include <sys/types.h>
44f70ef60cSkrw #include <sys/socket.h>
4548be18b4Shenning 
46f70ef60cSkrw #include <arpa/inet.h>
47f70ef60cSkrw 
48f70ef60cSkrw #include <net/if.h>
494be048dcSreyk #include <net/if_enc.h>
50f70ef60cSkrw 
51f70ef60cSkrw #include <netinet/in.h>
5248be18b4Shenning #include <netinet/ip.h>
5348be18b4Shenning #include <netinet/udp.h>
5448be18b4Shenning #include <netinet/if_ether.h>
5548be18b4Shenning 
56f70ef60cSkrw #include <string.h>
57f70ef60cSkrw 
58f70ef60cSkrw #include "dhcp.h"
59f70ef60cSkrw #include "dhcpd.h"
60986dbb4cSkrw #include "log.h"
61f70ef60cSkrw 
62f70ef60cSkrw 
63*22c60a6bSreyk u_int32_t	checksum(unsigned char *, u_int32_t, u_int32_t);
6448be18b4Shenning u_int32_t	wrapsum(u_int32_t);
6548be18b4Shenning 
6648be18b4Shenning u_int32_t
checksum(unsigned char * buf,u_int32_t nbytes,u_int32_t sum)67*22c60a6bSreyk checksum(unsigned char *buf, u_int32_t nbytes, u_int32_t sum)
6848be18b4Shenning {
69*22c60a6bSreyk 	u_int32_t i;
7048be18b4Shenning 
7148be18b4Shenning 	/* Checksum all the pairs of bytes first... */
7248be18b4Shenning 	for (i = 0; i < (nbytes & ~1U); i += 2) {
7348be18b4Shenning 		sum += (u_int16_t)ntohs(*((u_int16_t *)(buf + i)));
7448be18b4Shenning 		if (sum > 0xFFFF)
7548be18b4Shenning 			sum -= 0xFFFF;
7648be18b4Shenning 	}
7748be18b4Shenning 
7848be18b4Shenning 	/*
7948be18b4Shenning 	 * If there's a single byte left over, checksum it, too.
8048be18b4Shenning 	 * Network byte order is big-endian, so the remaining byte is
8148be18b4Shenning 	 * the high byte.
8248be18b4Shenning 	 */
8348be18b4Shenning 	if (i < nbytes) {
8448be18b4Shenning 		sum += buf[i] << 8;
8548be18b4Shenning 		if (sum > 0xFFFF)
8648be18b4Shenning 			sum -= 0xFFFF;
8748be18b4Shenning 	}
8848be18b4Shenning 
8948be18b4Shenning 	return (sum);
9048be18b4Shenning }
9148be18b4Shenning 
9248be18b4Shenning u_int32_t
wrapsum(u_int32_t sum)9348be18b4Shenning wrapsum(u_int32_t sum)
9448be18b4Shenning {
9548be18b4Shenning 	sum = ~sum & 0xFFFF;
9648be18b4Shenning 	return (htons(sum));
9748be18b4Shenning }
9848be18b4Shenning 
99*22c60a6bSreyk ssize_t
assemble_hw_header(unsigned char * buf,size_t buflen,size_t offset,struct packet_ctx * pc,unsigned int intfhtype)100*22c60a6bSreyk assemble_hw_header(unsigned char *buf, size_t buflen,
101*22c60a6bSreyk     size_t offset, struct packet_ctx *pc, unsigned int intfhtype)
10248be18b4Shenning {
10348be18b4Shenning 	struct ether_header eh;
10448be18b4Shenning 
105*22c60a6bSreyk 	switch (intfhtype) {
106*22c60a6bSreyk 	case HTYPE_ETHER:
107*22c60a6bSreyk 		if (buflen < offset + ETHER_HDR_LEN)
108*22c60a6bSreyk 			return (-1);
109*22c60a6bSreyk 
110fa3d4f89Srzalamena 		/* Use the supplied address or let the kernel fill it. */
111fa3d4f89Srzalamena 		memcpy(eh.ether_shost, pc->pc_smac, ETHER_ADDR_LEN);
112fa3d4f89Srzalamena 		memcpy(eh.ether_dhost, pc->pc_dmac, ETHER_ADDR_LEN);
11348be18b4Shenning 
11448be18b4Shenning 		eh.ether_type = htons(ETHERTYPE_IP);
11548be18b4Shenning 
116*22c60a6bSreyk 		memcpy(&buf[offset], &eh, ETHER_HDR_LEN);
117*22c60a6bSreyk 		offset += ETHER_HDR_LEN;
118*22c60a6bSreyk 		break;
119*22c60a6bSreyk 	default:
120*22c60a6bSreyk 		return (-1);
12148be18b4Shenning 	}
12248be18b4Shenning 
123*22c60a6bSreyk 	return (offset);
124*22c60a6bSreyk }
125*22c60a6bSreyk 
126*22c60a6bSreyk ssize_t
assemble_udp_ip_header(unsigned char * buf,size_t buflen,size_t offset,struct packet_ctx * pc,unsigned char * data,size_t datalen)127*22c60a6bSreyk assemble_udp_ip_header(unsigned char *buf, size_t buflen, size_t offset,
128*22c60a6bSreyk     struct packet_ctx *pc, unsigned char *data, size_t datalen)
12948be18b4Shenning {
13048be18b4Shenning 	struct ip ip;
13148be18b4Shenning 	struct udphdr udp;
13248be18b4Shenning 
133*22c60a6bSreyk 	if (buflen < offset + sizeof(ip) + sizeof(udp))
134*22c60a6bSreyk 		return (-1);
135*22c60a6bSreyk 
13648be18b4Shenning 	ip.ip_v = 4;
13748be18b4Shenning 	ip.ip_hl = 5;
13848be18b4Shenning 	ip.ip_tos = IPTOS_LOWDELAY;
139*22c60a6bSreyk 	ip.ip_len = htons(sizeof(ip) + sizeof(udp) + datalen);
14048be18b4Shenning 	ip.ip_id = 0;
14148be18b4Shenning 	ip.ip_off = 0;
14248be18b4Shenning 	ip.ip_ttl = 16;
14348be18b4Shenning 	ip.ip_p = IPPROTO_UDP;
14448be18b4Shenning 	ip.ip_sum = 0;
145fa3d4f89Srzalamena 	ip.ip_src.s_addr = ss2sin(&pc->pc_src)->sin_addr.s_addr;
146fa3d4f89Srzalamena 	ip.ip_dst.s_addr = ss2sin(&pc->pc_dst)->sin_addr.s_addr;
14748be18b4Shenning 
14848be18b4Shenning 	ip.ip_sum = wrapsum(checksum((unsigned char *)&ip, sizeof(ip), 0));
149*22c60a6bSreyk 	memcpy(&buf[offset], &ip, sizeof(ip));
150*22c60a6bSreyk 	offset += sizeof(ip);
15148be18b4Shenning 
152fa3d4f89Srzalamena 	udp.uh_sport = ss2sin(&pc->pc_src)->sin_port;
153fa3d4f89Srzalamena 	udp.uh_dport = ss2sin(&pc->pc_dst)->sin_port;
154*22c60a6bSreyk 	udp.uh_ulen = htons(sizeof(udp) + datalen);
15548be18b4Shenning 	memset(&udp.uh_sum, 0, sizeof(udp.uh_sum));
15648be18b4Shenning 
15748be18b4Shenning 	udp.uh_sum = wrapsum(checksum((unsigned char *)&udp, sizeof(udp),
158*22c60a6bSreyk 	    checksum(data, datalen, checksum((unsigned char *)&ip.ip_src,
15948be18b4Shenning 	    2 * sizeof(ip.ip_src),
16048be18b4Shenning 	    IPPROTO_UDP + (u_int32_t)ntohs(udp.uh_ulen)))));
16148be18b4Shenning 
162*22c60a6bSreyk 	memcpy(&buf[offset], &udp, sizeof(udp));
163*22c60a6bSreyk 	offset += sizeof(udp);
164*22c60a6bSreyk 
165*22c60a6bSreyk 	return (offset);
16648be18b4Shenning }
16748be18b4Shenning 
16848be18b4Shenning ssize_t
decode_hw_header(unsigned char * buf,size_t buflen,size_t offset,struct packet_ctx * pc,unsigned int intfhtype)169*22c60a6bSreyk decode_hw_header(unsigned char *buf, size_t buflen,
170*22c60a6bSreyk     size_t offset, struct packet_ctx *pc, unsigned int intfhtype)
17148be18b4Shenning {
1724be048dcSreyk 	u_int32_t ip_len;
1734be048dcSreyk 	struct ip *ip;
1744be048dcSreyk 
175*22c60a6bSreyk 	switch (intfhtype) {
176*22c60a6bSreyk 	case HTYPE_IPSEC_TUNNEL:
177*22c60a6bSreyk 		if (buflen < offset + ENC_HDRLEN + sizeof(*ip))
178*22c60a6bSreyk 			return (-1);
179*22c60a6bSreyk 		offset += ENC_HDRLEN;
180*22c60a6bSreyk 		ip_len = (buf[offset] & 0xf) << 2;
181*22c60a6bSreyk 		if (buflen < offset + ip_len)
182*22c60a6bSreyk 			return (-1);
183*22c60a6bSreyk 
184*22c60a6bSreyk 		ip = (struct ip *)(buf + offset);
1854be048dcSreyk 
1864be048dcSreyk 		/* Encapsulated IP */
1874be048dcSreyk 		if (ip->ip_p != IPPROTO_IPIP)
1884be048dcSreyk 			return (-1);
1894be048dcSreyk 
190fa3d4f89Srzalamena 		memset(pc->pc_dmac, 0xff, ETHER_ADDR_LEN);
191*22c60a6bSreyk 		offset += ip_len;
19248be18b4Shenning 
193fa3d4f89Srzalamena 		pc->pc_htype = ARPHRD_ETHER;
194fa3d4f89Srzalamena 		pc->pc_hlen = ETHER_ADDR_LEN;
195*22c60a6bSreyk 		break;
196*22c60a6bSreyk 	case HTYPE_ETHER:
197*22c60a6bSreyk 		if (buflen < offset + ETHER_HDR_LEN)
198*22c60a6bSreyk 			return (-1);
199*22c60a6bSreyk 
200*22c60a6bSreyk 		memcpy(pc->pc_dmac, buf + offset, ETHER_ADDR_LEN);
201*22c60a6bSreyk 		memcpy(pc->pc_smac, buf + offset + ETHER_ADDR_LEN,
202*22c60a6bSreyk 		    ETHER_ADDR_LEN);
203*22c60a6bSreyk 		offset += ETHER_HDR_LEN;
204*22c60a6bSreyk 
205*22c60a6bSreyk 		pc->pc_htype = ARPHRD_ETHER;
206*22c60a6bSreyk 		pc->pc_hlen = ETHER_ADDR_LEN;
207*22c60a6bSreyk 		break;
208*22c60a6bSreyk 	default:
209*22c60a6bSreyk 		return (-1);
210*22c60a6bSreyk 	}
21148be18b4Shenning 
2124be048dcSreyk 	return (offset);
21348be18b4Shenning }
21448be18b4Shenning 
21548be18b4Shenning ssize_t
decode_udp_ip_header(unsigned char * buf,size_t buflen,size_t offset,struct packet_ctx * pc)216*22c60a6bSreyk decode_udp_ip_header(unsigned char *buf, size_t buflen,
217*22c60a6bSreyk     size_t offset, struct packet_ctx *pc)
21848be18b4Shenning {
21948be18b4Shenning 	struct ip *ip;
22048be18b4Shenning 	struct udphdr *udp;
22123d823c3Ssthen 	unsigned char *data;
222b4e2b639Skrw 	u_int32_t ip_len;
22348be18b4Shenning 	u_int32_t sum, usum;
2248ae29202Ssthen 	static unsigned int ip_packets_seen;
2258ae29202Ssthen 	static unsigned int ip_packets_bad_checksum;
2268ae29202Ssthen 	static unsigned int udp_packets_seen;
2278ae29202Ssthen 	static unsigned int udp_packets_bad_checksum;
2288ae29202Ssthen 	static unsigned int udp_packets_length_checked;
2298ae29202Ssthen 	static unsigned int udp_packets_length_overflow;
23023d823c3Ssthen 	int len;
23148be18b4Shenning 
232b4e2b639Skrw 	/* Assure that an entire IP header is within the buffer. */
233*22c60a6bSreyk 	if (buflen < offset + sizeof(*ip))
234b4e2b639Skrw 		return (-1);
235*22c60a6bSreyk 	ip_len = (buf[offset] & 0xf) << 2;
236*22c60a6bSreyk 	if (buflen < offset + ip_len)
237b4e2b639Skrw 		return (-1);
238*22c60a6bSreyk 
239*22c60a6bSreyk 	ip = (struct ip *)(buf + offset);
240b4e2b639Skrw 	ip_packets_seen++;
24148be18b4Shenning 
24248be18b4Shenning 	/* Check the IP header checksum - it should be zero. */
243*22c60a6bSreyk 	if (wrapsum(checksum(buf + offset, ip_len, 0)) != 0) {
24448be18b4Shenning 		ip_packets_bad_checksum++;
24564b20cadStobias 		if (ip_packets_seen > 4 && ip_packets_bad_checksum != 0 &&
24648be18b4Shenning 		    (ip_packets_seen / ip_packets_bad_checksum) < 2) {
247986dbb4cSkrw 			log_info("%u bad IP checksums seen in %u packets",
24848be18b4Shenning 			    ip_packets_bad_checksum, ip_packets_seen);
24948be18b4Shenning 			ip_packets_seen = ip_packets_bad_checksum = 0;
25048be18b4Shenning 		}
25148be18b4Shenning 		return (-1);
25248be18b4Shenning 	}
25348be18b4Shenning 
254fa3d4f89Srzalamena 	pc->pc_src.ss_len = sizeof(struct sockaddr_in);
255fa3d4f89Srzalamena 	pc->pc_src.ss_family = AF_INET;
256fa3d4f89Srzalamena 	memcpy(&ss2sin(&pc->pc_src)->sin_addr, &ip->ip_src,
257fa3d4f89Srzalamena 	    sizeof(ss2sin(&pc->pc_src)->sin_addr));
258fa3d4f89Srzalamena 
259fa3d4f89Srzalamena 	pc->pc_dst.ss_len = sizeof(struct sockaddr_in);
260fa3d4f89Srzalamena 	pc->pc_dst.ss_family = AF_INET;
261fa3d4f89Srzalamena 	memcpy(&ss2sin(&pc->pc_dst)->sin_addr, &ip->ip_dst,
262fa3d4f89Srzalamena 	    sizeof(ss2sin(&pc->pc_dst)->sin_addr));
263b4e2b639Skrw 
264b4e2b639Skrw #ifdef DEBUG
265*22c60a6bSreyk 	if (buflen != offset + ntohs(ip->ip_len))
266*22c60a6bSreyk 		log_debug("ip length %d disagrees with bytes received %zd.",
267*22c60a6bSreyk 		    ntohs(ip->ip_len), buflen - offset);
268b4e2b639Skrw #endif
26948be18b4Shenning 
270b4e2b639Skrw 	/* Assure that the entire IP packet is within the buffer. */
271*22c60a6bSreyk 	if (buflen < offset + ntohs(ip->ip_len))
272b4e2b639Skrw 		return (-1);
273b4e2b639Skrw 
274b4e2b639Skrw 	/* Assure that the UDP header is within the buffer. */
275*22c60a6bSreyk 	if (buflen < offset + ip_len + sizeof(*udp))
276b4e2b639Skrw 		return (-1);
277*22c60a6bSreyk 	udp = (struct udphdr *)(buf + offset + ip_len);
278b4e2b639Skrw 	udp_packets_seen++;
279b4e2b639Skrw 
280b4e2b639Skrw 	/* Assure that the entire UDP packet is within the buffer. */
281*22c60a6bSreyk 	if (buflen < offset + ip_len + ntohs(udp->uh_ulen))
282b4e2b639Skrw 		return (-1);
283*22c60a6bSreyk 	data = buf + offset + ip_len + sizeof(*udp);
28448be18b4Shenning 
28548be18b4Shenning 	/*
28648be18b4Shenning 	 * Compute UDP checksums, including the ``pseudo-header'', the
28748be18b4Shenning 	 * UDP header and the data. If the UDP checksum field is zero,
28848be18b4Shenning 	 * we're not supposed to do a checksum.
28948be18b4Shenning 	 */
29048be18b4Shenning 	udp_packets_length_checked++;
291b4e2b639Skrw 	len = ntohs(udp->uh_ulen) - sizeof(*udp);
292*22c60a6bSreyk 	if ((len < 0) || (len + data > buf + buflen)) {
29348be18b4Shenning 		udp_packets_length_overflow++;
29448be18b4Shenning 		if (udp_packets_length_checked > 4 &&
29564b20cadStobias 		    udp_packets_length_overflow != 0 &&
29648be18b4Shenning 		    (udp_packets_length_checked /
29748be18b4Shenning 		    udp_packets_length_overflow) < 2) {
298986dbb4cSkrw 			log_info("%u udp packets in %u too long - dropped",
29948be18b4Shenning 			    udp_packets_length_overflow,
30048be18b4Shenning 			    udp_packets_length_checked);
30148be18b4Shenning 			udp_packets_length_overflow =
30248be18b4Shenning 			    udp_packets_length_checked = 0;
30348be18b4Shenning 		}
30448be18b4Shenning 		return (-1);
30548be18b4Shenning 	}
306*22c60a6bSreyk 	if (len + data != buf + buflen)
307986dbb4cSkrw 		log_debug("accepting packet with data after udp payload.");
30848be18b4Shenning 
30948be18b4Shenning 	usum = udp->uh_sum;
31048be18b4Shenning 	udp->uh_sum = 0;
31148be18b4Shenning 
31248be18b4Shenning 	sum = wrapsum(checksum((unsigned char *)udp, sizeof(*udp),
31348be18b4Shenning 	    checksum(data, len, checksum((unsigned char *)&ip->ip_src,
31448be18b4Shenning 	    2 * sizeof(ip->ip_src),
31548be18b4Shenning 	    IPPROTO_UDP + (u_int32_t)ntohs(udp->uh_ulen)))));
31648be18b4Shenning 
31748be18b4Shenning 	udp_packets_seen++;
31848be18b4Shenning 	if (usum && usum != sum) {
31948be18b4Shenning 		udp_packets_bad_checksum++;
32064b20cadStobias 		if (udp_packets_seen > 4 && udp_packets_bad_checksum != 0 &&
32148be18b4Shenning 		    (udp_packets_seen / udp_packets_bad_checksum) < 2) {
322986dbb4cSkrw 			log_info("%u bad udp checksums in %u packets",
32348be18b4Shenning 			    udp_packets_bad_checksum, udp_packets_seen);
32448be18b4Shenning 			udp_packets_seen = udp_packets_bad_checksum = 0;
32548be18b4Shenning 		}
32648be18b4Shenning 		return (-1);
32748be18b4Shenning 	}
32848be18b4Shenning 
329fa3d4f89Srzalamena 	ss2sin(&pc->pc_src)->sin_port = udp->uh_sport;
330fa3d4f89Srzalamena 	ss2sin(&pc->pc_dst)->sin_port = udp->uh_dport;
33148be18b4Shenning 
332*22c60a6bSreyk 	return (offset + ip_len + sizeof(*udp));
33348be18b4Shenning }
334