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 <unistd.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <strings.h>
38 #include <err.h>
39
40 static int
strtolerr(const char * s)41 strtolerr(const char *s)
42 {
43 int rv;
44
45 if ((rv = (int)strtol(s, NULL, 10)) < 1)
46 errx(1, "bad count %s", s);
47 return (rv);
48 }
49
50 static pcap_direction_t
strtodir(const char * s)51 strtodir(const char *s)
52 {
53 static const struct dirstr {
54 const char *str;
55 pcap_direction_t dir;
56 } dirs[] = {
57 { "in", PCAP_D_IN },
58 { "out", PCAP_D_OUT },
59 { "both", PCAP_D_INOUT },
60 { "inout", PCAP_D_INOUT },
61 };
62
63 for (u_int i = 0; i < nitems(dirs); i++)
64 if (strcasecmp(s, dirs[i].str) == 0)
65 return (dirs[i].dir);
66 errx(1, "bad directions %s", s);
67 }
68
69 static char errbuf[PCAP_ERRBUF_SIZE];
70
71 static pcap_t *
pcap_open(const char * name,pcap_direction_t dir)72 pcap_open(const char *name, pcap_direction_t dir)
73 {
74 pcap_t *p;
75
76 if ((p = pcap_create(name, errbuf)) == NULL)
77 errx(1, "pcap_create: %s", errbuf);
78 if (pcap_set_timeout(p, 10) != 0)
79 errx(1, "pcap_set_timeout: %s", pcap_geterr(p));
80 if (pcap_activate(p) != 0)
81 errx(1, "pcap_activate: %s", errbuf);
82 if (pcap_setdirection(p, dir) != 0)
83 errx(1, "pcap_setdirection: %s", pcap_geterr(p));
84 return (p);
85 }
86
87 #if 0
88 /*
89 * Deal with the FreeBSD writer only optimization hack in bpf(4).
90 * Needed only when net.bpf.optimize_writers=1.
91 */
92 static pcap_t *
93 pcap_rwopen(const char *name, pcap_direction_t dir)
94 {
95 pcap_t *p;
96 struct bpf_program fp;
97
98 p = pcap_open(name, dir);
99 if (pcap_compile(p, &fp, "", 0, PCAP_NETMASK_UNKNOWN) != 0)
100 errx(1, "pcap_compile: %s", pcap_geterr(p));
101 if (pcap_setfilter(p, &fp) != 0)
102 errx(1, "pcap_setfilter: %s", pcap_geterr(p));
103 pcap_freecode(&fp);
104 return (p);
105 }
106 #endif
107
108 static void
list(int argc __unused,char * argv[]__unused)109 list(int argc __unused, char *argv[] __unused)
110 {
111 pcap_if_t *all, *p;
112
113 if (pcap_findalldevs(&all, errbuf) != 0)
114 errx(1, "pcap_findalldevs: %s", errbuf);
115 for (p = all; p != NULL; p = p->next)
116 printf("%s ", p->name);
117 printf("\n");
118 pcap_freealldevs(all);
119 }
120
121 /* args: tap file count direction */
122 static void
capture(int argc __unused,char * argv[])123 capture(int argc __unused, char *argv[])
124 {
125 pcap_t *p;
126 pcap_dumper_t *d;
127 pcap_direction_t dir;
128 int cnt;
129
130 cnt = strtolerr(argv[2]);
131 dir = strtodir(argv[3]);
132 p = pcap_open(argv[0], dir);
133
134 if ((d = pcap_dump_open(p, argv[1])) == NULL)
135 errx(1, "pcap_dump_open: %s", pcap_geterr(p));
136
137 if (pcap_loop(p, cnt, pcap_dump, (u_char *)d) != 0)
138 errx(1, "pcap_loop: %s", pcap_geterr(p));
139 pcap_dump_close(d);
140 }
141
142 static void
inject_packet(u_char * user,const struct pcap_pkthdr * h,const u_char * bytes)143 inject_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes)
144 {
145 pcap_t *p = (pcap_t *)user;
146
147 if (h->caplen != h->len)
148 errx(1, "incomplete packet %u of %u", h->caplen, h->len);
149
150 if (pcap_inject(p, bytes, h->caplen) != (int)h->caplen)
151 errx(1, "pcap_inject: %s", errbuf);
152 }
153
154 /* args: tap file count */
155 static void
inject(int argc __unused,char * argv[])156 inject(int argc __unused, char *argv[])
157 {
158 pcap_t *p, *d;
159 int cnt;
160
161 cnt = strtolerr(argv[2]);
162 p = pcap_open(argv[0], PCAP_D_INOUT);
163
164 if ((d = pcap_open_offline(argv[1], errbuf)) == NULL)
165 errx(1, "pcap_open_offline: %s", errbuf);
166 if (pcap_loop(d, cnt, inject_packet, (u_char *)p) != 0)
167 errx(1, "pcap_loop: %s", pcap_geterr(p));
168 pcap_close(p);
169 pcap_close(d);
170 }
171
172 struct packet {
173 STAILQ_ENTRY(packet) next;
174 const void *data;
175 u_int caplen;
176 u_int len;
177 };
178 STAILQ_HEAD(plist, packet);
179
180 static void
store_packet(u_char * user,const struct pcap_pkthdr * h,const u_char * bytes)181 store_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes)
182 {
183 struct plist *list = (struct plist *)user;
184 struct packet *p;
185
186 p = malloc(sizeof(*p));
187 p->data = bytes;
188 p->caplen = h->caplen;
189 p->len = h->len;
190 STAILQ_INSERT_TAIL(list, p, next);
191 }
192
193 /* args: file1 file2 */
194 static void
compare(int argc __unused,char * argv[])195 compare(int argc __unused, char *argv[])
196 {
197 pcap_t *f1, *f2;
198 struct plist
199 list1 = STAILQ_HEAD_INITIALIZER(list1),
200 list2 = STAILQ_HEAD_INITIALIZER(list2);
201 struct packet *p1, *p2;
202 u_int cnt;
203
204 if ((f1 = pcap_open_offline(argv[0], errbuf)) == NULL)
205 errx(1, "pcap_open_offline: %s", errbuf);
206 if (pcap_loop(f1, 0, store_packet, (u_char *)&list1) != 0)
207 errx(1, "pcap_loop: %s", pcap_geterr(f1));
208 if ((f2 = pcap_open_offline(argv[1], errbuf)) == NULL)
209 errx(1, "pcap_open_offline: %s", errbuf);
210 if (pcap_loop(f2, 0, store_packet, (u_char *)&list2) != 0)
211 errx(1, "pcap_loop: %s", pcap_geterr(f2));
212
213 for (p1 = STAILQ_FIRST(&list1), p2 = STAILQ_FIRST(&list2), cnt = 1;
214 p1 != NULL && p2 != NULL;
215 p1 = STAILQ_NEXT(p1, next), p2 = STAILQ_NEXT(p2, next), cnt++) {
216 if (p1->len != p2->len)
217 errx(1, "packet #%u length %u != %u",
218 cnt, p1->len, p2->len);
219 if (p1->caplen != p2->caplen)
220 errx(1, "packet #%u capture length %u != %u",
221 cnt, p1->caplen, p2->caplen);
222 if (memcmp(p1->data, p2->data, p1->caplen) != 0)
223 errx(1, "packet #%u payload different", cnt);
224 }
225 if (p1 != NULL || p2 != NULL)
226 errx(1, "packet count different");
227
228 pcap_close(f1);
229 pcap_close(f2);
230 }
231
232 static const struct cmd {
233 const char *cmd;
234 void (*func)(int, char **);
235 u_int argc;
236 } cmds[] = {
237 { .cmd = "list", .func = list, .argc = 0 },
238 { .cmd = "inject", .func = inject, .argc = 3 },
239 { .cmd = "capture", .func = capture,.argc = 4 },
240 { .cmd = "compare", .func = compare,.argc = 2 },
241 };
242
243 int
main(int argc,char * argv[])244 main(int argc, char *argv[])
245 {
246
247 if (argc < 2) {
248 fprintf(stderr, "Usage: %s ", argv[0]);
249 for (u_int i = 0; i < nitems(cmds); i++)
250 fprintf(stderr, "%s%s", cmds[i].cmd,
251 i != nitems(cmds) - 1 ? "|" : "\n");
252 exit(1);
253 }
254
255 for (u_int i = 0; i < nitems(cmds); i++)
256 if (strcasecmp(argv[1], cmds[i].cmd) == 0) {
257 argc -= 2;
258 argv += 2;
259 if (argc < (int)cmds[i].argc)
260 errx(1, "%s takes %u args",
261 cmds[i].cmd, cmds[i].argc);
262 cmds[i].func(argc, argv);
263 return (0);
264 }
265
266 warnx("Unknown command %s\n", argv[1]);
267 return (1);
268 }
269