xref: /src/tests/sys/net/bpf/pcap-test.c (revision d4083fb836cf5d06a2bc2d6fe7297fb5139c8fc6)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2025 Gleb Smirnoff <glebius@FreeBSD.org>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  *
27  */
28 
29 #include <sys/param.h>
30 #include <sys/queue.h>
31 #include <netinet/ip.h>
32 #include <pcap/pcap.h>
33 #include <fcntl.h>
34 #include <libutil.h>
35 #include <unistd.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <strings.h>
39 #include <err.h>
40 
41 static int
strtolerr(const char * s)42 strtolerr(const char *s)
43 {
44 	int rv;
45 
46 	if ((rv = (int)strtol(s, NULL, 10)) < 1)
47 		errx(1, "bad count %s", s);
48 	return (rv);
49 }
50 
51 static pcap_direction_t
strtodir(const char * s)52 strtodir(const char *s)
53 {
54 	static const struct dirstr {
55 		const char *str;
56 		pcap_direction_t dir;
57 	} dirs[] = {
58 		{ "in", PCAP_D_IN },
59 		{ "out", PCAP_D_OUT },
60 		{ "both", PCAP_D_INOUT },
61 		{ "inout", PCAP_D_INOUT },
62 	};
63 
64 	for (u_int i = 0; i < nitems(dirs); i++)
65 		if (strcasecmp(s, dirs[i].str) == 0)
66 			return (dirs[i].dir);
67 	errx(1, "bad directions %s", s);
68 }
69 
70 static char errbuf[PCAP_ERRBUF_SIZE];
71 
72 static pcap_t *
pcap_open(const char * name,pcap_direction_t dir)73 pcap_open(const char *name, pcap_direction_t dir)
74 {
75 	pcap_t *p;
76 
77 	if ((p = pcap_create(name, errbuf)) == NULL)
78 		errx(1, "pcap_create: %s", errbuf);
79 	if (pcap_set_timeout(p, 10) != 0)
80 		errx(1, "pcap_set_timeout: %s", pcap_geterr(p));
81 	if (pcap_activate(p) != 0)
82 		errx(1, "pcap_activate: %s", errbuf);
83 	if (pcap_setdirection(p, dir) != 0)
84 		errx(1, "pcap_setdirection: %s", pcap_geterr(p));
85 	return (p);
86 }
87 
88 #if 0
89 /*
90  * Deal with the FreeBSD writer only optimization hack in bpf(4).
91  * Needed only when net.bpf.optimize_writers=1.
92  */
93 static pcap_t *
94 pcap_rwopen(const char *name, pcap_direction_t dir)
95 {
96 	pcap_t *p;
97 	struct bpf_program fp;
98 
99 	p = pcap_open(name, dir);
100 	if (pcap_compile(p, &fp, "", 0, PCAP_NETMASK_UNKNOWN) != 0)
101 		errx(1, "pcap_compile: %s", pcap_geterr(p));
102 	if (pcap_setfilter(p, &fp) != 0)
103 		errx(1, "pcap_setfilter: %s", pcap_geterr(p));
104 	pcap_freecode(&fp);
105 	return (p);
106 }
107 #endif
108 
109 static void
list(int argc __unused,char * argv[]__unused)110 list(int argc __unused, char *argv[] __unused)
111 {
112 	pcap_if_t *all, *p;
113 
114 	if (pcap_findalldevs(&all, errbuf) != 0)
115 		errx(1, "pcap_findalldevs: %s", errbuf);
116 	for (p = all; p != NULL; p = p->next)
117 		printf("%s ", p->name);
118 	printf("\n");
119 	pcap_freealldevs(all);
120 }
121 
122 /* args: tap file count direction */
123 static void
capture(int argc __unused,char * argv[])124 capture(int argc __unused, char *argv[])
125 {
126 	pcap_t *p;
127 	pcap_dumper_t *d;
128 	pcap_direction_t dir;
129 	int cnt;
130 
131 	cnt = strtolerr(argv[2]);
132 	dir = strtodir(argv[3]);
133 	p = pcap_open(argv[0], dir);
134 
135 	if ((d = pcap_dump_open(p, argv[1])) == NULL)
136 		errx(1, "pcap_dump_open: %s", pcap_geterr(p));
137 
138 	if (pcap_loop(p, cnt, pcap_dump, (u_char *)d) != 0)
139 		errx(1, "pcap_loop: %s", pcap_geterr(p));
140 	pcap_dump_close(d);
141 }
142 
143 static void
inject_packet(u_char * user,const struct pcap_pkthdr * h,const u_char * bytes)144 inject_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes)
145 {
146 	pcap_t *p = (pcap_t *)user;
147 
148 	if (h->caplen != h->len)
149 		errx(1, "incomplete packet %u of %u", h->caplen, h->len);
150 
151 	if (pcap_inject(p, bytes, h->caplen) != (int)h->caplen)
152 		errx(1, "pcap_inject: %s", errbuf);
153 }
154 
155 /* args: tap file count */
156 static void
inject(int argc __unused,char * argv[])157 inject(int argc __unused, char *argv[])
158 {
159 	pcap_t *p, *d;
160 	int cnt;
161 
162 	cnt = strtolerr(argv[2]);
163 	p = pcap_open(argv[0], PCAP_D_INOUT);
164 
165 	if ((d = pcap_open_offline(argv[1], errbuf)) == NULL)
166 		errx(1, "pcap_open_offline: %s", errbuf);
167 	if (pcap_loop(d, cnt, inject_packet, (u_char *)p) != 0)
168 		errx(1, "pcap_loop: %s", pcap_geterr(p));
169 	pcap_close(p);
170 	pcap_close(d);
171 }
172 
173 struct packet {
174 	STAILQ_ENTRY(packet)	next;
175 	const void		*data;
176 	u_int			caplen;
177 	u_int			len;
178 };
179 STAILQ_HEAD(plist, packet);
180 
181 static void
store_packet(u_char * user,const struct pcap_pkthdr * h,const u_char * bytes)182 store_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes)
183 {
184 	struct plist *list = (struct plist *)user;
185 	struct packet *p;
186 
187 	p = malloc(sizeof(*p));
188 	p->data = bytes;
189 	p->caplen = h->caplen;
190 	p->len = h->len;
191 	STAILQ_INSERT_TAIL(list, p, next);
192 }
193 
194 /* args: file1 file2 */
195 static void
compare(int argc __unused,char * argv[])196 compare(int argc __unused, char *argv[])
197 {
198 	pcap_t *f1, *f2;
199 	struct plist
200 	    list1 = STAILQ_HEAD_INITIALIZER(list1),
201 	    list2 = STAILQ_HEAD_INITIALIZER(list2);
202 	struct packet *p1, *p2;
203 	u_int cnt;
204 
205         if ((f1 = pcap_open_offline(argv[0], errbuf)) == NULL)
206                 errx(1, "pcap_open_offline: %s", errbuf);
207 	if (pcap_loop(f1, 0, store_packet, (u_char *)&list1) != 0)
208 		errx(1, "pcap_loop: %s", pcap_geterr(f1));
209         if ((f2 = pcap_open_offline(argv[1], errbuf)) == NULL)
210                 errx(1, "pcap_open_offline: %s", errbuf);
211 	if (pcap_loop(f2, 0, store_packet, (u_char *)&list2) != 0)
212 		errx(1, "pcap_loop: %s", pcap_geterr(f2));
213 
214 	for (p1 = STAILQ_FIRST(&list1), p2 = STAILQ_FIRST(&list2), cnt = 1;
215 	    p1 != NULL && p2 != NULL;
216 	    p1 = STAILQ_NEXT(p1, next), p2 = STAILQ_NEXT(p2, next), cnt++) {
217 		if (p1->len != p2->len)
218 			errx(1, "packet #%u length %u != %u",
219 			    cnt, p1->len, p2->len);
220 		if (p1->caplen != p2->caplen)
221 			errx(1, "packet #%u capture length %u != %u",
222 			    cnt, p1->caplen, p2->caplen);
223 		if (memcmp(p1->data, p2->data, p1->caplen) != 0) {
224 			hexdump(p1->data, p1->caplen, argv[0], 0);
225 			hexdump(p2->data, p2->caplen, argv[1], 0);
226 			errx(1, "packet #%u payload different", cnt);
227 		}
228 	}
229 	if (p1 != NULL || p2 != NULL)
230 		errx(1, "packet count different");
231 
232 	pcap_close(f1);
233 	pcap_close(f2);
234 }
235 
236 static const struct cmd {
237 	const char *cmd;
238 	void (*func)(int, char **);
239 	u_int argc;
240 } cmds[] = {
241 	{ .cmd = "list",	.func = list,	.argc = 0 },
242 	{ .cmd = "inject",	.func = inject,	.argc = 3 },
243 	{ .cmd = "capture",	.func = capture,.argc = 4 },
244 	{ .cmd = "compare",	.func = compare,.argc = 2 },
245 };
246 
247 int
main(int argc,char * argv[])248 main(int argc, char *argv[])
249 {
250 
251 	if (argc < 2) {
252 		fprintf(stderr, "Usage: %s ", argv[0]);
253 		for (u_int i = 0; i < nitems(cmds); i++)
254 			fprintf(stderr, "%s%s", cmds[i].cmd,
255 			    i != nitems(cmds) - 1 ? "|" : "\n");
256 		exit(1);
257 	}
258 
259 	for (u_int i = 0; i < nitems(cmds); i++)
260 		if (strcasecmp(argv[1], cmds[i].cmd) == 0) {
261 			argc -= 2;
262 			argv += 2;
263 			if (argc < (int)cmds[i].argc)
264 				errx(1, "%s takes %u args",
265 				    cmds[i].cmd, cmds[i].argc);
266 			cmds[i].func(argc, argv);
267 			return (0);
268 		}
269 
270 	warnx("Unknown command %s\n", argv[1]);
271 	return (1);
272 }
273