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 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 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 * 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 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 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 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 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 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 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 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