1 /*
2 * bittwistb - pcap based ethernet bridge
3 * Copyright (C) 2006 - 2011 Addy Yeow Chin Heng <ayeowch@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 *
19 */
20
21 #include "bittwistb.h"
22
23 char *program_name;
24
25 char ebuf[PCAP_ERRBUF_SIZE]; /* pcap error buffer */
26
27 int vflag = 0; /* 1 - print source and destination MAC for forwarded packets */
28
29 pcap_t *pd[PORT_MAX]; /* array of pcap descriptors for all interfaces */
30 int pd_count = 0; /* number of pcap descriptors created */
31
32 struct bridge_ht bridge_ht[HASH_SIZE]; /* hash table to store bridge_ht structures */
33 struct ether_addr *bridge_addr[PORT_MAX]; /* storage for local MAC */
34
35 /* Ethernet broadcast MAC address */
36 const u_char ether_bcast[ETHER_ADDR_LEN] = {
37 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
38 };
39
40 /* stats */
41 static u_int pkts_sent = 0;
42 static u_int bytes_sent = 0;
43 static u_int failed = 0;
44 struct timeval start = {0,0};
45 struct timeval end = {0,0};
46
main(int argc,char ** argv)47 int main(int argc, char **argv)
48 {
49 char *cp;
50 int c;
51 pcap_if_t *devptr;
52 int i, j;
53 char *devices = NULL;
54
55 if ((cp = strrchr(argv[0], '/')) != NULL)
56 program_name = cp + 1;
57 else
58 program_name = argv[0];
59
60 /* process options */
61 while ((c = getopt(argc, argv, "di:vh")) != -1) {
62 switch (c) {
63 case 'd':
64 if (pcap_findalldevs(&devptr, ebuf) < 0)
65 error("%s", ebuf);
66 else {
67 for (i = 0; devptr != 0; i++) {
68 (void)printf("%d. %s", i + 1, devptr->name);
69 if (devptr->description != NULL)
70 (void)printf(" (%s)", devptr->description);
71 (void)putchar('\n');
72 devptr = devptr->next;
73 }
74 }
75 exit(EXIT_SUCCESS);
76 case 'i':
77 devices = optarg;
78 break;
79 case 'v':
80 ++vflag;
81 break;
82 case 'h':
83 default:
84 usage();
85 }
86 }
87
88 if (devices == NULL)
89 error("interfaces not specified");
90
91 cp = (char *)strtok(devices, ",");
92 i = 0;
93 while (cp != NULL) {
94 if (i >= PORT_MAX)
95 error("invalid interfaces specification");
96
97 /* empty error buffer to grab warning message (if exist) from pcap_open_live() below */
98 *ebuf = '\0';
99
100 /* create a pcap descriptor for each interface to capture packets */
101 pd[i] = pcap_open_live(cp,
102 ETHER_MAX_LEN, /* portion of packet to capture */
103 1, /* promiscuous mode is on */
104 1, /* read timeout, in milliseconds */
105 ebuf);
106 if (pd[i] == NULL)
107 error("%s", ebuf);
108 else if (*ebuf)
109 notice("%s", ebuf); /* warning message from pcap_open_live() above */
110
111 bridge_addr[i] = (struct ether_addr *)malloc(sizeof(struct ether_addr));
112 if (bridge_addr[i] == NULL)
113 error("malloc(): cannot allocate memory for bridge_addr[%d]", i);
114
115 /* copy MAC address for cp into bridge_addr[i] */
116 if (gethwaddr(bridge_addr[i], cp) == -1)
117 error("gethwaddr(): cannot locate hardware address for %s", cp);
118
119 if (vflag > 0) {
120 (void)printf("%s=", cp);
121 for (j = 0; j < ETHER_ADDR_LEN; j++) {
122 (void)printf("%02x", bridge_addr[i]->octet[j]);
123 j == (ETHER_ADDR_LEN - 1) ? (void)putchar(',') : (void)putchar(':');
124 }
125 (void)printf("port=%d\n", i + 1);
126 }
127
128 cp = (char *)strtok(NULL, ",");
129 ++i;
130 }
131 if (i < PORT_MIN)
132 error("invalid interfaces specification");
133 pd_count = i; /* number of pcap descriptors that we have actually created */
134
135 /* set signal handler for SIGINT (Control-C) */
136 (void)signal(SIGINT, cleanup);
137
138 if (gettimeofday(&start, NULL) == -1)
139 notice("gettimeofday(): %s", strerror(errno));
140
141 bridge_on(); /* run bridge */
142
143 exit(EXIT_SUCCESS);
144 }
145
bridge_on(void)146 void bridge_on(void)
147 {
148 /*
149 * struct pollfd {
150 * int fd; -> file descriptor
151 * short events; -> events to look for
152 * short revents; -> events returned
153 * };
154 */
155 struct pollfd pcap_poll[pd_count]; /* to poll initialized pd */
156 int poll_ret = 0; /* return code for poll() */
157 int ret;
158 int i;
159 sigset_t block_sig;
160
161 (void)sigemptyset(&block_sig);
162 (void)sigaddset(&block_sig, SIGALRM);
163
164 /*
165 * we are interested only on these event bitmasks:
166 * POLLIN = Data other than high priority data may be read without blocking
167 * POLLPRI = High priority data may be read without blocking
168 */
169 for (i = 0; i < pd_count; i++) {
170 pcap_poll[i].fd = pcap_fileno(pd[i]);
171 pcap_poll[i].events = POLLIN | POLLPRI;
172 pcap_poll[i].revents = 0;
173 }
174
175 /*
176 * set signal handler for SIGALRM, generated by setitimer() in hash_alarm(),
177 * to remove old hash entries from the hash table
178 */
179 (void)signal(SIGALRM, hash_alarm_handler);
180
181 /* set alarm for the first time */
182 if (hash_alarm(HASH_ALARM) == -1)
183 error("setitimer(): %s", strerror(errno));
184
185 while (1) { /* run infinitely until user Control-C */
186 /* we do not want to interrupt the polling -> hold SIGALRM */
187 (void)sigprocmask(SIG_BLOCK, &block_sig, NULL);
188
189 /* poll for a packet on all the initialized pd */
190 poll_ret = poll(pcap_poll, /* array of pollfd structures */
191 pd_count, /* size of pcap_poll array */
192 1); /* timeout, in milliseconds */
193
194 /* release SIGALRM */
195 (void)sigprocmask(SIG_UNBLOCK, &block_sig, NULL);
196
197 /* we have got packet(s) from one or more of the initialized pd */
198 if (poll_ret > 0) {
199 for (i = 0; i < pd_count; i++) {
200 if (pcap_poll[i].revents > 0) {
201 ret = pcap_dispatch(pd[i], /* pcap descriptor */
202 -1, /* -1 -> process all packets */
203 (pcap_handler)bridge_fwd, /* callback function */
204 (u_char *)(i + 1)); /* LAN segment or port (first port is 1 not 0) */
205 /* ret = number of packets read */
206 if (ret == 0)
207 /* we came in from poll(), timeout is unlikely */
208 notice("pcap_dispatch(): read timeout");
209 else if (ret == -1)
210 notice("%s", pcap_geterr(pd[i]));
211 }
212 }
213 }
214 else if (poll_ret == 0) {
215 /* we do not want to print this during normal use! */
216 /* notice("poll(): time limit expires"); */
217 }
218 else {
219 notice("poll(): %s", strerror(errno));
220 }
221
222 /* reset events */
223 for (i = 0; i < pd_count; i++)
224 pcap_poll[i].revents = 0;
225 }
226 }
227
bridge_fwd(u_char * port,const struct pcap_pkthdr * header,const u_char * pkt_data)228 void bridge_fwd(u_char *port, const struct pcap_pkthdr *header, const u_char *pkt_data)
229 {
230 /*
231 * Ethernet header (14 bytes)
232 * 1. destination MAC (6 bytes)
233 * 2. source MAC (6 bytes)
234 * 3. type (2 bytes)
235 */
236 struct ether_header *eth_hdr;
237 int index; /* hash table index */
238 int i;
239 int new_hash_entry = 0; /* set to 1 if we have new hash entry */
240 int outport = 0; /* port on which this packet will depart */
241 /*
242 * port on which the host with source MAC address for this packet resides on, this
243 * value may be different with port (first argument of this function) if this packet
244 * is departing from an interface when it was captured
245 */
246 int sport = 0;
247
248 /* do nothing if Ethernet header is truncated */
249 if (header->caplen < ETHER_HDR_LEN)
250 return;
251
252 eth_hdr = (struct ether_header *)malloc(ETHER_HDR_LEN);
253 if (eth_hdr == NULL)
254 error("malloc(): cannot allocate memory for eth_hdr");
255
256 /* copy Ethernet header from pkt_data into eth_hdr */
257 memcpy(eth_hdr, pkt_data, ETHER_HDR_LEN);
258
259 /* BEGIN BRIDGE LEARNING SECTION */
260 index = HASH_FUNC(eth_hdr->ether_shost); /* hash source MAC */
261
262 /* found an entry in hash table @ index */
263 if (bridge_ht[index].port != 0) {
264 /* collision -> override previous bridge_ht structure @ index in hash table */
265 if (!ETHER_ADDR_EQ(&bridge_ht[index].etheraddr, eth_hdr->ether_shost))
266 new_hash_entry = 1;
267
268 /* port on which the host with source MAC address for this packet resides on */
269 sport = bridge_ht[index].port;
270 }
271 /* nothing @ index of hash table -> store our new bridge_ht structure there */
272 else {
273 new_hash_entry = 1;
274 }
275
276 /* store a new hash entry */
277 if (new_hash_entry) {
278 /* copy source MAC from eth_hdr into bridge_ht[index] */
279 memcpy(&bridge_ht[index].etheraddr, eth_hdr->ether_shost, ETHER_ADDR_LEN);
280
281 /* port on which source MAC resides on */
282 bridge_ht[index].port = (int)port;
283
284 /* set a timeout for this entry in units of HASH_ALARM in seconds */
285 bridge_ht[index].timeout = HASH_TIMEOUT;
286
287 if (vflag > 0) {
288 (void)printf("[%06d]mac=", index);
289 for (i = 0; i < ETHER_ADDR_LEN; i++) {
290 (void)printf("%02x", eth_hdr->ether_shost[i]);
291 i == (ETHER_ADDR_LEN - 1) ? (void)putchar(',') : (void)putchar(':');
292 }
293 (void)printf("port=%d\n", bridge_ht[index].port);
294 }
295 }
296 /* END BRIDGE LEARNING SECTION */
297
298 /* check if destination MAC is local MAC */
299 for (i = 0; i < pd_count; i++) {
300 if (ETHER_ADDR_EQ(bridge_addr[i]->octet, eth_hdr->ether_dhost)) {
301 free(eth_hdr); eth_hdr = NULL;
302 return;
303 }
304 }
305
306 /* BEGIN BRIDGE FORWARDING SECTION */
307 /* broadcast packet */
308 if (ETHER_ADDR_EQ(eth_hdr->ether_dhost, ether_bcast)) {
309 /* forward packet out through all ports except the port on which this packet arrives */
310 outport = -(int)port;
311 }
312 /* multicast packet */
313 else if (ETHER_IS_MULTICAST(eth_hdr->ether_dhost)) {
314 /* forward packet out through all ports except the port on which this packet arrives */
315 outport = -(int)port;
316 }
317 /* unicast packet */
318 else {
319 index = HASH_FUNC(eth_hdr->ether_dhost); /* hash destination MAC */
320
321 /* nothing @ index in hash table -> we have no information on destination MAC */
322 if (bridge_ht[index].port == 0) {
323 /* forward packet out through all ports except the port on which this packet arrives */
324 outport = -(int)port;
325 }
326 else {
327 /* source MAC in hash table matches destination MAC */
328 if (ETHER_ADDR_EQ(&bridge_ht[index].etheraddr, eth_hdr->ether_dhost)) {
329 /* do not forward packet within the same LAN segment */
330 if (bridge_ht[index].port == (int)port) {
331 outport = 0;
332 }
333 /* forward packet out through bridge_ht[index].port */
334 else {
335 outport = bridge_ht[index].port;
336 }
337 }
338 /* forward packet out through all ports except the port on which this packet arrives */
339 else {
340 outport = -(int)port;
341 }
342 }
343 }
344
345 if (outport != 0) {
346 send_packets(outport,
347 sport,
348 (const struct ether_addr *)eth_hdr->ether_dhost,
349 (const struct ether_addr *)eth_hdr->ether_shost,
350 pkt_data, header->caplen);
351 }
352 /* END BRIDGE FORWARDING SECTION */
353
354 free(eth_hdr); eth_hdr = NULL;
355 return;
356 }
357
send_packets(int outport,int sport,const struct ether_addr * ether_dhost,const struct ether_addr * ether_shost,const u_char * pkt_data,int pkt_len)358 void send_packets(int outport,
359 int sport,
360 const struct ether_addr *ether_dhost,
361 const struct ether_addr *ether_shost,
362 const u_char *pkt_data,
363 int pkt_len)
364 {
365 int start, end;
366 int i, j;
367 int ret;
368 sigset_t block_sig;
369
370 (void)sigemptyset(&block_sig);
371 (void)sigaddset(&block_sig, SIGINT);
372
373 /*
374 * if port is a negative integer, the positive of it is the port
375 * on which this packet arrives
376 */
377 if (outport < 0) { /* send out through all ports except the port on which this packet arrives */
378 start = 0;
379 end = pd_count;
380 }
381 else { /* send out through the specified port only */
382 start = outport - 1;
383 end = outport;
384 }
385
386 (void)sigprocmask(SIG_BLOCK, &block_sig, NULL); /* hold SIGINT */
387
388 /* finish the injection before we give way to SIGINT */
389 for (i = start; i < end; i++) {
390 /*
391 * if we are forwarding this packet out through all ports, do not forward
392 * it back to the port on which this packet arrives
393 */
394 if ((outport < 0) && (i == (-outport - 1))) {
395 /* do nothing */
396 }
397 /*
398 * in addition to the port on which we received this packet, we also should not send this
399 * packet to the port on which the host with this source MAC address resides on
400 */
401 else if ((sport != 0) && (i == (sport - 1))) {
402 /* do nothing */
403 }
404 else {
405 if ((ret = pcap_inject(pd[i], pkt_data, pkt_len)) == -1) {
406 notice("%s", pcap_geterr(pd[i]));
407 ++failed;
408 }
409 else {
410 if (vflag > 0) {
411 (void)printf("dmac=");
412 for (j = 0; j < ETHER_ADDR_LEN; j++) {
413 (void)printf("%02x", ether_dhost->octet[j]);
414 j == (ETHER_ADDR_LEN - 1) ? (void)putchar(',') : (void)putchar(':');
415 }
416 (void)printf("smac=");
417 for (j = 0; j < ETHER_ADDR_LEN; j++) {
418 (void)printf("%02x", ether_shost->octet[j]);
419 j == (ETHER_ADDR_LEN - 1) ? (void)putchar(',') : (void)putchar(':');
420 }
421 (void)printf("outport=%d\n", i + 1);
422 }
423 ++pkts_sent;
424 bytes_sent += ret;
425 }
426 }
427 }
428
429 (void)sigprocmask(SIG_UNBLOCK, &block_sig, NULL); /* release SIGINT */
430 }
431
hash_alarm_handler(int signum)432 void hash_alarm_handler(int signum)
433 {
434 int i;
435
436 for (i = 0; i < HASH_SIZE; i++) {
437 if ((bridge_ht[i].port != 0) && (--bridge_ht[i].timeout < 0)) {
438 bridge_ht[i].port = 0;
439 if (vflag > 0)
440 (void)printf("[%06d]deleted\n", i);
441 }
442 }
443
444 /* reset alarm */
445 if (hash_alarm(HASH_ALARM) == -1)
446 error("setitimer(): %s", strerror(errno));
447 }
448
hash_alarm(unsigned int seconds)449 int hash_alarm(unsigned int seconds)
450 {
451 struct itimerval old, new;
452
453 new.it_interval.tv_usec = 0;
454 new.it_interval.tv_sec = 0;
455 new.it_value.tv_usec = 0;
456 new.it_value.tv_sec = (long)seconds;
457
458 return (setitimer(ITIMER_REAL, &new, &old));
459 }
460
461 /*
462 * looks like BSD does not have SIOCGIFHWADDR,
463 * let me know if you have a portable gethwaddr(), i.e. works for both BSD and Linux systems
464 */
465 #ifndef SIOCGIFHWADDR
466 /*
467 * Reference: getmac.c from bridged by Keisuke Uehara(kei@wide.ad.jp)
468 *
469 * Copyright(c) 1999
470 * Keisuke UEHARA(kei@wide.ad.jp)
471 * All rights reserved.
472 *
473 *
474 * Copyright(c) 1998,1999
475 * Masahiro ISHIYAMA(masahiro@isl.rdc.toshiba.co.jp)
476 * All rights reserved.
477 *
478 */
479 #define ALLSET(flag, bits) (((flag) & (bits)) == (bits))
gethwaddr(struct ether_addr * ether_addr,char * device)480 int gethwaddr(struct ether_addr *ether_addr, char *device)
481 {
482 struct ifaddrs *ifa, *ifap;
483 struct sockaddr_dl *sdl;
484
485 if (getifaddrs(&ifap) < 0)
486 return (-1);
487
488 for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
489 if (strcmp(device, ifa->ifa_name) != 0)
490 continue;
491 if (!ALLSET(ifa->ifa_flags, IFF_UP | IFF_BROADCAST))
492 continue;
493 if (ifa->ifa_addr->sa_family != AF_LINK)
494 continue;
495
496 sdl = (struct sockaddr_dl *)ifa->ifa_addr;
497 bcopy(LLADDR(sdl), ether_addr, ETHER_ADDR_LEN);
498 freeifaddrs(ifap);
499 return (0);
500 }
501
502 freeifaddrs(ifap);
503 return (-1);
504 }
505 #else
506 /* works for Linux */
gethwaddr(struct ether_addr * ether_addr,char * device)507 int gethwaddr(struct ether_addr *ether_addr, char *device)
508 {
509 struct ifreq ifr;
510 int fd;
511
512 if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) != -1) {
513 strcpy(ifr.ifr_name, device);
514 if (ioctl(fd, SIOCGIFHWADDR, &ifr) != -1) {
515 bcopy(&ifr.ifr_hwaddr.sa_data, ether_addr, ETHER_ADDR_LEN);
516 return (0);
517 }
518 }
519
520 return (-1);
521 }
522 #endif
523
info(void)524 void info(void)
525 {
526 struct timeval elapsed;
527 float seconds;
528
529 if (gettimeofday(&end, NULL) == -1)
530 notice("gettimeofday(): %s", strerror(errno));
531 timersub(&end, &start, &elapsed);
532 seconds = elapsed.tv_sec + (float)elapsed.tv_usec / 1000000;
533
534 (void)putchar('\n');
535 notice("%u packets (%u bytes) sent", pkts_sent, bytes_sent);
536 if (failed)
537 notice("%u write attempts failed", failed);
538 notice("Elapsed time = %f seconds", seconds);
539 }
540
cleanup(int signum)541 void cleanup(int signum)
542 {
543 if (signum == -1)
544 exit(EXIT_FAILURE);
545 else
546 info();
547 exit(EXIT_SUCCESS);
548 }
549
550 /*
551 * Reference: tcpdump's util.c
552 *
553 * Copyright (c) 1990, 1991, 1993, 1994, 1995, 1996, 1997
554 * The Regents of the University of California. All rights reserved.
555 *
556 */
notice(const char * fmt,...)557 void notice(const char *fmt, ...)
558 {
559 va_list ap;
560 va_start(ap, fmt);
561 (void)vfprintf(stderr, fmt, ap);
562 va_end(ap);
563 if (*fmt) {
564 fmt += strlen(fmt);
565 if (fmt[-1] != '\n')
566 (void)fputc('\n', stderr);
567 }
568 }
569
570 /*
571 * Reference: tcpdump's util.c
572 *
573 * Copyright (c) 1990, 1991, 1993, 1994, 1995, 1996, 1997
574 * The Regents of the University of California. All rights reserved.
575 *
576 */
error(const char * fmt,...)577 void error(const char *fmt, ...)
578 {
579 va_list ap;
580 (void)fprintf(stderr, "%s: ", program_name);
581 va_start(ap, fmt);
582 (void)vfprintf(stderr, fmt, ap);
583 va_end(ap);
584 if (*fmt) {
585 fmt += strlen(fmt);
586 if (fmt[-1] != '\n')
587 (void)fputc('\n', stderr);
588 }
589 cleanup(-1);
590 }
591
usage(void)592 void usage(void)
593 {
594 (void)fprintf(stderr, "%s version %s\n"
595 "%s\n"
596 "Usage: %s [-d] [-v] [-i interfaces] [-h]\n"
597 "\nOptions:\n"
598 " -d Print a list of network interfaces available.\n"
599 " -v Print forwarding information including the destination\n"
600 " and source MAC addresses of forwarded packets.\n"
601 " -i interfaces List of comma separated Ethernet-type interfaces to bridge.\n"
602 " Example: -i vr0,fxp0\n"
603 " You can specify between %d to %d interfaces.\n"
604 " -h Print version information and usage.\n",
605 program_name, BITTWISTB_VERSION, pcap_lib_version(), program_name, PORT_MIN, PORT_MAX);
606 exit(EXIT_SUCCESS);
607 }
608