/* * Copyright (c) 2003-2005 The Regents of The University of Michigan * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer; * redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution; * neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Authors: Nathan Binkert */ extern "C" { #include } #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define panic(arg...) \ do { printf("Panic: " arg); exit(1); } while (0) const char *program = "ethertap"; void usage() { printf( "usage: \n" "\t%s [-b bufsize] [-d] [-f filter] [-p port] [-v] \n" "\t%s [-b bufsize] [-d] [-f filter] [-l] [-p port] [-v] \n", program, program); exit(2); } int verbose = 0; #define DPRINTF(args...) do { \ if (verbose >= 1) \ printf(args); \ } while (0) #define DDUMP(args...) do { \ if (verbose >= 2) \ dump((const u_char *)args); \ } while (0) void dump(const u_char *data, int len) { int c, i, j; for (i = 0; i < len; i += 16) { printf("%08x ", i); c = len - i; if (c > 16) c = 16; for (j = 0; j < c; j++) { printf("%02x ", data[i + j] & 0xff); if ((j & 0xf) == 7 && j > 0) printf(" "); } for (; j < 16; j++) printf(" "); printf(" "); for (j = 0; j < c; j++) { int ch = data[i + j] & 0x7f; printf("%c", (char)(isprint(ch) ? ch : ' ')); } printf("\n"); if (c < 16) break; } } bool quit = false; void quit_now(int sigtype) { DPRINTF("User requested exit\n"); quit = true; } int Socket(int reuse) { int fd = ::socket(PF_INET, SOCK_STREAM, 0); if (fd < 0) panic("Can't create socket!\n"); if (reuse) { int i = 1; if (::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&i, sizeof(i)) < 0) panic("setsockopt() SO_REUSEADDR failed!\n"); } return fd; } void Listen(int fd, int port) { struct sockaddr_in sockaddr; sockaddr.sin_family = PF_INET; sockaddr.sin_addr.s_addr = INADDR_ANY; sockaddr.sin_port = htons(port); int ret = ::bind(fd, (struct sockaddr *)&sockaddr, sizeof (sockaddr)); if (ret == -1) panic("bind() failed!\n"); if (::listen(fd, 1) == -1) panic("listen() failed!\n"); } // Open a connection. Accept will block, so if you don't want it to, // make sure a connection is ready before you call accept. int Accept(int fd, bool nodelay) { struct sockaddr_in sockaddr; socklen_t slen = sizeof (sockaddr); int sfd = ::accept(fd, (struct sockaddr *)&sockaddr, &slen); if (sfd == -1) panic("accept() failed!\n"); if (nodelay) { int i = 1; ::setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, (char *)&i, sizeof(i)); } return sfd; } void Connect(int fd, const std::string &host, int port) { struct sockaddr_in sockaddr; if (::inet_aton(host.c_str(), &sockaddr.sin_addr) == 0) { struct hostent *hp; hp = ::gethostbyname(host.c_str()); if (!hp) panic("Host %s not found\n", host.c_str()); sockaddr.sin_family = hp->h_addrtype; memcpy(&sockaddr.sin_addr, hp->h_addr, hp->h_length); } sockaddr.sin_port = htons(port); if (::connect(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) != 0) panic("could not connect to %s on port %d\n", host.c_str(), port); DPRINTF("connected to %s on port %d\n", host.c_str(), port); } class Ethernet { protected: int fd; public: virtual ~Ethernet() {} int getfd() const { return fd; } virtual bool read(const char *&data, int &len) = 0; virtual bool write(const char *data, int len) = 0; }; class Tap : public Ethernet { private: char buffer[65536]; int fd; public: Tap(char *device); ~Tap(); virtual bool read(const char *&data, int &len); virtual bool write(const char *data, int len); }; class PCap : public Ethernet { private: pcap_t *pcap; public: PCap(char *device, char *filter = NULL); ~PCap(); virtual bool read(const char *&data, int &len); virtual bool write(const char *data, int len); }; PCap::PCap(char *device, char *filter) { char errbuf[PCAP_ERRBUF_SIZE]; memset(errbuf, 0, sizeof errbuf); pcap = pcap_open_live(device, 1500, 1, -1, errbuf); if (pcap == NULL) panic("pcap_open_live failed: %s\n", errbuf); if (filter) { bpf_program program; bpf_u_int32 localnet, netmask; if (pcap_lookupnet(device, &localnet, &netmask, errbuf) == -1) { DPRINTF("pcap_lookupnet failed: %s\n", errbuf); netmask = 0xffffff00; } if (pcap_compile(pcap, &program, filter, 1, netmask) == -1) panic("pcap_compile failed, invalid filter:\n%s\n", filter); if (pcap_setfilter(pcap, &program) == -1) panic("pcap_setfilter failed\n"); } fd = pcap_fileno(pcap); } PCap::~PCap() { pcap_close(pcap); } bool PCap::read(const char *&data, int &len) { pcap_pkthdr hdr; data = (const char *)pcap_next(pcap, &hdr); if (!data) return false; len = hdr.len; return true; } bool PCap::write(const char *data, int len) { return pcap_inject(pcap, data, len) == len; } Tap::Tap(char *device) { fd = open(device, O_RDWR, 0); if (fd < 0) panic("could not open %s: %s\n", device, strerror(errno)); } Tap::~Tap() { close(fd); } bool Tap::read(const char *&data, int &len) { DPRINTF("tap read!\n"); data = buffer; len = ::read(fd, buffer, sizeof(buffer)); if (len < 0) return false; return true; } bool Tap::write(const char *data, int len) { int result = ::write(fd, data, len); if (result < 0) return false; return true; } int main(int argc, char *argv[]) { int port = 3500; int bufsize = 2000; bool listening = false; char *device = NULL; char *filter = NULL; Ethernet *tap = NULL; bool usetap = false; char c; int daemon = false; std::string host; int devfd; program = basename(argv[0]); int ret; while ((ret = getopt(argc, argv, "b:df:lp:tv")) != -1) { char c = ret; switch (c) { case 'b': bufsize = atoi(optarg); break; case 'd': daemon = true; break; case 'f': filter = optarg; break; case 'l': listening = true; break; case 'p': port = atoi(optarg); break; case 't': usetap = true; break; case 'v': verbose++; break; default: usage(); break; } } signal(SIGINT, quit_now); signal(SIGTERM, quit_now); signal(SIGHUP, quit_now); if (daemon) { verbose = 0; switch(fork()) { case -1: panic("Fork failed\n"); case 0: break; default: exit(0); } } char *buffer = new char[bufsize]; argc -= optind; argv += optind; if (argc-- == 0) usage(); device = *argv++; if (listening) { if (argc) usage(); } else { if (argc != 1) usage(); host = *argv; } if (usetap) { if (filter) panic("-f parameter not valid with a tap device!"); tap = new Tap(device); } else { tap = new PCap(device, filter); } pollfd pfds[3]; pfds[0].fd = Socket(true); pfds[0].events = POLLIN; pfds[0].revents = 0; if (listening) Listen(pfds[0].fd, port); else Connect(pfds[0].fd, host, port); pfds[1].fd = tap->getfd(); pfds[1].events = POLLIN; pfds[1].revents = 0; pfds[2].fd = 0; pfds[2].events = POLLIN|POLLERR; pfds[2].revents = 0; pollfd *listen_pfd = listening ? &pfds[0] : NULL; pollfd *tap_pfd = &pfds[1]; pollfd *client_pfd = listening ? NULL : &pfds[0]; int npfds = 2; int32_t buffer_offset = 0; int32_t data_len = 0; DPRINTF("Begin poll loop\n"); while (!quit) { int ret = ::poll(pfds, npfds, -1); if (ret < 0) continue; if (listen_pfd && listen_pfd->revents) { if (listen_pfd->revents & POLLIN) { int fd = Accept(listen_pfd->fd, false); if (client_pfd) { DPRINTF("Connection rejected\n"); close(fd); } else { DPRINTF("Connection accepted\n"); client_pfd = &pfds[2]; client_pfd->fd = fd; npfds++; } } listen_pfd->revents = 0; } DPRINTF("tap events: %x\n", tap_pfd->revents); if (tap_pfd && tap_pfd->revents) { if (tap_pfd->revents & POLLIN) { const char *data; int len; if (tap->read(data, len) && client_pfd) { DPRINTF("Received packet from ethernet len=%d\n", len); DDUMP(data, len); u_int32_t swaplen = htonl(len); write(client_pfd->fd, &swaplen, sizeof(swaplen)); write(client_pfd->fd, data, len); } } tap_pfd->revents = 0; } if (client_pfd && client_pfd->revents) { if (client_pfd->revents & POLLIN) { if (buffer_offset < data_len + sizeof(u_int32_t)) { int len = read(client_pfd->fd, buffer + buffer_offset, bufsize - buffer_offset); if (len <= 0) { perror("read"); goto error; } buffer_offset += len; if (data_len == 0) data_len = ntohl(*(u_int32_t *)buffer); DPRINTF("Received data from peer: len=%d buffer_offset=%d " "data_len=%d\n", len, buffer_offset, data_len); } while (data_len != 0 && buffer_offset >= data_len + sizeof(u_int32_t)) { char *data = buffer + sizeof(u_int32_t); tap->write(data, data_len); DPRINTF("Sent packet to ethernet len = %d\n", data_len); DDUMP(data, data_len); buffer_offset -= data_len + sizeof(u_int32_t); if (buffer_offset > 0 && data_len > 0) { memmove(buffer, data + data_len, buffer_offset); data_len = ntohl(*(u_int32_t *)buffer); } else data_len = 0; } } if (client_pfd->revents & POLLERR) { error: DPRINTF("Error on client socket\n"); close(client_pfd->fd); client_pfd = NULL; if (listening) npfds--; else { DPRINTF("Calling it quits because of poll error\n"); quit = true; } } if (client_pfd) client_pfd->revents = 0; } } delete [] buffer; delete tap; if (listen_pfd) close(listen_pfd->fd); if (client_pfd) close(client_pfd->fd); return 0; }