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(ðer_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