xref: /openbsd-src/usr.sbin/vmd/packet.c (revision 6eb4c859e9b6a4a1acf9b5c55557502f5d149733)
1*6eb4c859Sdv /*	$OpenBSD: packet.c,v 1.4 2021/06/16 16:55:02 dv Exp $	*/
2470adcf5Sreyk 
3470adcf5Sreyk /* Packet assembly code, originally contributed by Archie Cobbs. */
4470adcf5Sreyk 
5470adcf5Sreyk /*
6470adcf5Sreyk  * Copyright (c) 1995, 1996, 1999 The Internet Software Consortium.
7470adcf5Sreyk  * All rights reserved.
8470adcf5Sreyk  *
9470adcf5Sreyk  * Redistribution and use in source and binary forms, with or without
10470adcf5Sreyk  * modification, are permitted provided that the following conditions
11470adcf5Sreyk  * are met:
12470adcf5Sreyk  *
13470adcf5Sreyk  * 1. Redistributions of source code must retain the above copyright
14470adcf5Sreyk  *    notice, this list of conditions and the following disclaimer.
15470adcf5Sreyk  * 2. Redistributions in binary form must reproduce the above copyright
16470adcf5Sreyk  *    notice, this list of conditions and the following disclaimer in the
17470adcf5Sreyk  *    documentation and/or other materials provided with the distribution.
18470adcf5Sreyk  * 3. Neither the name of The Internet Software Consortium nor the names
19470adcf5Sreyk  *    of its contributors may be used to endorse or promote products derived
20470adcf5Sreyk  *    from this software without specific prior written permission.
21470adcf5Sreyk  *
22470adcf5Sreyk  * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
23470adcf5Sreyk  * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
24470adcf5Sreyk  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
25470adcf5Sreyk  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26470adcf5Sreyk  * DISCLAIMED.  IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
27470adcf5Sreyk  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28470adcf5Sreyk  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29470adcf5Sreyk  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
30470adcf5Sreyk  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
31470adcf5Sreyk  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32470adcf5Sreyk  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
33470adcf5Sreyk  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34470adcf5Sreyk  * SUCH DAMAGE.
35470adcf5Sreyk  *
36470adcf5Sreyk  * This software has been written for the Internet Software Consortium
37470adcf5Sreyk  * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
38470adcf5Sreyk  * Enterprises.  To learn more about the Internet Software Consortium,
39470adcf5Sreyk  * see ``http://www.vix.com/isc''.  To learn more about Vixie
40470adcf5Sreyk  * Enterprises, see ``http://www.vix.com''.
41470adcf5Sreyk  */
42470adcf5Sreyk 
43470adcf5Sreyk #include <sys/types.h>
44470adcf5Sreyk #include <sys/socket.h>
45470adcf5Sreyk 
46470adcf5Sreyk #include <arpa/inet.h>
47470adcf5Sreyk 
48470adcf5Sreyk #include <net/if.h>
49470adcf5Sreyk #include <net/if_enc.h>
50470adcf5Sreyk 
51470adcf5Sreyk #include <netinet/in.h>
52470adcf5Sreyk #include <netinet/ip.h>
53470adcf5Sreyk #include <netinet/udp.h>
54470adcf5Sreyk #include <netinet/if_ether.h>
55470adcf5Sreyk 
56470adcf5Sreyk #include <string.h>
57470adcf5Sreyk 
58470adcf5Sreyk #include "dhcp.h"
59470adcf5Sreyk #include "vmd.h"
60470adcf5Sreyk 
61470adcf5Sreyk u_int32_t	checksum(unsigned char *, u_int32_t, u_int32_t);
62470adcf5Sreyk u_int32_t	wrapsum(u_int32_t);
63470adcf5Sreyk 
64470adcf5Sreyk u_int32_t
checksum(unsigned char * buf,u_int32_t nbytes,u_int32_t sum)65470adcf5Sreyk checksum(unsigned char *buf, u_int32_t nbytes, u_int32_t sum)
66470adcf5Sreyk {
67470adcf5Sreyk 	u_int32_t i;
68470adcf5Sreyk 
69470adcf5Sreyk 	/* Checksum all the pairs of bytes first... */
70470adcf5Sreyk 	for (i = 0; i < (nbytes & ~1U); i += 2) {
71470adcf5Sreyk 		sum += (u_int16_t)ntohs(*((u_int16_t *)(buf + i)));
72470adcf5Sreyk 		if (sum > 0xFFFF)
73470adcf5Sreyk 			sum -= 0xFFFF;
74470adcf5Sreyk 	}
75470adcf5Sreyk 
76470adcf5Sreyk 	/*
77470adcf5Sreyk 	 * If there's a single byte left over, checksum it, too.
78470adcf5Sreyk 	 * Network byte order is big-endian, so the remaining byte is
79470adcf5Sreyk 	 * the high byte.
80470adcf5Sreyk 	 */
81470adcf5Sreyk 	if (i < nbytes) {
82470adcf5Sreyk 		sum += buf[i] << 8;
83470adcf5Sreyk 		if (sum > 0xFFFF)
84470adcf5Sreyk 			sum -= 0xFFFF;
85470adcf5Sreyk 	}
86470adcf5Sreyk 
87470adcf5Sreyk 	return (sum);
88470adcf5Sreyk }
89470adcf5Sreyk 
90470adcf5Sreyk u_int32_t
wrapsum(u_int32_t sum)91470adcf5Sreyk wrapsum(u_int32_t sum)
92470adcf5Sreyk {
93470adcf5Sreyk 	sum = ~sum & 0xFFFF;
94470adcf5Sreyk 	return (htons(sum));
95470adcf5Sreyk }
96470adcf5Sreyk 
97470adcf5Sreyk ssize_t
assemble_hw_header(unsigned char * buf,size_t buflen,size_t offset,struct packet_ctx * pc,unsigned int intfhtype)98470adcf5Sreyk assemble_hw_header(unsigned char *buf, size_t buflen,
99470adcf5Sreyk     size_t offset, struct packet_ctx *pc, unsigned int intfhtype)
100470adcf5Sreyk {
101470adcf5Sreyk 	struct ether_header eh;
102470adcf5Sreyk 
103470adcf5Sreyk 	switch (intfhtype) {
104470adcf5Sreyk 	case HTYPE_ETHER:
105470adcf5Sreyk 		if (buflen < offset + ETHER_HDR_LEN)
106470adcf5Sreyk 			return (-1);
107470adcf5Sreyk 
108470adcf5Sreyk 		/* Use the supplied address or let the kernel fill it. */
109470adcf5Sreyk 		memcpy(eh.ether_shost, pc->pc_smac, ETHER_ADDR_LEN);
110470adcf5Sreyk 		memcpy(eh.ether_dhost, pc->pc_dmac, ETHER_ADDR_LEN);
111470adcf5Sreyk 
112470adcf5Sreyk 		eh.ether_type = htons(ETHERTYPE_IP);
113470adcf5Sreyk 
114470adcf5Sreyk 		memcpy(&buf[offset], &eh, ETHER_HDR_LEN);
115470adcf5Sreyk 		offset += ETHER_HDR_LEN;
116470adcf5Sreyk 		break;
117470adcf5Sreyk 	default:
118470adcf5Sreyk 		return (-1);
119470adcf5Sreyk 	}
120470adcf5Sreyk 
121470adcf5Sreyk 	return (offset);
122470adcf5Sreyk }
123470adcf5Sreyk 
124470adcf5Sreyk 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)125470adcf5Sreyk assemble_udp_ip_header(unsigned char *buf, size_t buflen, size_t offset,
126470adcf5Sreyk     struct packet_ctx *pc, unsigned char *data, size_t datalen)
127470adcf5Sreyk {
128470adcf5Sreyk 	struct ip ip;
129470adcf5Sreyk 	struct udphdr udp;
130470adcf5Sreyk 
131470adcf5Sreyk 	if (buflen < offset + sizeof(ip) + sizeof(udp))
132470adcf5Sreyk 		return (-1);
133470adcf5Sreyk 
134470adcf5Sreyk 	ip.ip_v = 4;
135470adcf5Sreyk 	ip.ip_hl = 5;
136470adcf5Sreyk 	ip.ip_tos = IPTOS_LOWDELAY;
137470adcf5Sreyk 	ip.ip_len = htons(sizeof(ip) + sizeof(udp) + datalen);
138470adcf5Sreyk 	ip.ip_id = 0;
139470adcf5Sreyk 	ip.ip_off = 0;
140470adcf5Sreyk 	ip.ip_ttl = 16;
141470adcf5Sreyk 	ip.ip_p = IPPROTO_UDP;
142470adcf5Sreyk 	ip.ip_sum = 0;
143470adcf5Sreyk 	ip.ip_src.s_addr = ss2sin(&pc->pc_src)->sin_addr.s_addr;
144470adcf5Sreyk 	ip.ip_dst.s_addr = ss2sin(&pc->pc_dst)->sin_addr.s_addr;
145470adcf5Sreyk 
146470adcf5Sreyk 	ip.ip_sum = wrapsum(checksum((unsigned char *)&ip, sizeof(ip), 0));
147470adcf5Sreyk 	memcpy(&buf[offset], &ip, sizeof(ip));
148470adcf5Sreyk 	offset += sizeof(ip);
149470adcf5Sreyk 
150470adcf5Sreyk 	udp.uh_sport = ss2sin(&pc->pc_src)->sin_port;
151470adcf5Sreyk 	udp.uh_dport = ss2sin(&pc->pc_dst)->sin_port;
152470adcf5Sreyk 	udp.uh_ulen = htons(sizeof(udp) + datalen);
153470adcf5Sreyk 	memset(&udp.uh_sum, 0, sizeof(udp.uh_sum));
154470adcf5Sreyk 
155470adcf5Sreyk 	udp.uh_sum = wrapsum(checksum((unsigned char *)&udp, sizeof(udp),
156470adcf5Sreyk 	    checksum(data, datalen, checksum((unsigned char *)&ip.ip_src,
157470adcf5Sreyk 	    2 * sizeof(ip.ip_src),
158470adcf5Sreyk 	    IPPROTO_UDP + (u_int32_t)ntohs(udp.uh_ulen)))));
159470adcf5Sreyk 
160470adcf5Sreyk 	memcpy(&buf[offset], &udp, sizeof(udp));
161470adcf5Sreyk 	offset += sizeof(udp);
162470adcf5Sreyk 
163470adcf5Sreyk 	return (offset);
164470adcf5Sreyk }
165470adcf5Sreyk 
166470adcf5Sreyk ssize_t
decode_hw_header(unsigned char * buf,size_t buflen,size_t offset,struct packet_ctx * pc,unsigned int intfhtype)167470adcf5Sreyk decode_hw_header(unsigned char *buf, size_t buflen,
168470adcf5Sreyk     size_t offset, struct packet_ctx *pc, unsigned int intfhtype)
169470adcf5Sreyk {
170470adcf5Sreyk 	u_int32_t ip_len;
17186bbdb74Sclaudio 	u_int16_t ether_type;
17286bbdb74Sclaudio 	struct ether_header *eh;
173470adcf5Sreyk 	struct ip *ip;
174470adcf5Sreyk 
175470adcf5Sreyk 	switch (intfhtype) {
176470adcf5Sreyk 	case HTYPE_IPSEC_TUNNEL:
177470adcf5Sreyk 		if (buflen < offset + ENC_HDRLEN + sizeof(*ip))
178470adcf5Sreyk 			return (-1);
179470adcf5Sreyk 		offset += ENC_HDRLEN;
180470adcf5Sreyk 		ip_len = (buf[offset] & 0xf) << 2;
181470adcf5Sreyk 		if (buflen < offset + ip_len)
182470adcf5Sreyk 			return (-1);
183470adcf5Sreyk 
184470adcf5Sreyk 		ip = (struct ip *)(buf + offset);
185470adcf5Sreyk 
186470adcf5Sreyk 		/* Encapsulated IP */
187470adcf5Sreyk 		if (ip->ip_p != IPPROTO_IPIP)
188470adcf5Sreyk 			return (-1);
189470adcf5Sreyk 
190470adcf5Sreyk 		memset(pc->pc_dmac, 0xff, ETHER_ADDR_LEN);
191470adcf5Sreyk 		offset += ip_len;
192470adcf5Sreyk 
193470adcf5Sreyk 		pc->pc_htype = ARPHRD_ETHER;
194470adcf5Sreyk 		pc->pc_hlen = ETHER_ADDR_LEN;
195470adcf5Sreyk 		break;
196470adcf5Sreyk 	case HTYPE_ETHER:
197470adcf5Sreyk 		if (buflen < offset + ETHER_HDR_LEN)
198470adcf5Sreyk 			return (-1);
199470adcf5Sreyk 
20086bbdb74Sclaudio 		eh = (struct ether_header *)(buf + offset);
20186bbdb74Sclaudio 		memcpy(pc->pc_dmac, eh->ether_dhost, ETHER_ADDR_LEN);
20286bbdb74Sclaudio 		memcpy(pc->pc_smac, eh->ether_shost, ETHER_ADDR_LEN);
20386bbdb74Sclaudio 		memcpy(&ether_type, &eh->ether_type, sizeof(ether_type));
20486bbdb74Sclaudio 
20586bbdb74Sclaudio 		if (ether_type != htons(ETHERTYPE_IP))
20686bbdb74Sclaudio 			return (-1);
20786bbdb74Sclaudio 
208470adcf5Sreyk 		offset += ETHER_HDR_LEN;
209470adcf5Sreyk 
210470adcf5Sreyk 		pc->pc_htype = ARPHRD_ETHER;
211470adcf5Sreyk 		pc->pc_hlen = ETHER_ADDR_LEN;
212470adcf5Sreyk 		break;
213470adcf5Sreyk 	default:
214470adcf5Sreyk 		return (-1);
215470adcf5Sreyk 	}
216470adcf5Sreyk 
217470adcf5Sreyk 	return (offset);
218470adcf5Sreyk }
219470adcf5Sreyk 
220470adcf5Sreyk ssize_t
decode_udp_ip_header(unsigned char * buf,size_t buflen,size_t offset,struct packet_ctx * pc)221470adcf5Sreyk decode_udp_ip_header(unsigned char *buf, size_t buflen,
222470adcf5Sreyk     size_t offset, struct packet_ctx *pc)
223470adcf5Sreyk {
224470adcf5Sreyk 	struct ip *ip;
225470adcf5Sreyk 	struct udphdr *udp;
226470adcf5Sreyk 	unsigned char *data;
227470adcf5Sreyk 	u_int32_t ip_len;
228470adcf5Sreyk 	u_int32_t sum, usum;
229470adcf5Sreyk 	int len;
230470adcf5Sreyk 
231470adcf5Sreyk 	/* Assure that an entire IP header is within the buffer. */
232470adcf5Sreyk 	if (buflen < offset + sizeof(*ip))
233470adcf5Sreyk 		return (-1);
23486bbdb74Sclaudio 	ip = (struct ip *)(buf + offset);
23586bbdb74Sclaudio 	if (ip->ip_v != IPVERSION)
23686bbdb74Sclaudio 		return (-1);
23786bbdb74Sclaudio 	ip_len = ip->ip_hl << 2;
23886bbdb74Sclaudio 	if (ip_len < sizeof(struct ip) ||
23986bbdb74Sclaudio 	    buflen < offset + ip_len)
240470adcf5Sreyk 		return (-1);
241470adcf5Sreyk 
24229e58e7fSdv 	if (ip->ip_p != IPPROTO_UDP)
24329e58e7fSdv 		return (-1);
244470adcf5Sreyk 
245470adcf5Sreyk 	/* Check the IP header checksum - it should be zero. */
24686bbdb74Sclaudio 	if (wrapsum(checksum(buf + offset, ip_len, 0)) != 0)
247470adcf5Sreyk 		return (-1);
248470adcf5Sreyk 
249470adcf5Sreyk 	pc->pc_src.ss_len = sizeof(struct sockaddr_in);
250470adcf5Sreyk 	pc->pc_src.ss_family = AF_INET;
251470adcf5Sreyk 	memcpy(&ss2sin(&pc->pc_src)->sin_addr, &ip->ip_src,
252470adcf5Sreyk 	    sizeof(ss2sin(&pc->pc_src)->sin_addr));
253470adcf5Sreyk 
254470adcf5Sreyk 	pc->pc_dst.ss_len = sizeof(struct sockaddr_in);
255470adcf5Sreyk 	pc->pc_dst.ss_family = AF_INET;
256470adcf5Sreyk 	memcpy(&ss2sin(&pc->pc_dst)->sin_addr, &ip->ip_dst,
257470adcf5Sreyk 	    sizeof(ss2sin(&pc->pc_dst)->sin_addr));
258470adcf5Sreyk 
259470adcf5Sreyk #ifdef DEBUG
260470adcf5Sreyk 	if (buflen != offset + ntohs(ip->ip_len))
261470adcf5Sreyk 		log_debug("ip length %d disagrees with bytes received %zd.",
262470adcf5Sreyk 		    ntohs(ip->ip_len), buflen - offset);
263470adcf5Sreyk #endif
264470adcf5Sreyk 
265470adcf5Sreyk 	/* Assure that the entire IP packet is within the buffer. */
266470adcf5Sreyk 	if (buflen < offset + ntohs(ip->ip_len))
267470adcf5Sreyk 		return (-1);
268470adcf5Sreyk 
269470adcf5Sreyk 	/* Assure that the UDP header is within the buffer. */
270470adcf5Sreyk 	if (buflen < offset + ip_len + sizeof(*udp))
271470adcf5Sreyk 		return (-1);
272470adcf5Sreyk 	udp = (struct udphdr *)(buf + offset + ip_len);
273470adcf5Sreyk 
274470adcf5Sreyk 	/* Assure that the entire UDP packet is within the buffer. */
275470adcf5Sreyk 	if (buflen < offset + ip_len + ntohs(udp->uh_ulen))
276470adcf5Sreyk 		return (-1);
277470adcf5Sreyk 	data = buf + offset + ip_len + sizeof(*udp);
278470adcf5Sreyk 
279470adcf5Sreyk 	/*
280470adcf5Sreyk 	 * Compute UDP checksums, including the ``pseudo-header'', the
281470adcf5Sreyk 	 * UDP header and the data. If the UDP checksum field is zero,
282470adcf5Sreyk 	 * we're not supposed to do a checksum.
283470adcf5Sreyk 	 */
284470adcf5Sreyk 	len = ntohs(udp->uh_ulen) - sizeof(*udp);
285470adcf5Sreyk 	if ((len < 0) || (len + data > buf + buflen)) {
286470adcf5Sreyk 		return (-1);
287470adcf5Sreyk 	}
288470adcf5Sreyk 	if (len + data != buf + buflen)
289470adcf5Sreyk 		log_debug("accepting packet with data after udp payload.");
290470adcf5Sreyk 
291470adcf5Sreyk 	usum = udp->uh_sum;
292470adcf5Sreyk 	udp->uh_sum = 0;
293470adcf5Sreyk 
294470adcf5Sreyk 	sum = wrapsum(checksum((unsigned char *)udp, sizeof(*udp),
295470adcf5Sreyk 	    checksum(data, len, checksum((unsigned char *)&ip->ip_src,
296470adcf5Sreyk 	    2 * sizeof(ip->ip_src),
297470adcf5Sreyk 	    IPPROTO_UDP + (u_int32_t)ntohs(udp->uh_ulen)))));
298470adcf5Sreyk 
29986bbdb74Sclaudio 	if (usum && usum != sum)
300470adcf5Sreyk 		return (-1);
301470adcf5Sreyk 
302470adcf5Sreyk 	ss2sin(&pc->pc_src)->sin_port = udp->uh_sport;
303470adcf5Sreyk 	ss2sin(&pc->pc_dst)->sin_port = udp->uh_dport;
304470adcf5Sreyk 
305470adcf5Sreyk 	return (offset + ip_len + sizeof(*udp));
306470adcf5Sreyk }
307