/* * ether_unix.cpp - Ethernet device driver, Unix specific stuff (Linux and FreeBSD) * * Basilisk II (C) 1997-2004 Christian Bauer * * 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 */ #include "sysdeps.h" #include #include #include #include #include #include #include #include #include #include #if defined(__FreeBSD__) || defined(sgi) #include #endif #if defined(HAVE_LINUX_IF_H) && defined(HAVE_LINUX_IF_TUN_H) #include #include #endif #if defined(HAVE_NET_IF_H) && defined(HAVE_NET_IF_TUN_H) #include #include #endif #include "cpu_emulation.h" #include "main.h" #include "macos_util.h" #include "prefs.h" #include "user_strings.h" #include "ether.h" #include "ether_defs.h" #ifndef NO_STD_NAMESPACE using std::map; #endif #define DEBUG 0 #include "debug.h" #define MONITOR 0 // Ethernet device types enum { NET_IF_SHEEPNET, NET_IF_ETHERTAP, NET_IF_TUNTAP, }; // Constants static const char ETHERCONFIG_FILE_NAME[] = DATADIR "/tunconfig"; // Global variables static int fd = -1; // fd of sheep_net device static pthread_t ether_thread; // Packet reception thread static pthread_attr_t ether_thread_attr; // Packet reception thread attributes static bool thread_active = false; // Flag: Packet reception thread installed static sem_t int_ack; // Interrupt acknowledge semaphore static bool udp_tunnel; // Flag: UDP tunnelling active, fd is the socket descriptor static int net_if_type = -1; // Ethernet device type static char *net_if_name = NULL; // TUN/TAP device name static const char *net_if_script = NULL; // Network config script // Attached network protocols, maps protocol type to MacOS handler address static map net_protocols; // Prototypes static void *receive_func(void *arg); /* * Start packet reception thread */ static bool start_thread(void) { if (sem_init(&int_ack, 0, 0) < 0) { printf("WARNING: Cannot init semaphore"); return false; } Set_pthread_attr(ðer_thread_attr, 1); thread_active = (pthread_create(ðer_thread, ðer_thread_attr, receive_func, NULL) == 0); if (!thread_active) { printf("WARNING: Cannot start Ethernet thread"); return false; } return true; } /* * Stop packet reception thread */ static void stop_thread(void) { if (thread_active) { pthread_cancel(ether_thread); pthread_join(ether_thread, NULL); sem_destroy(&int_ack); thread_active = false; } } /* * Execute network script up|down */ static bool execute_network_script(const char *action) { if (net_if_script == NULL || net_if_name == NULL) return false; int pid = fork(); if (pid >= 0) { if (pid == 0) { char *args[4]; args[0] = (char *)net_if_script; args[1] = net_if_name; args[2] = (char *)action; args[3] = NULL; execv(net_if_script, args); exit(1); } int status; while (waitpid(pid, &status, 0) != pid); return WIFEXITED(status) && WEXITSTATUS(status) == 0; } return false; } /* * Initialization */ bool ether_init(void) { int nonblock = 1; char str[256]; // Do nothing if no Ethernet device specified const char *name = PrefsFindString("ether"); if (name == NULL) return false; // Determine Ethernet device type net_if_type = -1; if (strncmp(name, "tap", 3) == 0) net_if_type = NET_IF_ETHERTAP; #if ENABLE_TUNTAP else if (strcmp(name, "tun") == 0) net_if_type = NET_IF_TUNTAP; #endif else net_if_type = NET_IF_SHEEPNET; // Open sheep_net or ethertap device char dev_name[16]; switch (net_if_type) { case NET_IF_ETHERTAP: sprintf(dev_name, "/dev/%s", name); break; case NET_IF_TUNTAP: sprintf(dev_name, "/dev/net/tun", name); break; case NET_IF_SHEEPNET: strcpy(dev_name, "/dev/sheep_net"); break; } fd = open(dev_name, O_RDWR); if (fd < 0) { sprintf(str, GetString(STR_NO_SHEEP_NET_DRIVER_WARN), dev_name, strerror(errno)); WarningAlert(str); goto open_error; } #if ENABLE_TUNTAP // Open TUN/TAP interface if (net_if_type == NET_IF_TUNTAP) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); ifr.ifr_flags = IFF_TAP | IFF_NO_PI; strcpy(ifr.ifr_name, "tun%d"); if (ioctl(fd, TUNSETIFF, (void *) &ifr) != 0) { sprintf(str, GetString(STR_SHEEP_NET_ATTACH_WARN), strerror(errno)); WarningAlert(str); goto open_error; } // Get network config script file path net_if_script = PrefsFindString("etherconfig"); if (net_if_script == NULL) net_if_script = ETHERCONFIG_FILE_NAME; // Start network script up if (net_if_script == NULL) { sprintf(str, GetString(STR_TUN_TAP_CONFIG_WARN), "script not found"); WarningAlert(str); goto open_error; } net_if_name = strdup(ifr.ifr_name); if (!execute_network_script("up")) { sprintf(str, GetString(STR_TUN_TAP_CONFIG_WARN), "script execute error"); WarningAlert(str); goto open_error; } D(bug("Connected to host network interface: %s\n", net_if_name)); } #endif #if defined(__linux__) // Attach sheep_net to selected Ethernet card if (net_if_type == NET_IF_SHEEPNET && ioctl(fd, SIOCSIFLINK, name) < 0) { sprintf(str, GetString(STR_SHEEP_NET_ATTACH_WARN), strerror(errno)); WarningAlert(str); goto open_error; } #endif // Set nonblocking I/O ioctl(fd, FIONBIO, &nonblock); // Get Ethernet address if (net_if_type == NET_IF_ETHERTAP) { pid_t p = getpid(); // If configured for multicast, ethertap requires that the lower 32 bit of the Ethernet address are our PID ether_addr[0] = 0xfe; ether_addr[1] = 0xfd; ether_addr[2] = p >> 24; ether_addr[3] = p >> 16; ether_addr[4] = p >> 8; ether_addr[5] = p; } else ioctl(fd, SIOCGIFADDR, ether_addr); D(bug("Ethernet address %02x %02x %02x %02x %02x %02x\n", ether_addr[0], ether_addr[1], ether_addr[2], ether_addr[3], ether_addr[4], ether_addr[5])); // Start packet reception thread if (!start_thread()) goto open_error; // Everything OK return true; open_error: stop_thread(); if (fd > 0) { close(fd); fd = -1; } return false; } /* * Deinitialization */ void ether_exit(void) { // Stop reception thread if (thread_active) { pthread_cancel(ether_thread); pthread_join(ether_thread, NULL); sem_destroy(&int_ack); thread_active = false; } // Shut down TUN/TAP interface if (net_if_type == NET_IF_TUNTAP) execute_network_script("down"); // Free TUN/TAP device name if (net_if_name) free(net_if_name); // Close sheep_net device if (fd > 0) close(fd); } /* * Reset */ void ether_reset(void) { net_protocols.clear(); } /* * Add multicast address */ int16 ether_add_multicast(uint32 pb) { if (net_if_type != NET_IF_TUNTAP && ioctl(fd, SIOCADDMULTI, Mac2HostAddr(pb + eMultiAddr)) < 0) { D(bug("WARNING: Couldn't enable multicast address\n")); if (net_if_type == NET_IF_ETHERTAP) return noErr; else return eMultiErr; } else return noErr; } /* * Delete multicast address */ int16 ether_del_multicast(uint32 pb) { if (net_if_type != NET_IF_TUNTAP && ioctl(fd, SIOCDELMULTI, Mac2HostAddr(pb + eMultiAddr)) < 0) { D(bug("WARNING: Couldn't disable multicast address\n")); return eMultiErr; } else return noErr; } /* * Attach protocol handler */ int16 ether_attach_ph(uint16 type, uint32 handler) { if (net_protocols.find(type) != net_protocols.end()) return lapProtErr; net_protocols[type] = handler; return noErr; } /* * Detach protocol handler */ int16 ether_detach_ph(uint16 type) { if (net_protocols.erase(type) == 0) return lapProtErr; return noErr; } /* * Transmit raw ethernet packet */ int16 ether_write(uint32 wds) { // Copy packet to buffer uint8 packet[1516], *p = packet; int len = 0; #if defined(__linux__) if (net_if_type == NET_IF_ETHERTAP) { *p++ = 0; // Linux ethertap discards the first 2 bytes *p++ = 0; len += 2; } #endif len += ether_wds_to_buffer(wds, p); #if MONITOR bug("Sending Ethernet packet:\n"); for (int i=0; i