URL
https://opencores.org/ocsvn/openrisc/openrisc/trunk
Subversion Repositories openrisc
[/] [openrisc/] [trunk/] [rtos/] [ecos-2.0/] [packages/] [devs/] [eth/] [synth/] [ecosynth/] [v2_0/] [host/] [rawether.c] - Rev 377
Go to most recent revision | Compare with Previous | Blame | View Log
//============================================================================ // // rawether.c // // A utility program to perform low-level ethernet operations // //============================================================================ //####COPYRIGHTBEGIN#### // // ---------------------------------------------------------------------------- // Copyright (C) 2002 Bart Veer // // This file is part of the eCos host tools. // // This program is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free // Software Foundation; either version 2 of the License, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for // more details. // // You should have received a copy of the GNU General Public License along with // this program; if not, write to the Free Software Foundation, Inc., // 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // ---------------------------------------------------------------------------- // //####COPYRIGHTEND#### //============================================================================ //#####DESCRIPTIONBEGIN#### // // Author(s): bartv // Contact(s): bartv // Date: 2002/08/07 // Version: 0.01 // Description: // // This program is fork'ed by the ethernet.tcl script running inside // the synthetic target auxiliary. It is responsible for performing // low-level ethernet I/O. // //####DESCRIPTIONEND#### //============================================================================ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <signal.h> #include <limits.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/param.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <net/if.h> #include <net/if_arp.h> #include <netinet/in.h> #include <netinet/if_ether.h> #include <linux/if_packet.h> #include <linux/if_ether.h> #ifdef HAVE_LINUX_IF_TUN_H # include <linux/if_tun.h> #endif // The protocol between host and target is defined by a private // target-side header. #include "../src/protocol.h" // Allow debug builds. Set this flag to 0, 1 or 2 #define DEBUG 0 // ---------------------------------------------------------------------------- // Statics. // Are we using a real ethernet device or ethertap? static int real_ether = 0; static int ethertap = 0; // The six-byte MAC address, which must be returned to eCos static unsigned char MAC[6]; // Does the driver support multicasting? static int multicast_supported = 0; // The file descriptor for incoming data ethernet packets. // Used for select() together with fd 0 corresponding to ecosynth static int ether_fd = -1; // Is the interface up? static int up = 0; // Space for incoming and outgoing packets. In the case of rx_buffer // there are an extra four bytes at the front for the protocol header. #define MTU 1514 static unsigned char tx_buffer[MTU]; static unsigned char rx_buffer[MTU+4]; // Indirect to get to the actual implementation functions. static void (*tx_fn)(unsigned char*, int); static void (*rx_fn)(void); static void (*start_fn)(int); static void (*stop_fn)(void); static void (*multicast_fn)(int); // ---------------------------------------------------------------------------- // A utility buffer for messages. #define MSG_SIZE 256 static unsigned char msg[MSG_SIZE]; // Report an error to ecosynth during initialization. This means a // single byte 0, followed by a string. static void report_error(char* msg) { write(1, "0", 1); write(1, msg, strlen(msg)); close(1); exit(0); } // Report success to ecosynth. This means a byte 1 followed by // the MAC address. static void report_success(void) { write(1, "1", 1); memcpy(msg, MAC, 6); msg[6] = multicast_supported; write(1, msg, 7); } // ---------------------------------------------------------------------------- // Real ethernet. This involves creating a SOCK_RAW socket and binding it // to the appropriate interface. Relevant documentation can be found in // the man pages (packet(7) and netdevice(7)). // The device name. Needed for various ioctl()'s. static char real_devname[IFNAMSIZ]; // The interface index. static int real_ifindex = -1; // Transmit a single ethernet frame. The socket should be set up so a // simple send() operation should do the trick. Errors such as EAGAIN, // indicating that the network device is still busy, are ignored. // Ethernet is not a reliable communication medium. static void real_handle_tx(unsigned char* buffer, int size) { int result; result = send(ether_fd, buffer, size, MSG_DONTWAIT); if (result < 0) { // It appears that one retry is worthwhile, to clear pending // errors or something. result = send(ether_fd, buffer, size, MSG_DONTWAIT); } #if (DEBUG > 0) fprintf(stderr, "rawether dbg: tx %d bytes -> %d\n", size, result); #endif #if (DEBUG > 1) fprintf(stderr, " %x:%x:%x:%x:%x:%x %x:%x:%x:%x:%x:%x %x:%x\n", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12], buffer[13]); #endif } // Receive a single ethernet frame, using the static rxbuffer. If the // interface is not currently up discard it. Otherwise forward it on // to ecosynth. static void real_handle_rx(void) { int size; int result; size = recv(ether_fd, rx_buffer + 4, MTU, MSG_TRUNC); #if (DEBUG > 0) fprintf(stderr, "rawether dbg: rx returned %d, errno %s (%d)\n", size, (errno < sys_nerr) ? sys_errlist[errno] : "<unknown>", errno); #endif if (size < 0) { return; // Ignore errors, just go around the main loop again. } if ((size < 14) || (size > MTU)) { return; // Invalid packet size. Discard the packet. } #if (DEBUG > 1) fprintf(stderr, " %x:%x:%x:%x:%x:%x %x:%x:%x:%x:%x:%x %x:%x\n", rx_buffer[4], rx_buffer[5], rx_buffer[6], rx_buffer[7], rx_buffer[8], rx_buffer[9], rx_buffer[10], rx_buffer[11], rx_buffer[12], rx_buffer[13], rx_buffer[14], rx_buffer[15], rx_buffer[16], rx_buffer[17]); #endif if (!up) { // eCos is not currently expecting packets, so discard them. // This may not actually be necessary because the interface // is only up when eCos wants it to be up. return; } // It looks this packet should get forwarded to eCos. rx_buffer[0] = SYNTH_ETH_RX; rx_buffer[1] = 0; rx_buffer[2] = size & 0x00FF; rx_buffer[3] = (size >> 8) & 0x00FF; do { result = write(1, rx_buffer, 4 + size); } while ((-1 == result) && (EINTR == errno)); if (result != (size + 4)) { fprintf(stderr, "rawether(%s): failed to send ethernet packet to I/O auxiliary, exiting.\n", real_devname); exit(1); } } // Utility to manipulate interface flags. This involves retrieving the // current flags, or'ing in some bits, and'ing out others, and updating. static void real_update_ifflags(int set_bits, int clear_bits) { struct ifreq request; int flags; strncpy(request.ifr_name, real_devname, IFNAMSIZ); if (ioctl(ether_fd, SIOCGIFFLAGS, &request) < 0) { fprintf(stderr, "rawether (%s): failed to get interface flags, exiting\n", real_devname); exit(1); } flags = request.ifr_flags; flags |= set_bits; flags &= ~clear_bits; if (flags == request.ifr_flags) { // Nothing is changing. return; } strncpy(request.ifr_name, real_devname, IFNAMSIZ); request.ifr_flags = flags; if (ioctl(ether_fd, SIOCSIFFLAGS, &request) < 0) { fprintf(stderr, "rawether (%s): failed to update interface flags, exiting\n", real_devname); exit(1); } } // Starting an interface. This involves bringing the interface up, // and optionally setting promiscuous mode. // NOTE: is UP really the right thing here? There is no IP address // for this interface. In theory this should not matter because // we have a bound socket which should receive all packets for // this interface. static void real_handle_start(int promiscuous) { if (promiscuous) { real_update_ifflags(IFF_UP | IFF_PROMISC, 0); } else { real_update_ifflags(IFF_UP, IFF_PROMISC); } up = 1; } // Stopping an interface means clearing the UP flag static void real_handle_stop(void) { up = 0; real_update_ifflags(0, IFF_UP | IFF_PROMISC); } // Enabling/disabling multicast support. static void real_handle_multiall(int on) { struct packet_mreq req; req.mr_ifindex = real_ifindex; req.mr_type = PACKET_MR_ALLMULTI; req.mr_alen = 0; if (setsockopt(ether_fd, SOL_PACKET, on ? PACKET_ADD_MEMBERSHIP : PACKET_DROP_MEMBERSHIP, (void*)&req, sizeof(req)) < 0) { fprintf(stderr, "rawether (%s): failed to manipulate multicast-all flag, exiting\n", real_devname); exit(1); } } // When the application exists make sure that the interface goes down again. static void real_atexit(void) { if (up) { real_update_ifflags(0, IFF_UP | IFF_PROMISC); } } static void real_init(char* devname) { struct sockaddr_ll addr; struct ifreq request; tx_fn = &real_handle_tx; rx_fn = &real_handle_rx; start_fn = &real_handle_start; stop_fn = &real_handle_stop; multicast_fn = &real_handle_multiall; if (strlen(devname) >= IFNAMSIZ) { snprintf(msg, MSG_SIZE, "Invalid real network device name \"%s\", too long.\n", devname); report_error(msg); } strcpy(real_devname, devname); // All ioctl() operations need a socket. We might as well create the // raw socket immediately and use that. ether_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (ether_fd < 0) { snprintf(msg, MSG_SIZE, "Unable to create a raw socket for accessing network device\n" " Error %s (errno %d)\n", (errno < sys_nerr) ? sys_errlist[errno] : "unknown", errno); report_error(msg); } strncpy(request.ifr_name, real_devname, IFNAMSIZ); if (ioctl(ether_fd, SIOCGIFINDEX, &request) < 0) { snprintf(msg, MSG_SIZE, "Device %s does not correspond to a valid interface.\n" " Error %s (errno %d)\n", real_devname, (errno < sys_nerr) ? sys_errlist[errno] : "unknown", errno); report_error(msg); } real_ifindex = request.ifr_ifindex; // The interface exists. Now check that it is usable. strncpy(request.ifr_name, real_devname, IFNAMSIZ); if (ioctl(ether_fd, SIOCGIFFLAGS, &request) < 0) { snprintf(msg, MSG_SIZE, "Failed to get current interface flags for %s\n" " Error %s (errno %d)\n", real_devname, (errno < sys_nerr) ? sys_errlist[errno] : "unknown", errno); report_error(msg); } if (request.ifr_flags & (IFF_UP | IFF_RUNNING)) { snprintf(msg, MSG_SIZE, "Network device %s is already up and running.\n" " Exclusive access is required\n", real_devname); report_error(msg); } if (request.ifr_flags & IFF_LOOPBACK) { report_error("Loopback devices cannot be used for synthetic target ethernet emulation.\n"); } if (request.ifr_flags & IFF_POINTOPOINT) { report_error("Point-to-point devices cannot be used for synthetic target ethernet emulation.\n"); } if (request.ifr_flags & IFF_MULTICAST) { multicast_supported = 1; } // Make sure the interface is down. There is no point in receiving packets just yet. real_update_ifflags(0, IFF_UP | IFF_PROMISC); // The flags look ok. Now get hold of the hardware address. strncpy(request.ifr_name, real_devname, IFNAMSIZ); if (ioctl(ether_fd, SIOCGIFHWADDR, &request) < 0) { snprintf(msg, MSG_SIZE, "Failed to get hardware address for %s\n" " Error %s (errno %d)\n", real_devname, (errno < sys_nerr) ? sys_errlist[errno] : "unknown", errno); report_error(msg); } if (ARPHRD_ETHER != request.ifr_hwaddr.sa_family) { snprintf(msg, MSG_SIZE, "Device %s is not an ethernet device.\n", real_devname); report_error(msg); } memcpy(MAC, request.ifr_hwaddr.sa_data, 6); // The device is useable. Now just bind the socket to the appropriate address. addr.sll_family = AF_PACKET; addr.sll_protocol = htons(ETH_P_ALL); addr.sll_ifindex = real_ifindex; if (bind(ether_fd, (struct sockaddr*) &addr, sizeof(addr)) < 0) { snprintf(msg, MSG_SIZE, "Failed to bind socket for direct hardware address to %s\n" " Error %s (errno %d)\n", real_devname, (errno < sys_nerr) ? sys_errlist[errno] : "unknown", errno); report_error(msg); } // Make sure the interface gets shut down when rawether exits. atexit(real_atexit); // And that should be it. } // ---------------------------------------------------------------------------- // Ethertap device. // // See /usr/src/linux-2.x.y/Documentation/networking/tuntap.txt for more // information on the tun/tap driver. // // Basically during initialization this code opens /dev/net/tun, then // performs a TUNSETIFF ioctl() to initialize it. This causes a // new network device tap?? to appear. Any ethernet frames written // by the Linux kernel to this device can be read from the // dev/net/tun file descriptor, and ethernet frames can be written to // the same descriptor. The net effect is a virtual ethernet segment // with one interface managed by the Linux kernel and another // interface (or, theoretically, several) accessible via the file // descriptor. // // The Linux kernel will invent a MAC address for its interface. An // additional one is needed for eCos. This is either invented or // specified in the target definition file. // // Old Linux kernels may not have the required support. This is detected // by an autoconf test for <linux/if_tun.h> #ifdef HAVE_LINUX_IF_TUN_H static void tap_handle_tx(unsigned char* buffer, int size) { int result; result = write(ether_fd, buffer, size); #if (DEBUG > 0) fprintf(stderr, "rawether dbg: tx %d bytes -> %d\n", size, result); #endif #if (DEBUG > 1) fprintf(stderr, " %x:%x:%x:%x:%x:%x %x:%x:%x:%x:%x:%x %x:%x\n", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12], buffer[13]); #endif } // Receive a single packet from the socket. It is assumed that the // tuntap code inside the kernel will preserve packet boundaries. // // For now it is assumed that all incoming packets are intended for // eCos. That may not be accurate, and additional filtering in // software might be appropriate. In promiscuous mode all packets // should be accepted, obviously. Otherwise all broadcasts should // be accepted, as should all messages intended for this specific // interface's MAC address. Multicasts should be accepted only if // enabled. static void tap_handle_rx(void) { int size; int result; // select() has succeeded so this read() should never block. size = read(ether_fd, rx_buffer + 4, MTU); #if (DEBUG > 0) fprintf(stderr, "rawether dbg: rx returned %d, errno %s (%d)\n", size, (errno < sys_nerr) ? sys_errlist[errno] : "<unknown>", errno); #endif if (size < 0) { return; // Ignore errors, just go around the main loop again. } if ((size < 14) || (size > MTU)) { return; // Invalid packet size. Discard the packet. } #if (DEBUG > 1) fprintf(stderr, " %x:%x:%x:%x:%x:%x %x:%x:%x:%x:%x:%x %x:%x\n", rx_buffer[4], rx_buffer[5], rx_buffer[6], rx_buffer[7], rx_buffer[8], rx_buffer[9], rx_buffer[10], rx_buffer[11], rx_buffer[12], rx_buffer[13], rx_buffer[14], rx_buffer[15], rx_buffer[16], rx_buffer[17]); #endif if (!up) { // eCos is not currently expecting packets, so discard them. return; } // It looks this packet should get forwarded to eCos. rx_buffer[0] = SYNTH_ETH_RX; rx_buffer[1] = 0; rx_buffer[2] = size & 0x00FF; rx_buffer[3] = (size >> 8) & 0x00FF; do { result = write(1, rx_buffer, 4 + size); } while ((-1 == result) && (EINTR == errno)); if (result != (size + 4)) { fprintf(stderr, "rawether(%s): failed to send ethernet packet to I/O auxiliary, exiting.\n", real_devname); exit(1); } } // Nothing much can be done for start or stop. Just set the flag and // let the rx and tx code discard packets when appropriate. // // For now the device is implicitly promiscuous and accepts all // multicasts. Given the nature of a tap device it is unlikely that // any packets will arrive which are not destined here. // FIXME: this may have to change if bridging is enabled. static void tap_handle_start(int promiscuous) { up = 1; } static void tap_handle_stop(void) { up = 0; } static void tap_handle_multiall(int on) { } static void tap_init(int argc, char** argv) { char* devname = NULL; struct ifreq ifr; tx_fn = &tap_handle_tx; rx_fn = &tap_handle_rx; start_fn = &tap_handle_start; stop_fn = &tap_handle_stop; multicast_fn = &tap_handle_multiall; // Which device? By default let the system pick one, but the user // can override this. if (0 != argc) { devname = argv[0]; } // Work out the MAC address. By default a random one is generated, // but the user can specify one to avoid a source of randomness. // This MAC address is not actually needed by any of the code here, // but should be returned to eCos. if (2 == argc) { unsigned int mac_data[6]; // sscanf() needs unsigned ints int result = sscanf(argv[1], "%x:%x:%x:%x:%x:%x", &(mac_data[0]), &(mac_data[1]), &(mac_data[2]), &(mac_data[3]), &(mac_data[4]), &(mac_data[5])); if (6 != result) { snprintf(msg, MSG_SIZE, "Invalid MAC address %s\n", argv[1]); report_error(msg); } MAC[0] = mac_data[0]; MAC[1] = mac_data[1]; MAC[2] = mac_data[2]; MAC[3] = mac_data[3]; MAC[4] = mac_data[4]; MAC[5] = mac_data[5]; } else { srand(time(NULL)); MAC[0] = 0; MAC[1] = 0x0FF; MAC[2] = rand() & 0x0FF; MAC[3] = rand() & 0x0FF; MAC[4] = rand() & 0x0FF; MAC[5] = rand() & 0x0FF; } ether_fd = open("/dev/net/tun", O_RDWR); if (ether_fd < 0) { snprintf(msg, MSG_SIZE, "Failed to open /dev/net/tun, errno %s (%d)\n", (errno < sys_nerr) ? sys_errlist[errno] : "<unknown>", errno); report_error(msg); } memset(&ifr, 0, sizeof(ifr)); ifr.ifr_flags = IFF_TAP | IFF_NO_PI; if (NULL != devname) { strncpy(ifr.ifr_name, devname, IFNAMSIZ - 1); } if (ioctl(ether_fd, TUNSETIFF, (void*)&ifr) < 0) { snprintf(msg, MSG_SIZE, "Failed to initialize /dev/net/tun, errno %s (%d)\n", (errno < sys_nerr) ? sys_errlist[errno] : "<unknown>", errno); report_error(msg); } // Supporting multicasts is a no-op multicast_supported = 1; // All done. } #else static void tap_init(int argc, char** argv) { snprintf(msg, MSG_SIZE, "Ethertap support was not available when the host-side support was built\n"); report_error(msg); } #endif // HAVE_LINUX_IF_TUN_H // ---------------------------------------------------------------------------- // Receive a single request from ecosynth. This consists of a four-byte // header, optionally followed by a tx packet. EOF indicates that // ecosynth has exited, so this process should just exit immediately // as well. Any problems should be reported to stderr, followed by // termination. // // Currently rawether is single-threaded. Theoretically this could // cause a deadlock situation where the I/O auxiliary is trying to send // rawether a request and is blocked on the write, while rawether is trying // to send data to the I/O auxiliary. In practice the pipes should do // enough buffering to avoid complications, especially since rawether // gives priority to requests from the auxiliary. static void handle_ecosynth_request(void) { unsigned char req[4]; int result; int code, arg, size; result = read(0, req, 4); if (result == 0) { // select() succeeded but no data. EOF. So exit exit(0); } if (result < 0) { // EINTR? EAGAIN? The latter should not happen since the pipe // has not been put into non-blocking mode. if ((EINTR == errno) || (EAGAIN == errno)) { return; } else { fprintf(stderr, "rawether: unexpected error reading request from ecosynth\n"); if (errno < sys_nerr) { fprintf(stderr, " %s\n", sys_errlist[errno]); } exit(1); } } if (result < 4) { fprintf(stderr, "rawether: unexpected error reading request from ecosynth\n Expected 4 bytes, only received %d\n", result); exit(1); } code = req[0]; arg = req[1]; size = req[2] + (req[3] << 8); #if (DEBUG > 1) fprintf(stderr, "rawether dbg: request %x from auxiliary\n", code); #endif switch(code) { case SYNTH_ETH_TX: { if (size < 14) { fprintf(stderr, "rawether: attempt to send invalid ethernet packet of only %d bytes\n" "Ethernet packets should be at least 14 bytes.\n", size); exit(1); } if (size > MTU) { fprintf(stderr, "rawether: attempt to send invalid ethernet packet of %d bytes\n" "Only packets of up to %d bytes are supported.\n", size, MTU); exit(1); } do { result = read(0, tx_buffer, size); } while ((-1 == result) && (EINTR == errno)); if (0 == result) { // EOF, at an inopportune moment exit(0); } if (result < size) { fprintf(stderr, "rawether: error reading ethernet packet from I/O auxiliary\n" "Expected %d bytes but only read %d\n", size, result); exit(1); } (*tx_fn)(tx_buffer, size); break; } case SYNTH_ETH_START: { (*start_fn)(arg); break; } case SYNTH_ETH_STOP: { (*stop_fn)(); break; } case SYNTH_ETH_MULTIALL: { (*multicast_fn)(arg); break; } // SYNTH_ETH_RX and SYNTH_ETH_GETPARAMS are handled inside ethernet.tcl default: fprintf(stderr, "rawether: protocol violation, received unknown request %d\n", code); exit(1); } } // The main loop. This waits for an event either from ecosynth or from // the underlying ethernet device, using select. Requests from // ecosynth are handled, and take priority to prevent the connecting // pipe from filling up and ecosynth blocking. Incoming ethernet // frames are forwarded to ecosynth. static void mainloop(void) { fd_set read_set; struct timeval timeout; int result; for ( ; ; ) { FD_ZERO(&read_set); FD_SET(0, &read_set); FD_SET(ether_fd, &read_set); timeout.tv_sec = 24 * 60 * 60; timeout.tv_usec = 0; result = select(ether_fd + 1, &read_set, NULL, NULL, &timeout); if (result <= 0) { continue; } if (FD_ISSET(0, &read_set)) { handle_ecosynth_request(); } else if (FD_ISSET(ether_fd, &read_set)) { (*rx_fn)(); } } } // ---------------------------------------------------------------------------- int main(int argc, char**argv) { // Ignore incoming ctrl-C's. We are in the same process group as the // eCos application which may sensibly be ctrl-C'd, but that should // result in the auxiliary detecting EOF and closing the pipe to // this process, which in turn causes this process to exit completely. signal(SIGINT, SIG_IGN); if (2 > argc ) { report_error("Expected at least one argument, \"real\" or \"ethertap\"\n"); } if (0 == strcmp("real", argv[1])) { real_ether = 1; real_init(argv[2]); } else if (0 == strcmp("ethertap", argv[1])) { ethertap = 1; tap_init(argc - 2, argv + 2); } else { snprintf(msg, MSG_SIZE, "Invalid argument %s, expected \"real\" or \"ethertap\"\n", argv[1]); report_error(msg); } // If the device-specific initialization succeeded we must be set. report_success(); mainloop(); return 0; }
Go to most recent revision | Compare with Previous | Blame | View Log