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