Re: [hatari-devel] Network support for Hatari |
[ Thread Index | Date Index | More lists.tuxfamily.org/hatari-devel Archives ]
Small update, because i just realized that LOG_TRACE() in Hatari needs a trailing newline.
PS.: while looking after the STinG drivers, i think there is a problem. AFAIK, TOS 2.x/TOS 3.x sets a default interrupt level of 3, which would permanently block the interrupt i'm using. Any idea how to solve that? |
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 90d2830f..577b8b03 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -74,11 +74,13 @@ endif(CapsImage_FOUND) link_directories(${CMAKE_CURRENT_BINARY_DIR}/debug ${CMAKE_CURRENT_BINARY_DIR}/falcon + ${CMAKE_CURRENT_BINARY_DIR}/ethernet ${CMAKE_CURRENT_BINARY_DIR}/gui-sdl ${CMAKE_CURRENT_BINARY_DIR}/cpu) add_subdirectory(debug) add_subdirectory(falcon) +add_subdirectory(ethernet) add_subdirectory(gui-sdl) add_subdirectory(cpu) @@ -153,7 +155,7 @@ if(SDL2_OTHER_CFLAGS) # message(STATUS "Additional CFLAGS of SDL: ${SDL2_OTHER_CFLAGS}") endif(SDL2_OTHER_CFLAGS) -target_link_libraries(hatari Falcon UaeCpu GuiSdl Floppy Debug ${SDL2_LIBRARY}) +target_link_libraries(hatari Falcon UaeCpu GuiSdl Floppy Debug Ethernet ${SDL2_LIBRARY}) if(Math_FOUND AND NOT APPLE) target_link_libraries(hatari ${MATH_LIBRARY}) diff --git a/src/configuration.c b/src/configuration.c index 15daca88..bb4a92d1 100644 --- a/src/configuration.c +++ b/src/configuration.c @@ -455,6 +455,18 @@ static const struct Config_Tag configs_Midi[] = { NULL , Error_Tag, NULL } }; +/* Used to load/save ETHERNET options */ +static const struct Config_Tag configs_Ethernet[] = +{ + { "sType0", String_Tag, ConfigureParams.Ethernet[0].type }, + { "sTunnel0", String_Tag, ConfigureParams.Ethernet[0].tunnel }, + { "sHostIP0", String_Tag, ConfigureParams.Ethernet[0].ip_host }, + { "sAtariIP0", String_Tag, ConfigureParams.Ethernet[0].ip_atari }, + { "sNetmask0", String_Tag, ConfigureParams.Ethernet[0].netmask }, + { "sMAC0", String_Tag, ConfigureParams.Ethernet[0].mac_addr }, + { NULL , Error_Tag, NULL } +}; + /* Used to load system options from old config files */ static int nOldMachineType; static bool bOldRealTimeClock; @@ -683,6 +695,25 @@ void Configuration_SetDefault(void) strcpy(ConfigureParams.Midi.sMidiInPortName, "Off"); strcpy(ConfigureParams.Midi.sMidiOutPortName, "Off"); + /* Set defaults for ETHERNET */ + /* ETH[0] with some default values */ + strcpy(ConfigureParams.Ethernet[0].type, "ptp"); + strcpy(ConfigureParams.Ethernet[0].tunnel, "tap0"); + strcpy(ConfigureParams.Ethernet[0].ip_host, "192.168.0.1"); + strcpy(ConfigureParams.Ethernet[0].ip_atari, "192.168.0.2"); + strcpy(ConfigureParams.Ethernet[0].netmask, "255.255.255.0"); + strcpy(ConfigureParams.Ethernet[0].mac_addr, "00:41:45:54:48:30"); + /* ETH[1] - ETH[MAX_ETH] are empty by default */ + for (i = 1; i < MAX_ETH; i++) + { + ConfigureParams.Ethernet[i].type[0] = '\0'; + ConfigureParams.Ethernet[i].tunnel[0] = '\0'; + ConfigureParams.Ethernet[i].ip_host[0] = '\0'; + ConfigureParams.Ethernet[i].ip_atari[0] = '\0'; + ConfigureParams.Ethernet[i].netmask[0] = '\0'; + ConfigureParams.Ethernet[i].mac_addr[0] = '\0'; + } + /* Set defaults for Screen */ ConfigureParams.Screen.bFullScreen = false; ConfigureParams.Screen.bKeepResolution = true; @@ -1018,6 +1049,7 @@ void Configuration_Load(const char *psFileName) Configuration_LoadSection(psFileName, configs_Rs232, "[RS232]"); Configuration_LoadSection(psFileName, configs_Printer, "[Printer]"); Configuration_LoadSection(psFileName, configs_Midi, "[Midi]"); + Configuration_LoadSection(psFileName, configs_Ethernet, "[Network]"); Configuration_LoadSection(psFileName, configs_System, "[System]"); Configuration_LoadSection(psFileName, configs_Video, "[Video]"); } @@ -1075,6 +1107,7 @@ void Configuration_Save(void) Configuration_SaveSection(sConfigFileName, configs_Rs232, "[RS232]"); Configuration_SaveSection(sConfigFileName, configs_Printer, "[Printer]"); Configuration_SaveSection(sConfigFileName, configs_Midi, "[Midi]"); + Configuration_SaveSection(sConfigFileName, configs_Ethernet, "[Network]"); Configuration_SaveSection(sConfigFileName, configs_System, "[System]"); Configuration_SaveSection(sConfigFileName, configs_Video, "[Video]"); } diff --git a/src/cpu/hatari-glue.c b/src/cpu/hatari-glue.c index 94dad131..d41b7970 100644 --- a/src/cpu/hatari-glue.c +++ b/src/cpu/hatari-glue.c @@ -80,8 +80,12 @@ int intlev(void) { if ( pendingInterrupts & (1 << 6) ) /* MFP/DSP interrupt ? */ return 6; + else if ( pendingInterrupts & (1 << 5) ) /* SCC interrupt ? */ + return 5; else if ( pendingInterrupts & (1 << 4) ) /* VBL interrupt ? */ return 4; + else if ( pendingInterrupts & (1 << 3) ) /* VME interrupt ? */ + return 3; else if ( pendingInterrupts & (1 << 2) ) /* HBL interrupt ? */ return 2; diff --git a/src/cpu/newcpu.c b/src/cpu/newcpu.c index 5f3f2f55..508d8d41 100644 --- a/src/cpu/newcpu.c +++ b/src/cpu/newcpu.c @@ -2834,7 +2834,7 @@ static int iack_cycle(int nr) pendingInterrupts &= ~( 1 << 6 ); } } - else if ( ( nr == 26 ) || ( nr == 28 ) ) /* HBL / VBL */ + else if ( ( nr == 26 ) || ( nr == 27 ) || ( nr == 28 ) || ( nr == 29 ) ) /* HBL / VBL / VME / SCC */ { if (cycle_exact) { diff --git a/src/debug/log.c b/src/debug/log.c index eb437479..226af451 100644 --- a/src/debug/log.c +++ b/src/debug/log.c @@ -148,6 +148,8 @@ static flagname_t TraceFlags[] = { { TRACE_SCSIDRV , "scsidrv" } , + { TRACE_ETHERNET , "ethernet" } , + { TRACE_MEM , "mem" } , { TRACE_VME , "vme" } , diff --git a/src/debug/log.h b/src/debug/log.h index 9be233f9..7db70297 100644 --- a/src/debug/log.h +++ b/src/debug/log.h @@ -184,6 +184,8 @@ extern char *Log_MatchTrace(const char *text, int state); #define TRACE_VME (1ll<<55) +#define TRACE_ETHERNET (1ll<<56) + #define TRACE_NONE (0) #define TRACE_ALL (~0) diff --git a/src/debug/natfeats.c b/src/debug/natfeats.c index 517ed1e9..93fa14a0 100644 --- a/src/debug/natfeats.c +++ b/src/debug/natfeats.c @@ -22,6 +22,7 @@ const char Natfeats_fileid[] = "Hatari natfeats.c"; #include "debugui.h" #include "statusbar.h" #include "nf_scsidrv.h" +#include "nf_ethernet.h" #include "log.h" /* maximum input string length */ @@ -286,9 +287,12 @@ static const struct { { "NF_SHUTDOWN", true, nf_shutdown }, { "NF_EXIT", false, nf_exit }, { "NF_DEBUGGER", false, nf_debugger }, - { "NF_FASTFORWARD", false, nf_fastforward } + { "NF_FASTFORWARD", false, nf_fastforward }, #if defined(__linux__) - ,{ "NF_SCSIDRV", true, nf_scsidrv } + { "NF_SCSIDRV", true, nf_scsidrv }, +#endif +#ifdef WITH_NF_ETHERNET + { "ETHERNET", false, nf_ethernet }, #endif }; diff --git a/src/ethernet/CMakeLists.txt b/src/ethernet/CMakeLists.txt new file mode 100644 index 00000000..adab63b9 --- /dev/null +++ b/src/ethernet/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(Ethernet + nf_ethernet.c ethernet_linux.c ethernet_darwin.c + ethernet_macosx.c ethernet_win32.c fd_trans.c) diff --git a/src/ethernet/ethernet.h b/src/ethernet/ethernet.h new file mode 100644 index 00000000..a54b64ee --- /dev/null +++ b/src/ethernet/ethernet.h @@ -0,0 +1,55 @@ +/* + * ethernet.h - Ethernet Card Emulation + * + * Copyright (c) 2007 ARAnyM dev team (see AUTHORS) + * + * This file is part of the ARAnyM project which builds a new and powerful + * TOS/FreeMiNT compatible virtual machine running on almost any hardware. + * + * ARAnyM 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. + * + * ARAnyM 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 ARAnyM. If not see <http://www.gnu.org/licenses/> + */ + +#include "nf_ethernet.h" + +#define MAX_PACKET_SIZE 9000 + +struct EthernetHandler { + int ethX; + ssize_t packet_length; + uint8_t packet[MAX_PACKET_SIZE+2]; + SDL_Thread *handlingThread; /* Packet reception thread */ + SDL_sem *intAck; /* Interrupt acknowledge semaphore */ + + bool (*open)(struct EthernetHandler *); + void (*close)(struct EthernetHandler *); + int (*recv)(struct EthernetHandler *, uint8_t *, int); + int (*send)(struct EthernetHandler *, const uint8_t *, int); +}; + +#if defined(__CYGWIN32__) || defined(__WIN32__) + struct EthernetHandler *WinTapEthernetHandler(int eth_idx); +# define ETHERNET_HANDLER_CLASSNAME WinTapEthernetHandler +#elif defined(__APPLE__) +# define ENABLE_BPF 1 +# if ENABLE_BPF + struct EthernetHandler *BPFEthernetHandler(int eth_idx); +# define ETHERNET_HANDLER_CLASSNAME BPFEthernetHandler +# else + struct EthernetHandler *TunTapEthernetHandler(int eth_idx); +# define ETHERNET_HANDLER_CLASSNAME TunTapEthernetHandler +# endif +#elif defined(__linux__) + struct EthernetHandler *TunTapEthernetHandler(int eth_idx); +# define ETHERNET_HANDLER_CLASSNAME TunTapEthernetHandler +#endif diff --git a/src/ethernet/ethernet_darwin.c b/src/ethernet/ethernet_darwin.c new file mode 100644 index 00000000..1a5ce1ef --- /dev/null +++ b/src/ethernet/ethernet_darwin.c @@ -0,0 +1,352 @@ +/* + * ethernet_darwin.c - Darwin Ethernet support (via TUN/TAP driver) + * + * Copyright (c) 2007 ARAnyM dev team (see AUTHORS) + * + * Inspired by Bernie Meyer's UAE-JIT and Gwenole Beauchesne's Basilisk II-JIT + * + * This file is part of the ARAnyM project which builds a new and powerful + * TOS/FreeMiNT compatible virtual machine running on almost any hardware. + * + * ARAnyM 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. + * + * ARAnyM 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 ARAnyM. If not see <http://www.gnu.org/licenses/> + * + */ + + +#include "config.h" +#include <SDL.h> +#include <SDL_thread.h> +#include "stMemory.h" +#include "log.h" +#include "gemdos_defines.h" +#include "ethernet.h" +#include "configuration.h" + +#if defined(WITH_NF_ETHERNET) && defined(__APPLE__) && !defined(ENABLE_BPF) + +#include "screen.h" /* bInFullScreen */ +#include "paths.h" + +#include <sys/wait.h> /* waitpid() */ +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <sys/socket.h> +#include <net/if.h> +#include <errno.h> + +#include <CoreFoundation/CoreFoundation.h> +#include <Security/Authorization.h> +#include <Security/AuthorizationTags.h> + +/**************************** + * Configuration zone begins + */ + +#define TAP_SHELL "/bin/sh" +#define TAP_INIT "aratapif.sh" +#define TAP_MTU "1500" + +/* + * Configuration zone ends + **************************/ + +struct TunTapEthernetHandler +{ + struct EthernetHandler base; + + int fd; +}; + + +static OSStatus authStatus; +static AuthorizationRef authorizationRef; +static bool fullscreen; + + +static int openAuthorizationContext(void) +{ + if (authorizationRef == NULL) + { + AuthorizationFlags authFlags = kAuthorizationFlagDefaults; /* 1 */ + + authStatus = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, /* 3 */ + authFlags, &authorizationRef); /* 4 */ + if (authStatus != errAuthorizationSuccess) + return authStatus; + + fullscreen = bInFullScreen; + if (fullscreen) + Screen_ReturnFromFullScreen(); + + AuthorizationItem authItems = { kAuthorizationRightExecute, 0, /* 5 */ + NULL, 0 + }; /* 6 */ + AuthorizationRights authRights = { 1, &authItems }; /* 7 */ + authFlags = kAuthorizationFlagDefaults | /* 8 */ + kAuthorizationFlagInteractionAllowed | /* 9 */ + kAuthorizationFlagPreAuthorize | /* 10 */ + kAuthorizationFlagExtendRights; /* 11 */ + authStatus = AuthorizationCopyRights(authorizationRef, &authRights, NULL, authFlags, NULL); /* 12 */ + } + return authStatus; +} + + +static void closeAuthorizationContext(void) +{ + if (authorizationRef) + { + AuthorizationFree(authorizationRef, kAuthorizationFlagDefaults); /* 17 */ + authorizationRef = NULL; + + if (fullscreen) + { + Screen_EnterFullScreen(); + } + } +} + + +static int executeScriptAsRoot(char *args[]) +{ + OSStatus execStatus; + char app[FILENAME_MAX]; + FILE *authCommunicationsPipe = NULL; + char execReadBuffer[128]; + AuthorizationFlags authFlags = kAuthorizationFlagDefaults; /* 13 */ + + /* + CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); + CFURLGetFileSystemRepresentation(url, true, (uint8_t) *)app, MAXPATHLEN); + printf("TunTap: Binary directory '%s'\n", app); + CFRelease(url); */ + strcpy(app, Paths_GetWorkingDir()); + strcat(app, "/"); + strcat(app, args[0]); + args[0] = app; + + execStatus = AuthorizationExecuteWithPrivileges /* 14 */ + (authorizationRef, TAP_SHELL, authFlags, args, /* 15 */ + &authCommunicationsPipe); /* 16 */ + + if (execStatus == errAuthorizationSuccess) + for (;;) + { + int bytesRead = read(fileno(authCommunicationsPipe), + execReadBuffer, sizeof(execReadBuffer)); + + if (bytesRead < 1) + break; + write(fileno(stdout), execReadBuffer, bytesRead); + } + + return execStatus; +} + + +/* + * Allocate ETHERNETDriver TAP device, returns opened fd. + * Stores dev name in the first arg(must be large enough). + */ +static int tapOpenOld(struct TunTapEthernetHandler *handler, char *dev) +{ + char tapname[sizeof(ConfigureParams.Ethernet[0].tunnel) + 5]; + int i; + int fd; + + if (*dev) + { + snprintf(tapname, sizeof(tapname), "/dev/%s", dev); + tapname[sizeof(tapname) - 1] = '\0'; + LOG_TRACE(TRACE_ETHERNET, "Ethernet: TunTap(%d): tapOpenOld %s\n", handler->base.ethX, tapname); + fd = open(tapname, O_RDWR); + if (fd > 0) + return fd; + } + + for (i = 0; i < 255; i++) + { + sprintf(tapname, "/dev/tap%d", i); + /* Open device */ + if ((fd = open(tapname, O_RDWR)) > 0) + { + sprintf(dev, "tap%d", i); + LOG_TRACE(TRACE_ETHERNET, "Ethernet: TunTap(%d): tapOpenOld %s\n", handler->base.ethX, dev); + return fd; + } + } + return -1; +} + + +static int tapOpen(struct TunTapEthernetHandler *handler, char *dev) +{ + return tapOpenOld(handler, dev); +} + + +static bool TunTapEthernetHandler_open(struct EthernetHandler *_handler) +{ + struct TunTapEthernetHandler *handler = (struct TunTapEthernetHandler *) _handler; + /* int nonblock = 1; */ + char *type = ConfigureParams.Ethernet[handler->base.ethX].type; + char *devName = ConfigureParams.Ethernet[handler->base.ethX].tunnel; + int auth; + bool failed; + + handler->base.close(&handler->base); + + if (strlen(type) == 0 || strcmp(ConfigureParams.Ethernet[handler->base.ethX].type, "none") == 0) + { + if (handler->base.ethX == MAX_ETH - 1) + closeAuthorizationContext(); + return false; + } + + /* if 'bridge' mode then we are done */ + if (strstr(ConfigureParams.Ethernet[handler->base.ethX].type, "bridge") != NULL) + { + LOG_TRACE(LOG_WARN, "Ethernet: TunTap(%d): Bridge mode currently not supported '%s'\n", handler->base.ethX, devName); + if (handler->base.ethX == MAX_ETH - 1) + closeAuthorizationContext(); + return false; + } + + /* get the tunnel nif name if provided */ + if (strlen(devName) == 0) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: TunTap(%d): tunnel name undefined\n", handler->base.ethX); + if (handler->base.ethX == MAX_ETH - 1) + closeAuthorizationContext(); + return false; + } + + LOG_TRACE(TRACE_ETHERNET, "Ethernet: TunTap(%d): open('%s')\n", handler->base.ethX, devName); + + handler->fd = tapOpen(handler, devName); + if (handler->fd < 0) + { + LOG_TRACE(LOG_WARN, "Ethernet: TunTap(%d): NO_NET_DRIVER_WARN '%s': %s\n", handler->base.ethX, devName, strerror(errno)); + if (handler->base.ethX == MAX_ETH - 1) + closeAuthorizationContext(); + return false; + } + auth = openAuthorizationContext(); + + if (auth) + { + handler->base.close(&handler->base); + LOG_TRACE(LOG_WARN, "Ethernet: TunTap(%d): Authorization failed'%s'\n", handler->base.ethX, devName); + return false; + } + + failed = true; + + { + /* the arguments _need_ to be placed into the child process */ + /* memory (otherwise this does not work here) */ + static char tap_mtu[] = TAP_MTU; + static char tap_init[] = TAP_INIT; + char *args[] = { + tap_init, + ConfigureParams.Ethernet[handler->base.ethX].tunnel, + ConfigureParams.Ethernet[handler->base.ethX].ip_host, + ConfigureParams.Ethernet[handler->base.ethX].ip_atari, + ConfigureParams.Ethernet[handler->base.ethX].netmask, + tap_mtu, NULL + }; + + int result = executeScriptAsRoot(args); + + if (result != 0) + { + LOG_TRACE(LOG_WARN, "Ethernet: TunTap(%d): ERROR: " TAP_INIT " failed (code %d). Ethernet disabled!\n", handler->base.ethX, result); + } else + { + failed = false; + LOG_TRACE(TRACE_ETHERNET, "Ethernet: TunTap(%d): " TAP_INIT " initialized OK\n", handler->base.ethX); + } + } + + /* Close /dev/net/tun device if exec failed */ + if (failed) + { + handler->base.close(&handler->base); + if (handler->base.ethX == MAX_ETH - 1) + closeAuthorizationContext(); + return false; + } + + /* Set nonblocking I/O */ + /*ioctl(handler->fd, FIONBIO, &nonblock); */ + + if (handler->base.ethX == MAX_ETH - 1) + closeAuthorizationContext(); + return true; +} + + +static void TunTapEthernetHandler_close(struct EthernetHandler *_handler) +{ + struct TunTapEthernetHandler *handler = (struct TunTapEthernetHandler *) _handler; + LOG_TRACE(TRACE_ETHERNET, "Ethernet: TunTap(%d): close\n", handler->base.ethX); + /* Close /dev/net/tun device */ + if (handler->fd > 0) + { + close(handler->fd); + handler->fd = -1; + } +} + + +static int TunTapEthernetHandler_recv(struct EthernetHandler *_handler, uint8_t *buf, int len) +{ + struct TunTapEthernetHandler *handler = (struct TunTapEthernetHandler *) _handler; + return read(handler->fd, buf, len); +} + + +static int TunTapEthernetHandler_send(struct EthernetHandler *_handler, const uint8_t *buf, int len) +{ + struct TunTapEthernetHandler *handler = (struct TunTapEthernetHandler *) _handler; + int res = write(handler->fd, buf, len); + + if (res < 0) + LOG_TRACE(TRACE_ETHERNET, "Ethernet: TunTap(%d): WARNING: Couldn't transmit packet\n", handler->base.ethX); + return res; +} + + +struct EthernetHandler *TunTapEthernetHandler(int eth_idx) +{ + struct TunTapEthernetHandler *handler = (struct TunTapEthernetHandler *) malloc(sizeof(*handler)); + + handler->base.ethX = eth_idx; + handler->base.packet_length = 0; + handler->base.handlingThread = NULL; + handler->base.intAck = NULL; + + handler->base.open = TunTapEthernetHandler_open; + handler->base.close = TunTapEthernetHandler_close; + handler->base.recv = TunTapEthernetHandler_recv; + handler->base.send = TunTapEthernetHandler_send; + + handler->fd = -1; + + return &handler->base; +} + + +#endif /* WITH_NF_ETHERNET */ diff --git a/src/ethernet/ethernet_linux.c b/src/ethernet/ethernet_linux.c new file mode 100644 index 00000000..c1c60f11 --- /dev/null +++ b/src/ethernet/ethernet_linux.c @@ -0,0 +1,335 @@ +/* + * ethernet_linux.c - Linux Ethernet support (via TUN/TAP driver) + * + * Copyright (c) 2007 ARAnyM dev team (see AUTHORS) + * + * This file is part of the ARAnyM project which builds a new and powerful + * TOS/FreeMiNT compatible virtual machine running on almost any hardware. + * + * ARAnyM 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. + * + * ARAnyM 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 ARAnyM. If not see <http://www.gnu.org/licenses/> + * + */ + +#include "config.h" +#include <SDL.h> +#include <SDL_thread.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include "stMemory.h" +#include "log.h" +#include "gemdos_defines.h" +#include "ethernet.h" +#include "configuration.h" + +#if defined(WITH_NF_ETHERNET) && defined (__linux__) + +#include <sys/poll.h> +#include <sys/wait.h> /* waitpid() */ +#include <sys/ioctl.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/if.h> + +#define HAVE_LINUX_IF_H +#define HAVE_LINUX_IF_TUN_H +#define HAVE_SYS_SOCKET_H + +#if defined(HAVE_LINUX_IF_H) && defined(HAVE_LINUX_IF_TUN_H) +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#include <linux/if.h> +#include <linux/if_tun.h> +#endif +#if defined(HAVE_NET_IF_TUN_H) +#include <net/if_tun.h> +#endif + +#include <errno.h> + +/**************************** + * Configuration zone begins + */ + +#define TAP_INIT "aratapif" +#define TAP_MTU "1500" + +/* + * Configuration zone ends + **************************/ + + +struct TunTapEthernetHandler { + struct EthernetHandler base; + + int fd; +}; + + +/* + * Allocate ETHERNETDriver TAP device, returns opened fd. + * Stores dev name in the first arg(must be large enough). + */ +static int tapOpenOld(struct TunTapEthernetHandler *handler, char *dev) +{ + char tapname[sizeof(ConfigureParams.Ethernet[0].tunnel) + 5]; + int i, fd; + + if (*dev) + { + snprintf(tapname, sizeof(tapname), "/dev/%s", dev); + tapname[sizeof(tapname) - 1] = '\0'; + LOG_TRACE(TRACE_ETHERNET, "Ethernet: TunTap(%d): tapOpenOld %s\n", handler->base.ethX, tapname); + return open(tapname, O_RDWR); + } + + for (i = 0; i < 255; i++) + { + sprintf(tapname, "/dev/tap%d", i); + /* Open device */ + if ((fd = open(tapname, O_RDWR)) > 0) + { + sprintf(dev, "tap%d", i); + LOG_TRACE(TRACE_ETHERNET, "Ethernet: TunTap(%d): tapOpenOld %s\n", handler->base.ethX, dev); + return fd; + } + } + return -1; +} + + +#ifdef HAVE_LINUX_IF_TUN_H /* New driver support */ + +/* pre 2.4.6 compatibility */ +#define OTUNSETNOCSUM (('T'<< 8) | 200) +#define OTUNSETDEBUG (('T'<< 8) | 201) +#define OTUNSETIFF (('T'<< 8) | 202) +#define OTUNSETPERSIST (('T'<< 8) | 203) +#define OTUNSETOWNER (('T'<< 8) | 204) + +static int tapOpen(struct TunTapEthernetHandler *handler, char *dev) +{ + struct ifreq ifr; + + if ((handler->fd = open("/dev/net/tun", O_RDWR)) < 0) + { + LOG_TRACE(LOG_WARN, "Ethernet: TunTap(%d): Error opening /dev/net/tun. Check if module is loaded and privileges are OK\n", handler->base.ethX); + return tapOpenOld(handler, dev); + } + + memset(&ifr, 0, sizeof(ifr)); + + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + if (*dev) + { + int len = strlen(dev); + if (len >= IFNAMSIZ) + len = IFNAMSIZ - 1; + memcpy(ifr.ifr_name, dev, len); + ifr.ifr_name[len] = '\0'; + } + + if (ioctl(handler->fd, TUNSETIFF, (void *) &ifr) < 0) + { + if (errno != EBADFD) + goto failed; + + /* Try old ioctl */ + if (ioctl(handler->fd, OTUNSETIFF, (void *) &ifr) < 0) + goto failed; + } + + strcpy(dev, ifr.ifr_name); + + LOG_TRACE(TRACE_ETHERNET, "Ethernet: TunTap(%d): if opened %s\n", handler->base.ethX, dev); + return handler->fd; + + failed: + handler->base.close(&handler->base); + return -1; +} + +#else + +static int tapOpen(struct TunTapEthernetHandler *handler, char *dev) +{ + return tapOpenOld(handler, dev); +} + +#endif /* New driver support */ + + +static bool TunTapEthernetHandler_open(struct EthernetHandler *_handler) +{ + struct TunTapEthernetHandler *handler = (struct TunTapEthernetHandler *) _handler; + int pid; + int status; + bool failed; + + /* int nonblock = 1; */ + char *type = ConfigureParams.Ethernet[handler->base.ethX].type; + char *devName = ConfigureParams.Ethernet[handler->base.ethX].tunnel; + + handler->base.close(&handler->base); + + if (strcmp(type, "none") == 0 || strlen(type) == 0) + { + return false; + } + + /* get the tunnel nif name if provided */ + if (strlen(devName) == 0) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: TunTap(%d): tunnel name undefined\n", handler->base.ethX); + return false; + } + + LOG_TRACE(TRACE_ETHERNET, "Ethernet: TunTap(%d): open('%s')\n", handler->base.ethX, devName); + + handler->fd = tapOpen(handler, devName); + if (handler->fd < 0) + { + LOG_TRACE(LOG_WARN, "Ethernet: TunTap(%d): NO_NET_DRIVER_WARN '%s': %s\n", handler->base.ethX, devName, strerror(errno)); + return false; + } + + /* if 'bridge' mode then we are done */ + if (strstr(ConfigureParams.Ethernet[handler->base.ethX].type, "bridge") != NULL) + return true; + + pid = fork(); + + if (pid < 0) + { + LOG_TRACE(LOG_WARN, "Ethernet: TunTap(%d): ERROR: fork() failed. Ethernet disabled!\n", handler->base.ethX); + handler->base.close(&handler->base); + return false; + } + + if (pid == 0) + { + /* the arguments _need_ to be placed into the child process */ + /* memory (otherwise this does not work here) */ + static char tap_init[] = TAP_INIT; + static char tap_mtu[] = TAP_MTU; + char *args[] = { + tap_init, + ConfigureParams.Ethernet[handler->base.ethX].tunnel, + ConfigureParams.Ethernet[handler->base.ethX].ip_host, + ConfigureParams.Ethernet[handler->base.ethX].ip_atari, + ConfigureParams.Ethernet[handler->base.ethX].netmask, + tap_mtu, NULL + }; + int result; + + result = execvp(TAP_INIT, args); + _exit(result); + } + + LOG_TRACE(TRACE_ETHERNET, "Ethernet: TunTap(%d): waiting for " TAP_INIT " at pid %d\n", handler->base.ethX, pid); + + waitpid(pid, &status, 0); + failed = true; + + if (WIFEXITED(status)) + { + int err = WEXITSTATUS(status); + + if (err == 255) + { + LOG_TRACE(LOG_WARN, "Ethernet: TunTap(%d): ERROR: " TAP_INIT " not found. Ethernet disabled!\n", handler->base.ethX); + } else if (err != 0) + { + LOG_TRACE(LOG_WARN, "Ethernet: TunTap(%d): ERROR: " TAP_INIT " failed (code %d). Ethernet disabled!\n", handler->base.ethX, err); + } else + { + failed = false; + LOG_TRACE(TRACE_ETHERNET, "Ethernet: TunTap(%d): " TAP_INIT " initialized OK\n", handler->base.ethX); + } + } else + { + LOG_TRACE(LOG_WARN, "Ethernet: TunTap(%d): ERROR: " TAP_INIT " could not be started. Ethernet disabled!\n", handler->base.ethX); + } + + /* Close /dev/net/tun device if exec failed */ + if (failed) + { + handler->base.close(&handler->base); + return false; + } + + /* Set nonblocking I/O */ + /* ioctl(handler->fd, FIONBIO, &nonblock); */ + + return true; +} + + +static void TunTapEthernetHandler_close(struct EthernetHandler *_handler) +{ + struct TunTapEthernetHandler *handler = (struct TunTapEthernetHandler *) _handler; + + LOG_TRACE(TRACE_ETHERNET, "Ethernet: TunTap(%d): close\n", handler->base.ethX); + + /* Close /dev/net/tun device */ + if (handler->fd > 0) + { + close(handler->fd); + handler->fd = -1; + } +} + + +static int TunTapEthernetHandler_recv(struct EthernetHandler *_handler, uint8_t *buf, int len) +{ + struct TunTapEthernetHandler *handler = (struct TunTapEthernetHandler *) _handler; + + return read(handler->fd, buf, len); +} + + +static int TunTapEthernetHandler_send(struct EthernetHandler *_handler, const uint8_t *buf, int len) +{ + struct TunTapEthernetHandler *handler = (struct TunTapEthernetHandler *) _handler; + int res = write(handler->fd, buf, len); + + if (res < 0) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: TunTap(%d): WARNING: Couldn't transmit packet\n", handler->base.ethX); + } + return res; +} + + +struct EthernetHandler *TunTapEthernetHandler(int eth_idx) +{ + struct TunTapEthernetHandler *handler = (struct TunTapEthernetHandler *) malloc(sizeof(*handler)); + + handler->base.ethX = eth_idx; + handler->base.packet_length = 0; + handler->base.handlingThread = NULL; + handler->base.intAck = NULL; + + handler->base.open = TunTapEthernetHandler_open; + handler->base.close = TunTapEthernetHandler_close; + handler->base.recv = TunTapEthernetHandler_recv; + handler->base.send = TunTapEthernetHandler_send; + + handler->fd = -1; + + return &handler->base; +} + +#endif /* WITH_NF_ETHERNET */ diff --git a/src/ethernet/ethernet_macosx.c b/src/ethernet/ethernet_macosx.c new file mode 100644 index 00000000..d7045e57 --- /dev/null +++ b/src/ethernet/ethernet_macosx.c @@ -0,0 +1,688 @@ +/* + * ethernet_macosx.c - Mac OS X Ethernet support (via Berkley Packet Filter device) + * + * Copyright (c) 2007 ARAnyM dev team (see AUTHORS) + * + * This file is part of the ARAnyM project which builds a new and powerful + * TOS/FreeMiNT compatible virtual machine running on almost any hardware. + * + * ARAnyM 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. + * + * ARAnyM 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 ARAnyM. If not see <http://www.gnu.org/licenses/> + */ + + +#include "config.h" +#include <SDL.h> +#include <SDL_thread.h> +#include "stMemory.h" +#include "log.h" +#include "gemdos_defines.h" +#include "ethernet.h" +#include "configuration.h" + +#if defined(WITH_NF_ETHERNET) && defined(__APPLE__) && defined(ENABLE_BPF) + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/ioctl.h> + +#include <sys/fcntl.h> +#include <sys/select.h> +#include <sys/errno.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <net/bpf.h> + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <CoreFoundation/CoreFoundation.h> + +#include "fd_trans.h" + +#define ETH_HELPER "bpf_helper" + +struct BPFEthernetHandler +{ + struct EthernetHandler base; + + bool debug; + int fd; + int buf_len; + struct bpf_hdr *bpf_buf; + + /* variables used for looping over multiple packets */ + int read_len; + struct bpf_hdr *bpf_packet; +}; + +/* BPF filter program to ensure only ethernet packets are processed which are for configured MAC */ +static struct bpf_insn bpf_filter_mac_insn[] = { + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 0), /* ld P[0:4] */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xAAAAAAAA, 0, 3), /* jeq destMAC, CONT, NOK */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 4), /* CONT:ld P[4:2] */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xAAAA, 0, 1), /* jeq destMAC, OK, NOK */ + BPF_STMT(BPF_RET + BPF_K, (u_int) -1), /* OK: ret -1 */ + BPF_STMT(BPF_RET + BPF_K, 0), /* NOK: ret 0 */ +}; + +static struct bpf_program bpf_filter_mac = { sizeof(bpf_filter_mac_insn) / sizeof(struct bpf_insn), &bpf_filter_mac_insn[0] }; + +/* BPF filter program to process multicast ethernet and configured MAC address packets */ +static struct bpf_insn bpf_filter_mcast_insn[] = { + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 0), /* ld P[0:4] */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xAAAAAAAA, 0, 2), /* jeq destMAC, CONT, NOK1 */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 4), /* CONT:ld P[4:2] */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xAAAA, 2, 0), /* jeq destMAC, OK, NOK1 */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 0), /* NOK1:ld P[0:1] */ + BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, 0x01, 0, 1), /* if (A & 1) OK else NOK2 */ + BPF_STMT(BPF_RET + BPF_K, (u_int) -1), /* OK: ret -1 */ + BPF_STMT(BPF_RET + BPF_K, 0), /* NOK2:ret 0 */ +}; + +static struct bpf_program bpf_filter_mcast = { sizeof(bpf_filter_mcast_insn) / sizeof(struct bpf_insn), &bpf_filter_mcast_insn[0] }; + +/* BPF filter program to process ethernet packets if the configured IP address matches */ +static struct bpf_insn bpf_filter_ip_insn[] = { + /* Check ethernet protocol (IP and ARP supported) */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), /* ld P[12:2] */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0800, 3, 0), /* jeq IP_Type, CONT_IP, CONT */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0806, 0, 5), /* CONT: jeq IP_Type, CONT_ARP, NOK */ + /* Load IP address from offset 38 */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 38), /* CONT_ARP:ld P[38:4] */ + BPF_JUMP(BPF_JMP + BPF_JA, 1, 1, 1), /* jmp COMPARE */ + /* Load IP address from offset 30 */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 30), /* CONT_IP: ld P[30:4] */ + /* Compare value with expected dest IP address */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xAAAAAAAA, 0, 1), /* COMPARE: jeq destIP, OK, NOK */ + BPF_STMT(BPF_RET + BPF_K, (u_int) -1), /* OK: ret -1 */ + BPF_STMT(BPF_RET + BPF_K, 0), /* NOK: ret 0 */ +}; + +static struct bpf_program bpf_filter_ip = { sizeof(bpf_filter_ip_insn) / sizeof(struct bpf_insn), &bpf_filter_ip_insn[0] }; + + +/* Ethernet frame */ +struct ethernet_frame +{ + unsigned char dst_addr[6]; + unsigned char src_addr[6]; + unsigned char type[2]; + unsigned char payload; +}; + +/* ARP packet structure */ +struct arp_packet +{ + unsigned char hardware_type[2]; + unsigned char proto_type[2]; + unsigned char hardware_addr_length; + unsigned char proto_addr_length; + unsigned char opcode[2]; + unsigned char src_mac[6]; + unsigned char src_ip[4]; + unsigned char dst_mac[6]; + unsigned char dst_ip[4]; +}; + +/* IP packet structure */ +struct ip_packet +{ + unsigned char version_hdr_len; + unsigned char services; + unsigned char tot_len[2]; + unsigned char ident[2]; + unsigned char flags_frag_offset[2]; + unsigned char ttl; + unsigned char proto_type; + unsigned char hdr_chksum[2]; + unsigned char src_ip[4]; + unsigned char dst_ip[4]; + unsigned char data; +}; + +static void dump_frame(const char *prefix, const struct ethernet_frame *frame) +{ + unsigned short int frame_type = (frame->type[0] << 8) | frame->type[1]; + + LOG_TRACE(TRACE_ETHERNET, " %s: %02x:%02x:%02x:%02x:%02x:%02x > %02x:%02x:%02x:%02x:%02x:%02x (Type %04x)\n", prefix, + frame->src_addr[0], frame->src_addr[1], frame->src_addr[2], frame->src_addr[3], frame->src_addr[4], + frame->src_addr[5], frame->dst_addr[0], frame->dst_addr[1], frame->dst_addr[2], frame->dst_addr[3], + frame->dst_addr[4], frame->dst_addr[5], frame_type); + if (frame_type == 0x0806) /* TYPE_ARP */ + { + const struct arp_packet *arp = (const struct arp_packet *) &frame->payload; + + LOG_TRACE(TRACE_ETHERNET, " %s: ARP packet %d.%d.%d.%d > %d.%d.%d.%d (Type %04x)\n", prefix, + arp->src_ip[0], arp->src_ip[1], arp->src_ip[2], arp->src_ip[3], + arp->dst_ip[0], arp->dst_ip[1], arp->dst_ip[2], arp->dst_ip[3], + (arp->proto_type[0] << 8) | arp->proto_type[1]); + } else if (frame_type == 0x0800) /* TYPE_IP */ + { + const struct ip_packet *ip = (const struct ip_packet *) &frame->payload; + int tot_len = (ip->tot_len[0] << 8) + ip->tot_len[1]; + + LOG_TRACE(TRACE_ETHERNET, " %s: IP packet %d.%d.%d.%d > %d.%d.%d.%d (Type %04x Total %02x%02x = %d, Payload=%d)\n", prefix, + ip->src_ip[0], ip->src_ip[1], ip->src_ip[2], ip->src_ip[3], + ip->dst_ip[0], ip->dst_ip[1], ip->dst_ip[2], ip->dst_ip[3], + ip->proto_type, ip->tot_len[0], ip->tot_len[1], tot_len, tot_len - (int) sizeof(struct ip_packet) + 1); + +#if 0 + for (unsigned char *ptr=&ip->data; ptr<(unsigned char*)&ip->data+tot_len; ptr++) + LOG_TRACE(TRACE_ETHERNET, ">%*s\n", tot_len-(sizeof(struct ip_packet)-1), &ip->data); +#endif + } +} + +static void dump_bpf_buf(const char *prefix, struct bpf_hdr *bpf_buf) +{ + if (bpf_buf->bh_datalen > 0) + { + LOG_TRACE(TRACE_ETHERNET, " %s: %d bytes in buf:\n", prefix, bpf_buf->bh_datalen); + + dump_frame(prefix, (const struct ethernet_frame *) ((char *) bpf_buf + bpf_buf->bh_hdrlen)); + } +} + + +static void reset_read_pos(struct BPFEthernetHandler *handler) +{ + handler->read_len = 0; + handler->bpf_packet = NULL; +} + + +static bool BPFEthernetHandler_open(struct EthernetHandler *_handler) +{ + struct BPFEthernetHandler *handler = (struct BPFEthernetHandler *) _handler; + /* int nonblock = 1; */ + char *type = ConfigureParams.Ethernet[handler->base.ethX].type; + char *dev_name = ConfigureParams.Ethernet[handler->base.ethX].tunnel; + int sockfd; + char exe_path[2048]; + CFURLRef url; + CFURLRef url2; + pid_t pid; + struct ifreq ifr; + int immediate; + int promiscuous; + int complete; + int seesent; + int len; + + handler->base.close(&handler->base); + + if (strcmp(type, "none") == 0 || strlen(type) == 0) + { + return false; + } + + handler->debug = strstr(type, "debug") != NULL; + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): debug mode=%d\n", handler->base.ethX, handler->debug); + } + + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): open() type=%s\n", handler->base.ethX, type); + } + if (strstr(type, "bridge") == NULL) + { + LOG_TRACE(LOG_WARN, "Ethernet: BPF(%d): unsupported type '%s'\n", handler->base.ethX, type); + return false; + } + + if ((sockfd = fd_setup_server()) < 0) + { + fprintf(stderr, "receiver: failed to create socket.\n"); + return false; + } + + /****************************************************** + Fork child process to get an open BPF file descriptor + ******************************************************/ + url = CFBundleCopyExecutableURL(CFBundleGetMainBundle()); + url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); + + CFURLGetFileSystemRepresentation(url2, true, (uint8_t *) exe_path, sizeof(exe_path)); + CFRelease(url2); + CFRelease(url); + len = (int)strlen(exe_path); + if (len > 0 && exe_path[len - 1] != '/') + strcat(exe_path, "/"); + strcat(exe_path, ETH_HELPER); + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): starting helper from <%s>\n", handler->base.ethX, exe_path); + } + + pid = fork(); + + if (pid < 0) + { + LOG_TRACE(LOG_WARN, "Ethernet: BPF(%d): ERROR: fork() failed. Ethernet disabled!\n", handler->base.ethX); + close(sockfd); + return false; + } else if (pid == 0) + { + int result; + + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): " ETH_HELPER " child running\n", handler->base.ethX); + } + result = execl(exe_path, exe_path, NULL); + + _exit(result); + } + + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): waiting for " ETH_HELPER " (PID %d) to send file descriptor\n", + handler->base.ethX, pid); + } + handler->fd = fd_receive(sockfd, pid); + close(sockfd); + if (handler->fd < 0) + { + LOG_TRACE(LOG_WARN, "Ethernet: BPF(%d): failed receiving file descriptor from " ETH_HELPER ".\n", handler->base.ethX); + return false; + } + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): got file descriptor %d\n", handler->base.ethX, handler->fd); + } + + + /****************************************************** + Configure BPF device + ******************************************************/ + /* associate with specified interface */ + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, dev_name, IFNAMSIZ - 1); + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): connecting with device %s\n", handler->base.ethX, dev_name); + } + if (ioctl(handler->fd, BIOCSETIF, &ifr) > 0) + { + LOG_TRACE(LOG_WARN, "Ethernet: BPF(%d): Failed associating to %s: %s\n", handler->base.ethX, dev_name, strerror(errno)); + handler->base.close(&handler->base); + return false; + } + + /* activate immediate mode */ + immediate = 1; + + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): enabling immediate mode\n", handler->base.ethX); + } + if (ioctl(handler->fd, BIOCIMMEDIATE, &immediate) == -1) + { + LOG_TRACE(LOG_WARN, "Ethernet: BPF(%d): Unable to set immediate mode: %s\n", handler->base.ethX, strerror(errno)); + handler->base.close(&handler->base); + return false; + } + + /* request buffer length */ + if (ioctl(handler->fd, BIOCGBLEN, &handler->buf_len) == -1) + { + LOG_TRACE(LOG_WARN, "Ethernet: BPF(%d): Unable to get buffer length: %s\n", handler->base.ethX, strerror(errno)); + handler->base.close(&handler->base); + return false; + } + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): buf_len=%d\n", handler->base.ethX, handler->buf_len); + } + handler->bpf_buf = (struct bpf_hdr *) malloc(handler->buf_len); + + /* activate promiscuous mode */ + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): enabling promiscuous mode\n", handler->base.ethX); + } + promiscuous = 1; + if (ioctl(handler->fd, BIOCPROMISC, &promiscuous) == -1) + { + LOG_TRACE(LOG_WARN, "Ethernet: BPF(%d): Unable to set promiscuous mode: %s\n", handler->base.ethX, strerror(errno)); +#if 0 + handler->base.close(&handler->base); + return false; +#endif + } + + /* activate "header complete" mode */ + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): enabling header complete mode\n", handler->base.ethX); + } + complete = 1; + if (ioctl(handler->fd, BIOCGHDRCMPLT, &complete) == -1) + { + LOG_TRACE(LOG_WARN, "Ethernet: BPF(%d): Unable to set header complete mode: %s\n", handler->base.ethX, strerror(errno)); + handler->base.close(&handler->base); + return false; + } + + /* disable "see sent" mode */ + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): disabling see sent mode\n", handler->base.ethX); + } + seesent = 1; + if (ioctl(handler->fd, BIOCSSEESENT, &seesent) == -1) + { + LOG_TRACE(LOG_WARN, "Ethernet: BPF(%d): Unable to disable see sent mode: %s\n", handler->base.ethX, strerror(errno)); + handler->base.close(&handler->base); + return false; + } + + if (strstr(type, "nofilter") == NULL) + { + /* convert and validate MAC address from configuration */ + /* default MAC Address is just made up */ + uint8_t mac_addr[6] = { '\0', 'A', 'E', 'T', 'H', 0 }; + + /* convert user-defined MAC Address from string to 6 bytes array */ + char *mac_text = ConfigureParams.Ethernet[handler->base.ethX].mac_addr; + bool format_OK = false; + uint8_t ip_addr[4]; + struct bpf_program *filter; + + mac_addr[5] = handler->base.ethX + '0'; + if (strlen(mac_text) == 2 * 6 + 5 && (mac_text[2] == ':' || mac_text[2] == '-')) + { + int matched; + int md[6] = { 0, 0, 0, 0, 0, 0 }; + + mac_text[2] = mac_text[5] = mac_text[8] = mac_text[11] = mac_text[14] = ':'; + matched = sscanf(mac_text, "%02x:%02x:%02x:%02x:%02x:%02x", + &md[0], &md[1], &md[2], &md[3], &md[4], &md[5]); + + if (matched == 6) + { + int i; + + for (i = 0; i < 6; i++) + mac_addr[i] = md[i]; + format_OK = true; + } + } + if (!format_OK) + { + LOG_TRACE(LOG_WARN, "Ethernet: BPF(%d): Invalid MAC address: %s\n", handler->base.ethX, mac_text); + handler->base.close(&handler->base); + return false; + } + + /* convert and validate specified IP address */ + + if (!inet_pton(AF_INET, ConfigureParams.Ethernet[handler->base.ethX].ip_atari, ip_addr)) + { + LOG_TRACE(LOG_WARN, "Ethernet: BPF(%d): Invalid IP address specified: %s\n", handler->base.ethX, + ConfigureParams.Ethernet[handler->base.ethX].ip_atari); + handler->base.close(&handler->base); + return false; + } + + /* modify filter program to use specified IP address */ + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): setting filter program for MAC address %s\n", handler->base.ethX, + mac_text); + } + + /* Select filter program according to chosen options */ + + if (strstr(type, "mcast")) + { + filter = &bpf_filter_mcast; + + /* patch filter instructions to detect valid MAC address */ + filter->bf_insns[1].k = (mac_addr[0] << 24) | (mac_addr[1] << 16) | (mac_addr[2] << 8) | mac_addr[3]; + filter->bf_insns[3].k = (mac_addr[4] << 8) | mac_addr[5]; + } else if (strstr(type, "ip")) + { + filter = &bpf_filter_ip; + + /* patch filter instructions to detect valid IP address */ + filter->bf_insns[6].k = (ip_addr[0] << 24) | (ip_addr[1] << 16) | (ip_addr[2] << 8) | ip_addr[3]; + + } else + { + filter = &bpf_filter_mac; + /* patch filter instructions to detect valid MAC address */ + filter->bf_insns[1].k = (mac_addr[0] << 24) | (mac_addr[1] << 16) | (mac_addr[2] << 8) | mac_addr[3]; + filter->bf_insns[3].k = (mac_addr[4] << 8) | mac_addr[5]; + } + + /* Enable filter program */ + if (ioctl(handler->fd, BIOCSETF, filter) == -1) + { + LOG_TRACE(LOG_WARN, "Ethernet: BPF(%d): Unable to load filter program: %s\n", handler->base.ethX, strerror(errno)); + handler->base.close(&handler->base); + return false; + } + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): filter program load\n", handler->base.ethX); + } + } else + { + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): filter program skipped\n", handler->base.ethX); + } + } + + + /* Reset bpf buffer read position (for handling multi packet) */ + reset_read_pos(handler); + + return true; +} + + +static void BPFEthernetHandler_close(struct EthernetHandler *_handler) +{ + struct BPFEthernetHandler *handler = (struct BPFEthernetHandler *) _handler; + + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): close\n", handler->base.ethX); + } + + reset_read_pos(handler); + + if (handler->fd > 0) + { + close(handler->fd); + handler->fd = -1; + } + + free(handler->bpf_buf); + handler->bpf_buf = NULL; + + handler->debug = false; +} + + +static int BPFEthernetHandler_recv(struct EthernetHandler *_handler, uint8_t *buf, int len) +{ + struct BPFEthernetHandler *handler = (struct BPFEthernetHandler *) _handler; + char *ptr; + + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): recv(len=%d)\n", handler->base.ethX, len); + } + + /* No more cached packet in memory? */ + if (handler->bpf_packet == NULL) + { + /* Read a packet from the ethernet device, timeout after 2s */ + /* Initialize the file descriptor set. */ + fd_set set; + struct timeval timeout; + + FD_ZERO(&set); + FD_SET(handler->fd, &set); + + /* Initialize the timeout data structure. */ + timeout.tv_sec = 2; + timeout.tv_usec = 0; + + /* select returns 0 if timeout, 1 if input available, -1 if error. */ + if (select(FD_SETSIZE, &set, NULL, NULL, &timeout) == 1) + { + /* Read from BPF device */ + handler->read_len = read(handler->fd, handler->bpf_buf, handler->buf_len); + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): %d bytes read => %d data bytes\n", handler->base.ethX, handler->read_len, + handler->bpf_buf->bh_datalen); + } + if (handler->read_len > 0) + { + handler->bpf_packet = handler->bpf_buf; + + if ((int)BPF_WORDALIGN(handler->bpf_buf->bh_hdrlen + handler->bpf_buf->bh_caplen) < handler->read_len) + { + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): More than one packet received by BPF\n" + " read_len = %d\n" + " bh_hdrlen=%d\n" + " bh_datalen=%d\n" + " bh_caplen=%d\n", handler->base.ethX, handler->read_len, handler->bpf_buf->bh_hdrlen, + handler->bpf_buf->bh_datalen, handler->bpf_buf->bh_caplen); + } + } + } + } + } else + { + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): using cached %d data bytes from previous read\n", handler->base.ethX, + handler->read_len); + } + } + + ptr = (char *) handler->bpf_packet; + + if (handler->bpf_packet && (ptr < ((char *) handler->bpf_buf + handler->read_len))) + { + char *frame_start = ptr + handler->bpf_packet->bh_hdrlen; + int frame_len = handler->bpf_packet->bh_caplen; + + ptr += BPF_WORDALIGN(handler->bpf_packet->bh_hdrlen + handler->bpf_packet->bh_caplen); + + /* Copy valid frame data */ + if (frame_len <= len) + { + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): frame length %d bytes\n", handler->base.ethX, frame_len); + dump_bpf_buf("recv", handler->bpf_packet); + } + memcpy(buf, frame_start, frame_len); + } else + { + + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): Host side received %d bytes of data but only %d bytes expected by guest.\n" + "There are probably multiple packets in the received %d bytes.\n" + "Packet discarded!\n", handler->base.ethX, frame_len, len, handler->read_len); + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): read_len = %d\n" + " bh_hdrlen=%d\n" + " bh_datalen=%d\n" + " bh_caplen=%d\n", handler->base.ethX, handler->read_len, handler->bpf_packet->bh_hdrlen, handler->bpf_packet->bh_datalen, + handler->bpf_packet->bh_caplen); + frame_len = 0; + } + + if (ptr < ((char *) handler->bpf_buf + handler->read_len)) + handler->bpf_packet = (struct bpf_hdr *) ptr; + else + handler->bpf_packet = NULL; + + return frame_len; + } + return 0; +} + + +static int BPFEthernetHandler_send(struct EthernetHandler *_handler, const uint8_t *buf, int len) +{ + struct BPFEthernetHandler *handler = (struct BPFEthernetHandler *) _handler; + int res = -1; + + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): send(len=%d)\n", handler->base.ethX, len); + } + + if (len > 0) + { + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): send(len=%d)\n", handler->base.ethX, len); + dump_frame("send", (const struct ethernet_frame *) buf); + } + res = write(handler->fd, buf, len); + if (res < 0) + { + if (handler->debug) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: BPF(%d): WARNING: Couldn't transmit packet\n", handler->base.ethX); + } + } + } + return res; +} + + + +struct EthernetHandler *BPFEthernetHandler(int eth_idx) +{ + struct BPFEthernetHandler *handler = (struct BPFEthernetHandler *) malloc(sizeof(*handler)); + + handler->base.ethX = eth_idx; + handler->base.packet_length = 0; + handler->base.handlingThread = NULL; + handler->base.intAck = NULL; + + handler->base.open = BPFEthernetHandler_open; + handler->base.close = BPFEthernetHandler_close; + handler->base.recv = BPFEthernetHandler_recv; + handler->base.send = BPFEthernetHandler_send; + + handler->debug = false; + handler->fd = -1; + handler->buf_len = 0; + handler->bpf_buf = NULL; + handler->read_len = 0; + handler->bpf_packet = NULL; + + return &handler->base; +} + +#endif /* WITH_NF_ETHERNET */ diff --git a/src/ethernet/ethernet_nfapi.h b/src/ethernet/ethernet_nfapi.h new file mode 100644 index 00000000..d97bcaf4 --- /dev/null +++ b/src/ethernet/ethernet_nfapi.h @@ -0,0 +1,55 @@ +/* + * ARAnyM ethernet driver - header file. + * + * Copyright (c) 2002-2004 Standa and Petr of ARAnyM dev team (see AUTHORS) + * + * This file is part of the ARAnyM project which builds a new and powerful + * TOS/FreeMiNT compatible virtual machine running on almost any hardware. + * + * ARAnyM 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. + * + * ARAnyM 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 ARAnyM. If not see <http://www.gnu.org/licenses/> + */ + +#ifndef _ARAETHER_NFAPI_H +#define _ARAETHER_NFAPI_H + +/* + The code for the FreeMiNT driver was moved to the FreeMiNT CVS: + freemint/sys/sockets/xif/nfeth. + + If you edit this file then you would need to synchronize it with the + driver in the freemint CVS (nfeth_nfapi.h). + + if you change anything in the enum {} below you have to increase + this ARAETHER_NFAPI_VERSION! +*/ +#define ARAETHER_NFAPI_VERSION 0x00000005 + +enum { + GET_VERSION = 0, /* no parameters, return NFAPI_VERSION in d0 */ + XIF_INTLEVEL, /* no parameters, return Interrupt Level in d0 */ + XIF_IRQ, /* acknowledge interrupt from host */ + XIF_START, /* (ethX), called on 'ifup', start receiver thread */ + XIF_STOP, /* (ethX), called on 'ifdown', stop the thread */ + XIF_READLENGTH, /* (ethX), return size of network data block to read */ + XIF_READBLOCK, /* (ethX, buffer, size), read block of network data */ + XIF_WRITEBLOCK, /* (ethX, buffer, size), write block of network data */ + XIF_GET_MAC, /* (ethX, buffer, size), return MAC HW addr in buffer */ + XIF_GET_IPHOST, /* (ethX, buffer, size), return IP address of host */ + XIF_GET_IPATARI, /* (ethX, buffer, size), return IP address of atari */ + XIF_GET_NETMASK /* (ethX, buffer, size), return IP netmask */ +}; + +#define ETH(a) (nfEtherID + a) + +#endif /* _ARAETHER_NFAPI_H */ diff --git a/src/ethernet/ethernet_win32.c b/src/ethernet/ethernet_win32.c new file mode 100644 index 00000000..594c2a04 --- /dev/null +++ b/src/ethernet/ethernet_win32.c @@ -0,0 +1,456 @@ +/* + ethernet_win32.c -- Interaction with Windows tap driver in a Win32/Cygwin environment + Copyright (C) 2002-2004 Ivo Timmermans <ivo@xxxxxxxxxxxx>, + 2002-2004 Guus Sliepen <guus@xxxxxxxxxxxx> + + 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 see <http://www.gnu.org/licenses/> +*/ + +#include "config.h" +#include <SDL.h> +#include <SDL_thread.h> +#include "stMemory.h" +#include "log.h" +#include "gemdos_defines.h" +#include "ethernet.h" +#include "configuration.h" + +#if defined(WITH_NF_ETHERNET) && (defined(__CYGWIN32__) || defined(__WIN32__)) + +#define _WIN32_VERSION 0x501 +#undef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#if !defined(__CYGWIN__) +#include <winsock2.h> +#endif +#include <windows.h> +#undef WIN32_LEAN_AND_MEAN /* to avoid redefinition in SDL headers */ + +#include <winioctl.h> + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif + +#include <errno.h> + +#define MTU 1500 + +/*============= */ +/* TAP IOCTLs */ +/*============= */ + +#define TAP_CONTROL_CODE(request,method) \ + CTL_CODE (FILE_DEVICE_UNKNOWN, request, method, FILE_ANY_ACCESS) + +#define TAP_IOCTL_GET_MAC TAP_CONTROL_CODE (1, METHOD_BUFFERED) +#define TAP_IOCTL_GET_VERSION TAP_CONTROL_CODE (2, METHOD_BUFFERED) +#define TAP_IOCTL_GET_MTU TAP_CONTROL_CODE (3, METHOD_BUFFERED) +#define TAP_IOCTL_GET_INFO TAP_CONTROL_CODE (4, METHOD_BUFFERED) +#define TAP_IOCTL_CONFIG_POINT_TO_POINT TAP_CONTROL_CODE (5, METHOD_BUFFERED) +#define TAP_IOCTL_SET_MEDIA_STATUS TAP_CONTROL_CODE (6, METHOD_BUFFERED) +#define TAP_IOCTL_CONFIG_DHCP_MASQ TAP_CONTROL_CODE (7, METHOD_BUFFERED) +#define TAP_IOCTL_GET_LOG_LINE TAP_CONTROL_CODE (8, METHOD_BUFFERED) +#define TAP_IOCTL_CONFIG_DHCP_SET_OPT TAP_CONTROL_CODE (9, METHOD_BUFFERED) + +/*================= */ +/* Registry keys */ +/*================= */ + +#define ADAPTER_KEY "SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}" + +#define NETWORK_CONNECTIONS_KEY "SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}" + +/*====================== */ +/* Filesystem prefixes */ +/*====================== */ + +#define USERMODEDEVICEDIR "\\\\.\\Global\\" +#define SYSDEVICEDIR "\\Device\\" +#define USERDEVICEDIR "\\DosDevices\\Global\\" +#define TAPSUFFIX ".tap" + +/*========================================================= */ +/* TAP_COMPONENT_ID -- This string defines the TAP driver */ +/* type -- different component IDs can reside in the system */ +/* simultaneously. */ +/*========================================================= */ + +#define TAP_COMPONENT_ID "tap0801" + + +struct WinTapEthernetHandler +{ + struct EthernetHandler base; + + OVERLAPPED read_overlapped; + OVERLAPPED write_overlapped; + HANDLE device_handle; + char *device; + char *iface; + + int device_total_in; + int device_total_out; +}; + + +static char *winerror(int err) +{ + static char buf[1024]; + char *newline; + + if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, sizeof(buf), NULL)) + { + strncpy(buf, "(unable to format errormessage)", sizeof(buf)); + } + + if ((newline = strchr(buf, '\r'))) + *newline = '\0'; + + return buf; +} + + +static bool is_tap_win32_dev(const char *guid) +{ + HKEY netcard_key; + LONG status; + DWORD len; + int i = 0; + + status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, ADAPTER_KEY, 0, KEY_READ, &netcard_key); + + if (status != ERROR_SUCCESS) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: WinTap: Error opening registry key: %s\n", ADAPTER_KEY); + return false; + } + + for (;;) + { + char enum_name[256]; + char unit_string[256]; + HKEY unit_key; + char component_id_string[] = "ComponentId"; + char component_id[256]; + char net_cfg_instance_id_string[] = "NetCfgInstanceId"; + char net_cfg_instance_id[256]; + DWORD data_type; + + len = sizeof(enum_name); + status = RegEnumKeyEx(netcard_key, i, enum_name, &len, NULL, NULL, NULL, NULL); + + if (status == ERROR_NO_MORE_ITEMS) + break; + else if (status != ERROR_SUCCESS) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: WinTap: Error enumerating registry subkeys of key: %s\n", ADAPTER_KEY); + return false; + } + + snprintf(unit_string, sizeof(unit_string), "%s\\%s", ADAPTER_KEY, enum_name); + + status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, unit_string, 0, KEY_READ, &unit_key); + + if (status != ERROR_SUCCESS) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: WinTap: Error opening registry key: %s\n", unit_string); + return false; + } else + { + len = sizeof(component_id); + status = RegQueryValueEx(unit_key, component_id_string, NULL, &data_type, (BYTE *) component_id, &len); + + if (!(status != ERROR_SUCCESS || data_type != REG_SZ)) + { + len = sizeof(net_cfg_instance_id); + status = RegQueryValueEx(unit_key, + net_cfg_instance_id_string, + NULL, &data_type, (BYTE *) net_cfg_instance_id, &len); + + if (status == ERROR_SUCCESS && data_type == REG_SZ) + { + if (!strcmp(net_cfg_instance_id, guid)) + { + RegCloseKey(unit_key); + RegCloseKey(netcard_key); + return true; + } + } + } + RegCloseKey(unit_key); + } + ++i; + } + + RegCloseKey(netcard_key); + return false; +} + + +static int get_device_guid(char *name, int name_size, char *actual_name, int actual_name_size) +{ + LONG status; + HKEY control_net_key; + DWORD len; + int i = 0; + int stop = 0; + + status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, NETWORK_CONNECTIONS_KEY, 0, KEY_READ, &control_net_key); + + if (status != ERROR_SUCCESS) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: WinTap: Error opening registry key: %s\n", NETWORK_CONNECTIONS_KEY); + return -1; + } + + while (!stop) + { + char enum_name[256]; + char connection_string[256]; + HKEY connection_key; + char name_data[256]; + DWORD name_type; + const char name_string[] = "Name"; + + len = sizeof(enum_name); + status = RegEnumKeyEx(control_net_key, i, enum_name, &len, NULL, NULL, NULL, NULL); + + if (status == ERROR_NO_MORE_ITEMS) + break; + else if (status != ERROR_SUCCESS) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: WinTap: Error enumerating registry subkeys of key: %s\n", NETWORK_CONNECTIONS_KEY); + return -1; + } + + snprintf(connection_string, + sizeof(connection_string), "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, enum_name); + + status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, connection_string, 0, KEY_READ, &connection_key); + + if (status == ERROR_SUCCESS) + { + len = sizeof(name_data); + status = RegQueryValueEx(connection_key, name_string, NULL, &name_type, (BYTE *) name_data, &len); + + if (status != ERROR_SUCCESS || name_type != REG_SZ) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: WinTap: Error opening registry key: %s\\%s\\%s", + NETWORK_CONNECTIONS_KEY, connection_string, name_string); + return -1; + } else + { + if (is_tap_win32_dev(enum_name)) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: WinTap: found TAP device named \"%s\" ~ \"%s\"\n", name_data, actual_name); + + snprintf(name, name_size, "%s", enum_name); + if (actual_name) + { + if (strcmp(actual_name, "") != 0) + { + if (strcmp(name_data, actual_name) != 0) + { + RegCloseKey(connection_key); + ++i; + continue; + } + } else + { + snprintf(actual_name, actual_name_size, "%s", name_data); + } + } + stop = 1; + } + } + + RegCloseKey(connection_key); + } + ++i; + } + + RegCloseKey(control_net_key); + + if (stop == 0) + return -1; + + return 0; +} + + +static bool WinTapEthernetHandler_open(struct EthernetHandler *_handler) +{ + struct WinTapEthernetHandler *handler = (struct WinTapEthernetHandler *) _handler; + char *type = ConfigureParams.Ethernet[handler->base.ethX].type; + char device_path[256]; + char device_guid[256]; + char name_buffer[256]; + + handler->base.close(&handler->base); + + if (strcmp(type, "none") == 0 || strlen(type) == 0) + { + return false; + } + + if (strlen(ConfigureParams.Ethernet[handler->base.ethX].tunnel) == 0) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: WinTap(%d): tunnel name undefined\n", handler->base.ethX); + return false; + } + + strcpy(name_buffer, ConfigureParams.Ethernet[handler->base.ethX].tunnel); + + if (get_device_guid(device_guid, sizeof(device_guid), name_buffer, sizeof(name_buffer)) < 0) + { + LOG_TRACE(LOG_WARN, "Ethernet: WinTap(%d): ERROR: Could not find Windows tap device: %s\n", handler->base.ethX, winerror(GetLastError())); + return false; + } + + /* + * Open Windows TAP-Win32 adapter + */ + snprintf(device_path, sizeof(device_path), "%s%s%s", USERMODEDEVICEDIR, device_guid, TAPSUFFIX); + + handler->device_handle = + CreateFile(device_path, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, + FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0); + + if (handler->device_handle == INVALID_HANDLE_VALUE) + { + LOG_TRACE(LOG_WARN, "Ethernet: WinTap(%d): ERROR: Could not open (%s) Windows tap device: %s\n", handler->base.ethX, device_path, + winerror(GetLastError())); + return false; + } + handler->device = strdup(device_path); + + handler->read_overlapped.Offset = 0; + handler->read_overlapped.OffsetHigh = 0; + handler->read_overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + handler->write_overlapped.Offset = 0; + handler->write_overlapped.OffsetHigh = 0; + handler->write_overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + + LOG_TRACE(TRACE_ETHERNET, "Ethernet: WinTap(%d): tap device open %s [handle=%p]\n", handler->base.ethX, device_path, (void *) handler->device_handle); + return true; +} + + +static void WinTapEthernetHandler_close(struct EthernetHandler *_handler) +{ + struct WinTapEthernetHandler *handler = (struct WinTapEthernetHandler *) _handler; + + if (handler->device_handle != INVALID_HANDLE_VALUE) + { + CloseHandle(handler->device_handle); + handler->device_handle = INVALID_HANDLE_VALUE; + } + free(handler->device); + handler->device = NULL; +} + + +static int WinTapEthernetHandler_recv(struct EthernetHandler *_handler, uint8_t *buf, int len) +{ + struct WinTapEthernetHandler *handler = (struct WinTapEthernetHandler *) _handler; + DWORD lenin; + BOOL result; + + LOG_TRACE(TRACE_ETHERNET, "Ethernet: WinTap(%d): Read packet from %s\n", handler->base.ethX, handler->device); + + result = ReadFile(handler->device_handle, buf, len, &lenin, &handler->read_overlapped); + + if (!result) + { + DWORD error = GetLastError(); + + switch (error) + { + case ERROR_IO_PENDING: + WaitForSingleObject(handler->read_overlapped.hEvent, INFINITE); + result = GetOverlappedResult(handler->device_handle, &handler->read_overlapped, &lenin, 0); + if (result) + break; + /* fallthrough */ + default: + LOG_TRACE(TRACE_ETHERNET, "Ethernet: WinTap(%d): Error while reading from %s: %s\n", handler->base.ethX, handler->device, + winerror(GetLastError())); + return -1; + } + } + + handler->device_total_in += lenin; + LOG_TRACE(TRACE_ETHERNET, "Ethernet: WinTap(%d): Read packet done (len %d)\n", handler->base.ethX, (int) lenin); + return lenin; +} + + +static int WinTapEthernetHandler_send(struct EthernetHandler *_handler, const uint8_t *buf, int len) +{ + struct WinTapEthernetHandler *handler = (struct WinTapEthernetHandler *) _handler; + DWORD lenout; + BOOL result; + + LOG_TRACE(TRACE_ETHERNET, "Ethernet: WinTap(%d): Writing packet of %d bytes to %s\n", handler->base.ethX, len, handler->device); + + result = WriteFile(handler->device_handle, buf, len, &lenout, &handler->write_overlapped); + if (!result) + { + DWORD error = GetLastError(); + + switch (error) + { + case ERROR_IO_PENDING: + WaitForSingleObject(handler->write_overlapped.hEvent, INFINITE); + break; + default: + LOG_TRACE(TRACE_ETHERNET, "Ethernet: WinTap(%d): Error while writing to %s: %s\n", handler->base.ethX, handler->device, + winerror(GetLastError())); + return -1; + } + } + + handler->device_total_out += lenout; + LOG_TRACE(TRACE_ETHERNET, "Ethernet: WinTap(%d): Writing packet done\n", handler->base.ethX); + return lenout; +} + + +struct EthernetHandler *WinTapEthernetHandler(int eth_idx) +{ + struct WinTapEthernetHandler *handler = (struct WinTapEthernetHandler *) malloc(sizeof(*handler)); + + handler->base.ethX = eth_idx; + handler->base.packet_length = 0; + handler->base.handlingThread = NULL; + handler->base.intAck = NULL; + + handler->base.open = WinTapEthernetHandler_open; + handler->base.close = WinTapEthernetHandler_close; + handler->base.recv = WinTapEthernetHandler_recv; + handler->base.send = WinTapEthernetHandler_send; + + handler->device_handle = INVALID_HANDLE_VALUE; + handler->device = NULL; + handler->iface = NULL; + + handler->device_total_in = 0; + handler->device_total_out = 0; + + return &handler->base; +} + +#endif /* WITH_NF_ETHERNET */ diff --git a/src/ethernet/fd_trans.c b/src/ethernet/fd_trans.c new file mode 100644 index 00000000..354fd602 --- /dev/null +++ b/src/ethernet/fd_trans.c @@ -0,0 +1,258 @@ +/* + * fd_trans.c + * Used to transfer a file descriptor between two Unix processes + * + * Inspired by Amit Singhs description in the "Mac OS X manual": + * http://topiks.org/mac-os-x/0321278542/ch09lev1sec11.html + * + */ + +#include <stdio.h> +#include <fcntl.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> + +/* + * only used by ethernet implementation for macOS so far + */ +#ifdef __APPLE__ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/uio.h> +#include <sys/wait.h> + + +#include "fd_trans.h" + +#define SOCKET_PATH "/tmp/fd_trans_socket" + +#define MSG_CONTROL_SIZE CMSG_SPACE(sizeof(int)) + + + +static int fd_receive_using_sockfd(int *fd, int sockfd) +{ + ssize_t ret; + u_char c; + int errcond = 0; + struct iovec iovec[1]; + struct msghdr msg; + struct cmsghdr *cmsghdrp; + union { + struct cmsghdr cmsghdr; + u_char msg_control[MSG_CONTROL_SIZE]; + } cmsghdr_msg_control; + + iovec[0].iov_base = (void*)&c; + iovec[0].iov_len = 1; + + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = iovec; + msg.msg_iovlen = 1; + msg.msg_control = (void*)cmsghdr_msg_control.msg_control; + msg.msg_controllen = sizeof(cmsghdr_msg_control.msg_control); + msg.msg_flags = 0; + + if ((ret = recvmsg(sockfd, &msg, 0)) <= 0) + { + perror("recvmsg"); + *fd = -1; + return (int)ret; + } + + cmsghdrp = CMSG_FIRSTHDR(&msg); + if (cmsghdrp == NULL) + { + *fd = -1; + return (int)ret; + } + + if (cmsghdrp->cmsg_len != CMSG_LEN(sizeof(int))) + errcond++; + + if (cmsghdrp->cmsg_level != SOL_SOCKET) + errcond++; + + if (cmsghdrp->cmsg_type != SCM_RIGHTS) + errcond++; + + if (errcond) + { + fprintf(stderr, "receiver: %d errors in received message\n", errcond); + *fd = -1; + } else + *fd = *((int *)CMSG_DATA(cmsghdrp)); + + return (int)ret; +} + + +int fd_setup_server(void) +{ + int sockfd; + socklen_t len; + struct sockaddr_un server_unix_addr; + const char *name = SOCKET_PATH; + + if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) + { + perror("socket"); + return sockfd; + } + + unlink(name); + bzero((char *)&server_unix_addr, sizeof(server_unix_addr)); + server_unix_addr.sun_family = AF_LOCAL; + strcpy(server_unix_addr.sun_path, name); + len = (socklen_t)strlen(name) + 1; + len += sizeof(server_unix_addr.sun_family); + + if (bind(sockfd, (struct sockaddr *)&server_unix_addr, len) < 0) + { + close(sockfd); + return -1; + } + + return sockfd; +} + + +static int fd_send_using_sockfd(int fd, int sockfd) +{ + ssize_t ret; + struct iovec iovec[1]; + struct msghdr msg; + struct cmsghdr *cmsghdrp; + union { + struct cmsghdr cmsghdr; + u_char msg_control[MSG_CONTROL_SIZE]; + } cmsghdr_msg_control; + static char iov_base[1] = { 0 }; + + iovec[0].iov_base = iov_base; + iovec[0].iov_len = 1; + + msg.msg_name = NULL; /* address (optional) */ + msg.msg_namelen = 0; /* size of address */ + msg.msg_iov = iovec; /* scatter/gather array */ + msg.msg_iovlen = 1; /* members in msg.msg_iov */ + msg.msg_control = (void*)cmsghdr_msg_control.msg_control; /* ancillary data */ + /* ancillary data buffer length */ + msg.msg_controllen = sizeof(cmsghdr_msg_control.msg_control); + msg.msg_flags = 0; /* flags on received message */ + + /* CMSG_FIRSTHDR() returns a pointer to the first cmsghdr structure in */ + /* the ancillary data associated with the given msghdr structure */ + cmsghdrp = CMSG_FIRSTHDR(&msg); + + cmsghdrp->cmsg_len = CMSG_LEN(sizeof(int)); /* data byte count */ + cmsghdrp->cmsg_level = SOL_SOCKET; /* originating protocol */ + cmsghdrp->cmsg_type = SCM_RIGHTS; /* protocol-specified type */ + + /* CMSG_DATA() returns a pointer to the data array associated with */ + /* the cmsghdr structure pointed to by cmsghdrp */ + *((int *)CMSG_DATA(cmsghdrp)) = fd; + + if ((ret = sendmsg(sockfd, &msg, 0)) < 0) + { + perror("sendmsg"); + return (int)ret; + } + + return 0; +} + + +void fd_send(int fd) +{ + int sockfd, len; + struct sockaddr_un server_unix_addr; + int i, ret = -1; + + bzero((char *)&server_unix_addr, sizeof(server_unix_addr)); + strcpy(server_unix_addr.sun_path, SOCKET_PATH); + server_unix_addr.sun_family = AF_LOCAL; + len = strlen(SOCKET_PATH) + 1; + len += sizeof(server_unix_addr.sun_family); + + if ((sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) + { + perror("socket"); + return; + } + + for (i = 1; i < 3 && ret < 0; i++) + { + ret = connect(sockfd, (struct sockaddr *)&server_unix_addr, len); + if (ret < 0) + { + perror("sender: connect"); + sleep(1); + } + } + if (ret >= 0) + { + /* Send file descriptor message */ + if ((fd_send_using_sockfd(fd, sockfd) < 0)) + fprintf(stderr, "sender: failed to send file descriptor (fd = %d)\n", fd); + } else + { + perror("sender: connect"); + } + sleep(1); + + close(sockfd); +} + + +int fd_receive(int sockfd, pid_t from) +{ + int fd = -1; + int status; + fd_set readfds; + struct timeval timeout; + + /* Wait until a connection is established */ + listen(sockfd, 0); + + for (;;) + { + struct sockaddr_un cl_un; + socklen_t cl_un_len = sizeof(cl_un); + + FD_ZERO(&readfds); + FD_SET(sockfd, &readfds); + timeout.tv_sec = 1; + timeout.tv_usec = 0; + if (select(sockfd + 1, &readfds, NULL, NULL, &timeout) >= 0 && FD_ISSET(sockfd, &readfds)) + { + int csockfd = accept(sockfd, (struct sockaddr *)&cl_un, &cl_un_len); + + if (csockfd < 0) + { + perror("accept"); + } + else + { + int ret = fd_receive_using_sockfd(&fd, csockfd); + if (ret < 0) + fprintf(stderr, "receiver: failed to receive file descriptor\n"); + break; + } + } + + if (waitpid(from, &status, WNOHANG) > 0 && WIFEXITED(status)) + { + fprintf(stderr, "accept: child process died\n"); + break; + } + } + + return fd; +} + +#endif /* __APPLE__ */ diff --git a/src/ethernet/fd_trans.h b/src/ethernet/fd_trans.h new file mode 100644 index 00000000..7253f09b --- /dev/null +++ b/src/ethernet/fd_trans.h @@ -0,0 +1,20 @@ +/* + * fd_trans.h + * Used to transfer a file descriptor between two Unix processes + * + * Inspired by Amit Singhs description in the "Mac OS X manual": + * http://topiks.org/mac-os-x/0321278542/ch09lev1sec11.html + * + */ + +#ifdef __cplusplus +extern "C" { +#endif + +int fd_receive(int sockfd, pid_t from); +void fd_send(int fd); +int fd_setup_server(void); + +#ifdef __cplusplus +} +#endif diff --git a/src/ethernet/nf_ethernet.c b/src/ethernet/nf_ethernet.c new file mode 100644 index 00000000..1b3be922 --- /dev/null +++ b/src/ethernet/nf_ethernet.c @@ -0,0 +1,606 @@ +/* + * nf_ethernet.c - Ethernet Card Emulation + * + * Copyright (c) 2007 ARAnyM dev team (see AUTHORS) + * + * This file is part of the ARAnyM project which builds a new and powerful + * TOS/FreeMiNT compatible virtual machine running on almost any hardware. + * + * ARAnyM 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. + * + * ARAnyM 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 ARAnyM. If not see <http://www.gnu.org/licenses/> + */ + +#include "config.h" +#include <SDL.h> +#include <SDL_thread.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include "stMemory.h" +#include "log.h" +#include "gemdos_defines.h" +#include "m68000.h" +#include "configuration.h" + +#include "nf_ethernet.h" +#include "ethernet_nfapi.h" +#include "ethernet.h" + +/**************************** + * Configuration zone begins + */ + + +/* Ethernet runs at interrupt level 3 by default but can be reconfigured */ +#if 1 +#define INTLEVEL 3 +#define TRIGGER_INTERRUPT M68000_Exception(24 + INTLEVEL, M68000_EXC_SRC_AUTOVEC) +#else +#define INTLEVEL 5 +#define TRIGGER_INTERRUPT M68000_Exception(24 + INTLEVEL, M68000_EXC_SRC_AUTOVEC) +#endif + +/* + * Configuration zone ends + **************************/ + +typedef enum { HOST_IP, ATARI_IP, NETMASK } GET_PAR; + +static volatile int pending_interrupts; + +static struct EthernetHandler *handlers[MAX_ETH]; + + +static uint32_t read_stack_long(uint32_t * stack) +{ + uint32_t value = STMemory_ReadLong(*stack); + + *stack += SIZE_LONG; + + return value; +} + + +static void *read_stack_pointer(uint32_t * stack) +{ + uint32_t ptr = read_stack_long(stack); + + return ptr ? STMemory_STAddrToPointer(ptr) : 0; +} + + +static struct EthernetHandler *getHandler(int ethX) +{ + if (ethX >= 0 && ethX < MAX_ETH) + { + struct EthernetHandler *h = handlers[ethX]; + + if (h != NULL) + { + assert(h->ethX == ethX); + return h; + } + } + + return NULL; +} + + +static int get_params(GET_PAR which, uint32_t stack) +{ + int ethX; + uint32_t st_name_ptr; + char *name_ptr; + uint32_t name_maxlen; + const char *text = NULL; + + ethX = read_stack_long(&stack); + st_name_ptr = STMemory_ReadLong(stack); + name_ptr = read_stack_pointer(&stack); + name_maxlen = read_stack_long(&stack); + + LOG_TRACE(TRACE_ETHERNET, "Ethernet: getPAR(%d) for eth%d to buffer at %x of size %d\n", which, ethX, st_name_ptr, name_maxlen); + + if (ethX < 0 || ethX >= MAX_ETH) + { + LOG_TRACE(LOG_WARN, "Ethernet: handler for %d not found\n", ethX); + return 0; + } + if (!STMemory_CheckAreaType(st_name_ptr, name_maxlen, ABFLAG_RAM)) + { + Log_Printf(LOG_WARN, "ethernet: Invalid RAM range 0x%x+%i\n", st_name_ptr, name_maxlen); + return 0; + } + + switch (which) + { + case HOST_IP: + text = ConfigureParams.Ethernet[ethX].ip_host; + break; + case ATARI_IP: + text = ConfigureParams.Ethernet[ethX].ip_atari; + break; + case NETMASK: + text = ConfigureParams.Ethernet[ethX].netmask; + break; + default: + text = ""; + break; + } + + strncpy(name_ptr, text, name_maxlen); + return strlen(text); +} + + +static int32_t readPacketLength(int ethX) +{ + struct EthernetHandler *handler = getHandler(ethX); + + if (handler == NULL) + { + LOG_TRACE(LOG_WARN, "Ethernet: handler for %d not found\n", ethX); + return 0; + } + return handler->packet_length; +} + + +/* + * ETHERNETDriver ReadPacket routine + */ + +static void readPacket(int ethX, unsigned char *buffer, uint32_t len) +{ + struct EthernetHandler *handler = getHandler(ethX); + + if (handler == NULL) + { + LOG_TRACE(LOG_WARN, "Ethernet: handler for %d not found\n", ethX); + return; + } + if (len > MAX_PACKET_SIZE) + { + LOG_TRACE(LOG_WARN, "Ethernet: readPacket() - length %d > %d\n", len, MAX_PACKET_SIZE); + len = MAX_PACKET_SIZE; + } + memcpy(buffer, handler->packet, len); +} + + +/* + * ETHERNETDriver writePacket routine + */ + +static void sendPacket(int ethX, const unsigned char *buffer, uint32_t len) +{ + struct EthernetHandler *handler = getHandler(ethX); + uint8_t packetToWrite[MAX_PACKET_SIZE + 2]; + + if (handler == NULL) + { + LOG_TRACE(LOG_WARN, "Ethernet: handler for %d not found\n", ethX); + return; + } + + if (len > MAX_PACKET_SIZE) + len = MAX_PACKET_SIZE; + memcpy(packetToWrite, buffer, len); + + /* Transmit packet */ + if (handler->send(handler, packetToWrite, len) < 0) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: WARNING: Couldn't transmit packet\n"); + } +} + + +/* + * Packet reception thread + */ +static int receiveFunc(void *arg) +{ + struct EthernetHandler *handler = (struct EthernetHandler *) arg; + + /* Call protocol handler for received packets */ + for (;;) + { + /* Read packet device */ + handler->packet_length = handler->recv(handler, handler->packet, MAX_PACKET_SIZE); + + /* Trigger ETHERNETDriver interrupt (call the m68k side) */ + LOG_TRACE(TRACE_ETHERNET, "Ethernet:%d: packet received (len %d), triggering ETHERNETDriver interrupt\n", handler->ethX, (int) handler->packet_length); + + pending_interrupts |= (1 << handler->ethX); + TRIGGER_INTERRUPT; + + /* Wait for interrupt acknowledge (m68k network driver read interrupt to finish) */ + LOG_TRACE(TRACE_ETHERNET, "Ethernet:%d: waiting for int acknowledge with pending irq mask %02x\n", handler->ethX, pending_interrupts); + SDL_SemWait(handler->intAck); + pending_interrupts &= ~(1 << handler->ethX); + LOG_TRACE(TRACE_ETHERNET, "Ethernet:%d: int acknowledged, pending irq mask now %02x\n", handler->ethX, pending_interrupts); + } + + return 0; +} + + +/* + * Stop packet reception thread + */ +static void stopThread(struct EthernetHandler *handler) +{ + if (handler && handler->handlingThread) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet:%d: Stop thread\n", handler->ethX); + +#ifdef FIXME + /* pthread_cancel(handlingThread); // FIXME: set the cancel flag. */ + SDL_WaitThread(handler->handlingThread, NULL); + SDL_DestroySemaphore(handler->intAck); +#endif + handler->handlingThread = NULL; + } +} + + +/* + * Start packet reception thread + */ +static bool startThread(struct EthernetHandler *handler) +{ + if (handler->handlingThread == NULL) + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet:%d: Start thread\n", handler->ethX); + + if ((handler->intAck = SDL_CreateSemaphore(0)) == NULL) + { + Log_Printf(LOG_WARN, "Ethernet:%d: WARNING: Cannot init semaphore\n", handler->ethX); + return false; + } + + handler->handlingThread = SDL_CreateThread(receiveFunc, "Ethernet", handler); + if (handler->handlingThread == NULL) + { + Log_Printf(LOG_WARN, "Ethernet:%d: WARNING: Cannot start ETHERNETDriver thread\n", handler->ethX); + return false; + } + } + return true; +} + + +/* + * Dispatcher for ETHERNET native features. + * Note that in ARAnyM, all of the functions below + * are only allowed to called from SuperVisor mode. + * This works because they are only issued from inside the kernel, + * through ioctl() on the device descriptor. + * In Hatari, to simplify things a bit, we allow a few query options + * to also be called from User mode. + */ +bool nf_ethernet(uint32_t stack, uint32_t subid, uint32_t *retval) +{ + int ethX; + struct EthernetHandler *handler; + bool super = ((M68000_GetSR() & SR_SUPERMODE) == SR_SUPERMODE); + + *retval = 0; + switch (subid) + { + case GET_VERSION: + *retval = ARAETHER_NFAPI_VERSION; + break; + + case XIF_INTLEVEL: /* what interrupt level is used? */ + LOG_TRACE(TRACE_ETHERNET, "Ethernet: getINTlevel\n"); + *retval = INTLEVEL; + break; + + case XIF_GET_MAC: /* what is the MAC address? */ + /* store MAC address to provided buffer */ + { + uint32_t st_buf_ptr; + unsigned char *buf_ptr; + uint32_t buf_size; + char *ms; + bool format_OK; + + /* default MAC Address is just made up */ + uint8_t mac_addr[6] = { '\0', 'A', 'E', 'T', 'H', 0 }; + + ethX = read_stack_long(&stack); + + handler = getHandler(ethX); + + if (handler == NULL) + { + *retval = 0; /* return FALSE if ethX not defined */ + return true; + } + + st_buf_ptr = STMemory_ReadLong(stack); + buf_ptr = read_stack_pointer(&stack); /* destination buffer */ + buf_size = read_stack_long(&stack); /* buffer size */ + LOG_TRACE(TRACE_ETHERNET, "Ethernet: getMAC(%d, %x, %d)\n", ethX, st_buf_ptr, (int) buf_size); + + /* default MAC Address is just made up */ + mac_addr[5] = '0' + ethX; + + /* convert user-defined MAC Address from string to 6 bytes array */ + ms = ConfigureParams.Ethernet[ethX].mac_addr; + format_OK = false; + if (strlen(ms) == 2 * 6 + 5 && (ms[2] == ':' || ms[2] == '-')) + { + int md[6] = { 0, 0, 0, 0, 0, 0 }; + int matched; + int i; + + ms[2] = ms[5] = ms[8] = ms[11] = ms[14] = ':'; + matched = sscanf(ms, "%02x:%02x:%02x:%02x:%02x:%02x", &md[0], &md[1], &md[2], &md[3], &md[4], &md[5]); + if (matched == 6) + { + for (i = 0; i < 6; i++) + mac_addr[i] = md[i]; + format_OK = true; + } + } + if (!format_OK) + { + LOG_TRACE(LOG_WARN, "Ethernet: MAC Address of [ETH%d] in incorrect format\n", ethX); + } + if (buf_size > sizeof(mac_addr)) + buf_size = sizeof(mac_addr); + if (!STMemory_CheckAreaType(st_buf_ptr, buf_size, ABFLAG_RAM)) + { + Log_Printf(LOG_WARN, "ethernet: Invalid RAM range 0x%x+%i\n", st_buf_ptr, buf_size); + *retval = 0; + M68000_BusError(st_buf_ptr, BUS_ERROR_WRITE, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); + return false; + } else + { + memcpy(buf_ptr, mac_addr, buf_size); + *retval = 1; /* TRUE */ + } + } + break; + + case XIF_IRQ: /* interrupt raised by native side thread polling tap0 interface */ + if (!super) + { + LOG_TRACE(TRACE_NATFEATS, "Ethernet: ERROR: NF function called without supervisor mode\n"); + M68000_Exception(8, M68000_EXC_SRC_CPU); + return false; + } + { + int dev_bit = read_stack_long(&stack); + + if (dev_bit == 0) + { + /* dev_bit = 0 means "tell me what devices want me to serve their interrupts" */ + *retval = pending_interrupts; + } else + { + /* otherwise the set bit means "I'm acknowledging this device's interrupt" */ + ethX = -1; + + switch (dev_bit) + { + case 0x01: + ethX = 0; + break; + case 0x02: + ethX = 1; + break; + case 0x04: + ethX = 2; + break; + case 0x08: + ethX = 3; + break; + case 0x10: + ethX = 4; + break; + case 0x20: + ethX = 5; + break; + case 0x40: + ethX = 6; + break; + case 0x80: + ethX = 7; + break; + default: + LOG_TRACE(LOG_WARN, "Ethernet: wrong XIF_IRQ(%d)\n", dev_bit); + break; + } + + handler = getHandler(ethX); + + if (handler == NULL) + { + LOG_TRACE(LOG_WARN, "Ethernet: handler for %d not found\n", ethX); + } else + { + LOG_TRACE(TRACE_ETHERNET, "Ethernet: ETH%d IRQ acknowledged\n", ethX); + /* Acknowledge interrupt to reception thread */ + SDL_SemPost(handler->intAck); + } + } + } + break; + + case XIF_START: + if (!super) + { + LOG_TRACE(TRACE_NATFEATS, "Ethernet: ERROR: NF function called without supervisor mode\n"); + M68000_Exception(8, M68000_EXC_SRC_CPU); + return false; + } + ethX = read_stack_long(&stack); + handler = getHandler(ethX); + if (handler == NULL || startThread(handler) == false) + { + *retval = GEMDOS_EUNDEV; + } + break; + + case XIF_STOP: + if (!super) + { + LOG_TRACE(TRACE_NATFEATS, "Ethernet: ERROR: NF function called without supervisor mode\n"); + M68000_Exception(8, M68000_EXC_SRC_CPU); + return false; + } + ethX = read_stack_long(&stack); + stopThread(getHandler(ethX)); + break; + + case XIF_READLENGTH: + if (!super) + { + LOG_TRACE(TRACE_NATFEATS, "Ethernet: ERROR: NF function called without supervisor mode\n"); + M68000_Exception(8, M68000_EXC_SRC_CPU); + return false; + } + ethX = read_stack_long(&stack); + *retval = readPacketLength(ethX); + break; + + case XIF_READBLOCK: + if (!super) + { + LOG_TRACE(TRACE_NATFEATS, "Ethernet: ERROR: NF function called without supervisor mode\n"); + M68000_Exception(8, M68000_EXC_SRC_CPU); + return false; + } + { + uint32_t st_buf_ptr; + unsigned char *buf_ptr; + uint32_t buf_size; + + ethX = read_stack_long(&stack); + st_buf_ptr = STMemory_ReadLong(stack); + buf_ptr = read_stack_pointer(&stack); /* destination buffer */ + buf_size = read_stack_long(&stack); /* buffer size */ + if (!STMemory_CheckAreaType(st_buf_ptr, buf_size, ABFLAG_RAM)) + { + Log_Printf(LOG_WARN, "ethernet: Invalid RAM range 0x%x+%i\n", st_buf_ptr, buf_size); + M68000_BusError(st_buf_ptr, BUS_ERROR_WRITE, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); + return false; + } + LOG_TRACE(TRACE_ETHERNET, "Ethernet: ReadPacket dest %08x, len %x\n", st_buf_ptr, buf_size); + readPacket(ethX, buf_ptr, buf_size); + } + break; + + case XIF_WRITEBLOCK: + if (!super) + { + LOG_TRACE(TRACE_NATFEATS, "Ethernet: ERROR: NF function called without supervisor mode\n"); + M68000_Exception(8, M68000_EXC_SRC_CPU); + return false; + } + { + uint32_t st_buf_ptr; + unsigned char *buf_ptr; + uint32_t buf_size; + + ethX = read_stack_long(&stack); + st_buf_ptr = STMemory_ReadLong(stack); + buf_ptr = read_stack_pointer(&stack); /* destination buffer */ + buf_size = read_stack_long(&stack); /* buffer size */ + if (!STMemory_CheckAreaType(st_buf_ptr, buf_size, ABFLAG_RAM | ABFLAG_ROM)) + { + Log_Printf(LOG_WARN, "ethernet: Invalid RAM range 0x%x+%i\n", st_buf_ptr, buf_size); + M68000_BusError(st_buf_ptr, BUS_ERROR_WRITE, BUS_ERROR_SIZE_BYTE, BUS_ERROR_ACCESS_DATA, 0); + return false; + } + LOG_TRACE(TRACE_ETHERNET, "Ethernet: SendPacket src %08x, len %x\n", st_buf_ptr, buf_size); + sendPacket(ethX, buf_ptr, buf_size); + } + break; + + case XIF_GET_IPHOST: + LOG_TRACE(TRACE_ETHERNET, "Ethernet: XIF_GET_IPHOST\n"); + *retval = get_params(HOST_IP, stack); + break; + + case XIF_GET_IPATARI: + LOG_TRACE(TRACE_ETHERNET, "Ethernet: XIF_GET_IPATARI\n"); + *retval = get_params(ATARI_IP, stack); + break; + + case XIF_GET_NETMASK: + LOG_TRACE(TRACE_ETHERNET, "Ethernet: XIF_GET_NETMASK\n"); + *retval = get_params(NETMASK, stack); + break; + + default: + LOG_TRACE(TRACE_ETHERNET, "Ethernet: unsupported function %d\n", subid); + *retval = GEMDOS_EINVFN; + break; + } + + return true; +} + + +/* + * Initialization + */ +void nf_ethernet_init(void) +{ + struct EthernetHandler *handler; + int i; + + LOG_TRACE(TRACE_ETHERNET, "Ethernet: init\n"); + + pending_interrupts = 0; + for (i = 0; i < MAX_ETH; i++) + { + handler = ETHERNET_HANDLER_CLASSNAME(i); + if (!handler->open(handler)) + { + free(handler); + handler = NULL; + } + handlers[i] = handler; + } +} + + +/* + * Deinitialization + */ +void nf_ethernet_reset(void) +{ + int i; + + LOG_TRACE(TRACE_ETHERNET, "Ethernet: reset\n"); + + for (i = 0; i < MAX_ETH; i++) + { + /* Stop reception thread */ + struct EthernetHandler *handler = handlers[i]; + + if (handler) + { + stopThread(handler); + handler->close(handler); + free(handler); + handlers[i] = NULL; + } + } + pending_interrupts = 0; +} diff --git a/src/includes/configuration.h b/src/includes/configuration.h index a400e2c8..4a47e340 100644 --- a/src/includes/configuration.h +++ b/src/includes/configuration.h @@ -331,6 +331,17 @@ typedef struct char sMidiOutPortName[FILENAME_MAX]; } CNF_MIDI; +/* ethernet support */ +#define MAX_ETH 4 +typedef struct { + char type[32]; + char tunnel[16]; + char ip_host[16]; + char ip_atari[16]; + char netmask[16]; + char mac_addr[18]; +} CNF_ETHERNET; + /* Dialog System */ typedef enum @@ -427,6 +438,7 @@ typedef struct CNF_MIDI Midi; CNF_SYSTEM System; CNF_VIDEO Video; + CNF_ETHERNET Ethernet[MAX_ETH]; } CNF_PARAMS; diff --git a/src/includes/nf_ethernet.h b/src/includes/nf_ethernet.h new file mode 100644 index 00000000..c948b897 --- /dev/null +++ b/src/includes/nf_ethernet.h @@ -0,0 +1,36 @@ +/* + * nf_ethernet.h - Ethernet Card Emulation + * + * Copyright (c) 2007 ARAnyM dev team (see AUTHORS) + * + * This file is part of the ARAnyM project which builds a new and powerful + * TOS/FreeMiNT compatible virtual machine running on almost any hardware. + * + * ARAnyM 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. + * + * ARAnyM 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 ARAnyM. If not see <http://www.gnu.org/licenses/> + */ + +#ifndef HATARI_ETHERNET_H +#define HATARI_ETHERNET_H + +#if defined(__linux__) || defined(__CYGWIN32__) || defined(__WIN32__) || defined(__APPLE__) + +#define WITH_NF_ETHERNET 1 + +bool nf_ethernet(Uint32 stack, Uint32 subid, Uint32 *retval); +void nf_ethernet_init(void); +void nf_ethernet_reset(void); + +#endif + +#endif /* HATARI_ETHERNET_H */ diff --git a/src/reset.c b/src/reset.c index 3f668be5..9b548069 100644 --- a/src/reset.c +++ b/src/reset.c @@ -42,6 +42,7 @@ const char Reset_fileid[] = "Hatari reset.c"; #include "debugcpu.h" #include "debugdsp.h" #include "nf_scsidrv.h" +#include "nf_ethernet.h" /*-----------------------------------------------------------------------*/ /** @@ -116,7 +117,12 @@ static int Reset_ST(bool bCold) Midi_Reset(); #if defined(__linux__) - nf_scsidrv_reset(); + nf_scsidrv_reset(); +#endif + +#ifdef WITH_NF_ETHERNET + nf_ethernet_reset(); + nf_ethernet_init(); #endif /* Start HBL, Timer B and VBL interrupts with a 0 cycle delay */
Mail converted by MHonArc 2.6.19+ | http://listengine.tuxfamily.org/ |