mirror of
https://github.com/kanjitalk755/macemu.git
synced 2024-06-12 04:29:29 +00:00
385b49ba8e
Previously, when "ether tun" configuration was used on a Linux host, packets were sent with Ethernet/MAC address 00:00:00:00:00:00. Under CentOS 7 at least, this did not appear to cause any issues where the tun interface on the host was configured with an IP address, but when an attempt was made to bridge the tun interface, it was no longer possible to establish IP communication between the emulated machine and either the Linux host or a Windows NT Server 4 VM. This fix causes an Ethernet/MAC address to be generated in the same way when using TUN/TAP as is done for ethertap.
1052 lines
23 KiB
C++
1052 lines
23 KiB
C++
/*
|
|
* ether_unix.cpp - Ethernet device driver, Unix specific stuff (Linux and FreeBSD)
|
|
*
|
|
* Basilisk II (C) 1997-2008 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"
|
|
|
|
/*
|
|
* NOTES concerning MacOS X issues:
|
|
* - poll() does not exist in 10.2.8, but is available in 10.4.4
|
|
* - select(), and very likely poll(), are not cancellation points. So
|
|
* the ethernet thread doesn't stop on exit. An explicit check is
|
|
* performed to workaround this problem.
|
|
*/
|
|
#if (defined __APPLE__ && defined __MACH__) || ! defined HAVE_POLL
|
|
#define USE_POLL 0
|
|
#else
|
|
#define USE_POLL 1
|
|
#endif
|
|
|
|
// Define to let the slirp library determine the right timeout for select()
|
|
#define USE_SLIRP_TIMEOUT 1
|
|
|
|
#ifdef HAVE_SYS_POLL_H
|
|
#include <sys/poll.h>
|
|
#endif
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/wait.h>
|
|
#include <netinet/in.h>
|
|
#include <pthread.h>
|
|
#include <semaphore.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <signal.h>
|
|
#include <map>
|
|
|
|
#if defined(__FreeBSD__) || defined(sgi) || (defined(__APPLE__) && defined(__MACH__))
|
|
#include <net/if.h>
|
|
#endif
|
|
|
|
#if defined(HAVE_LINUX_IF_H) && defined(HAVE_LINUX_IF_TUN_H)
|
|
#include <linux/if.h>
|
|
#include <linux/if_tun.h>
|
|
#endif
|
|
|
|
#if defined(HAVE_NET_IF_H) && defined(HAVE_NET_IF_TUN_H)
|
|
#include <net/if.h>
|
|
#include <net/if_tun.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SLIRP
|
|
#include "libslirp.h"
|
|
#include "ctl.h"
|
|
#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 STATISTICS 0
|
|
#define MONITOR 0
|
|
|
|
|
|
// Ethernet device types
|
|
enum {
|
|
NET_IF_SHEEPNET,
|
|
NET_IF_ETHERTAP,
|
|
NET_IF_TUNTAP,
|
|
NET_IF_SLIRP
|
|
};
|
|
|
|
// Constants
|
|
#if ENABLE_TUNTAP
|
|
static const char ETHERCONFIG_FILE_NAME[] = DATADIR "/tunconfig";
|
|
#endif
|
|
|
|
// 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
|
|
static pthread_t slirp_thread; // Slirp reception thread
|
|
static bool slirp_thread_active = false; // Flag: Slirp reception threadinstalled
|
|
static int slirp_output_fd = -1; // fd of slirp output pipe
|
|
static int slirp_input_fds[2] = { -1, -1 }; // fds of slirp input pipe
|
|
#ifdef SHEEPSHAVER
|
|
static bool net_open = false; // Flag: initialization succeeded, network device open
|
|
static uint8 ether_addr[6]; // Our Ethernet address
|
|
#else
|
|
const bool ether_driver_opened = true; // Flag: is the MacOS driver opened?
|
|
#endif
|
|
|
|
// Attached network protocols, maps protocol type to MacOS handler address
|
|
static map<uint16, uint32> net_protocols;
|
|
|
|
// Prototypes
|
|
static void *receive_func(void *arg);
|
|
static void *slirp_receive_func(void *arg);
|
|
static int16 ether_do_add_multicast(uint8 *addr);
|
|
static int16 ether_do_del_multicast(uint8 *addr);
|
|
static int16 ether_do_write(uint32 arg);
|
|
static void ether_do_interrupt(void);
|
|
static void slirp_add_redirs();
|
|
static int slirp_add_redir(const char *redir_str);
|
|
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
#ifdef HAVE_SLIRP
|
|
if (net_if_type == NET_IF_SLIRP) {
|
|
slirp_thread_active = (pthread_create(&slirp_thread, NULL, slirp_receive_func, NULL) == 0);
|
|
if (!slirp_thread_active) {
|
|
printf("WARNING: Cannot start slirp reception thread\n");
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* Stop packet reception thread
|
|
*/
|
|
|
|
static void stop_thread(void)
|
|
{
|
|
#ifdef HAVE_SLIRP
|
|
if (slirp_thread_active) {
|
|
#ifdef HAVE_PTHREAD_CANCEL
|
|
pthread_cancel(slirp_thread);
|
|
#endif
|
|
pthread_join(slirp_thread, NULL);
|
|
slirp_thread_active = false;
|
|
}
|
|
#endif
|
|
|
|
if (thread_active) {
|
|
#ifdef HAVE_PTHREAD_CANCEL
|
|
pthread_cancel(ether_thread);
|
|
#endif
|
|
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 val;
|
|
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
|
|
#ifdef HAVE_SLIRP
|
|
else if (strcmp(name, "slirp") == 0)
|
|
net_if_type = NET_IF_SLIRP;
|
|
#endif
|
|
else
|
|
net_if_type = NET_IF_SHEEPNET;
|
|
|
|
// Don't raise SIGPIPE, let errno be set to EPIPE
|
|
struct sigaction sigpipe_sa;
|
|
if (sigaction(SIGPIPE, NULL, &sigpipe_sa) == 0) {
|
|
assert(sigpipe_sa.sa_handler == SIG_DFL || sigpipe_sa.sa_handler == SIG_IGN);
|
|
sigfillset(&sigpipe_sa.sa_mask);
|
|
sigpipe_sa.sa_flags = 0;
|
|
sigpipe_sa.sa_handler = SIG_IGN;
|
|
sigaction(SIGPIPE, &sigpipe_sa, NULL);
|
|
}
|
|
|
|
#ifdef HAVE_SLIRP
|
|
// Initialize slirp library
|
|
if (net_if_type == NET_IF_SLIRP) {
|
|
if (slirp_init() < 0) {
|
|
sprintf(str, "%s", GetString(STR_SLIRP_NO_DNS_FOUND_WARN));
|
|
WarningAlert(str);
|
|
return false;
|
|
}
|
|
|
|
// Open slirp output pipe
|
|
int fds[2];
|
|
if (pipe(fds) < 0)
|
|
return false;
|
|
fd = fds[0];
|
|
slirp_output_fd = fds[1];
|
|
|
|
// Open slirp input pipe
|
|
if (pipe(slirp_input_fds) < 0)
|
|
return false;
|
|
|
|
// Set up port redirects
|
|
slirp_add_redirs();
|
|
}
|
|
#endif
|
|
|
|
// Open sheep_net or ethertap or TUN/TAP device
|
|
char dev_name[16];
|
|
switch (net_if_type) {
|
|
case NET_IF_ETHERTAP:
|
|
sprintf(dev_name, "/dev/%s", name);
|
|
break;
|
|
case NET_IF_TUNTAP:
|
|
strcpy(dev_name, "/dev/net/tun");
|
|
break;
|
|
case NET_IF_SHEEPNET:
|
|
strcpy(dev_name, "/dev/sheep_net");
|
|
break;
|
|
}
|
|
if (net_if_type != NET_IF_SLIRP) {
|
|
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
|
|
#ifdef USE_FIONBIO
|
|
int nonblock = 1;
|
|
if (ioctl(fd, FIONBIO, &nonblock) < 0) {
|
|
sprintf(str, GetString(STR_BLOCKING_NET_SOCKET_WARN), strerror(errno));
|
|
WarningAlert(str);
|
|
goto open_error;
|
|
}
|
|
#else
|
|
val = fcntl(fd, F_GETFL, 0);
|
|
if (val < 0 || fcntl(fd, F_SETFL, val | O_NONBLOCK) < 0) {
|
|
sprintf(str, GetString(STR_BLOCKING_NET_SOCKET_WARN), strerror(errno));
|
|
WarningAlert(str);
|
|
goto open_error;
|
|
}
|
|
#endif
|
|
|
|
// Get Ethernet address
|
|
if (net_if_type == NET_IF_ETHERTAP || net_if_type == NET_IF_TUNTAP) {
|
|
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;
|
|
#ifdef HAVE_SLIRP
|
|
} else if (net_if_type == NET_IF_SLIRP) {
|
|
ether_addr[0] = 0x52;
|
|
ether_addr[1] = 0x54;
|
|
ether_addr[2] = 0x00;
|
|
ether_addr[3] = 0x12;
|
|
ether_addr[4] = 0x34;
|
|
ether_addr[5] = 0x56;
|
|
#endif
|
|
} 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;
|
|
}
|
|
if (slirp_input_fds[0] >= 0) {
|
|
close(slirp_input_fds[0]);
|
|
slirp_input_fds[0] = -1;
|
|
}
|
|
if (slirp_input_fds[1] >= 0) {
|
|
close(slirp_input_fds[1]);
|
|
slirp_input_fds[1] = -1;
|
|
}
|
|
if (slirp_output_fd >= 0) {
|
|
close(slirp_output_fd);
|
|
slirp_output_fd = -1;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* Deinitialization
|
|
*/
|
|
|
|
void ether_exit(void)
|
|
{
|
|
// Stop reception threads
|
|
stop_thread();
|
|
|
|
// 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);
|
|
|
|
// Close slirp input buffer
|
|
if (slirp_input_fds[0] >= 0)
|
|
close(slirp_input_fds[0]);
|
|
if (slirp_input_fds[1] >= 0)
|
|
close(slirp_input_fds[1]);
|
|
|
|
// Close slirp output buffer
|
|
if (slirp_output_fd > 0)
|
|
close(slirp_output_fd);
|
|
|
|
#if STATISTICS
|
|
// Show statistics
|
|
printf("%ld messages put on write queue\n", num_wput);
|
|
printf("%ld error acks\n", num_error_acks);
|
|
printf("%ld packets transmitted (%ld raw, %ld normal)\n", num_tx_packets, num_tx_raw_packets, num_tx_normal_packets);
|
|
printf("%ld tx packets dropped because buffer full\n", num_tx_buffer_full);
|
|
printf("%ld packets received\n", num_rx_packets);
|
|
printf("%ld packets passed upstream (%ld Fast Path, %ld normal)\n", num_rx_fastpath + num_unitdata_ind, num_rx_fastpath, num_unitdata_ind);
|
|
printf("EtherIRQ called %ld times\n", num_ether_irq);
|
|
printf("%ld rx packets dropped due to low memory\n", num_rx_no_mem);
|
|
printf("%ld rx packets dropped because no stream found\n", num_rx_dropped);
|
|
printf("%ld rx packets dropped because stream not ready\n", num_rx_stream_not_ready);
|
|
printf("%ld rx packets dropped because no memory for unitdata_ind\n", num_rx_no_unitdata_mem);
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
* Glue around low-level implementation
|
|
*/
|
|
|
|
#ifdef SHEEPSHAVER
|
|
// Error codes
|
|
enum {
|
|
eMultiErr = -91,
|
|
eLenErr = -92,
|
|
lapProtErr = -94,
|
|
excessCollsns = -95
|
|
};
|
|
|
|
// Initialize ethernet
|
|
void EtherInit(void)
|
|
{
|
|
net_open = false;
|
|
|
|
// Do nothing if the user disabled the network
|
|
if (PrefsFindBool("nonet"))
|
|
return;
|
|
|
|
net_open = ether_init();
|
|
}
|
|
|
|
// Exit ethernet
|
|
void EtherExit(void)
|
|
{
|
|
ether_exit();
|
|
net_open = false;
|
|
}
|
|
|
|
// Get ethernet hardware address
|
|
void AO_get_ethernet_address(uint32 arg)
|
|
{
|
|
uint8 *addr = Mac2HostAddr(arg);
|
|
if (net_open)
|
|
OTCopy48BitAddress(ether_addr, addr);
|
|
else {
|
|
addr[0] = 0x12;
|
|
addr[1] = 0x34;
|
|
addr[2] = 0x56;
|
|
addr[3] = 0x78;
|
|
addr[4] = 0x9a;
|
|
addr[5] = 0xbc;
|
|
}
|
|
D(bug("AO_get_ethernet_address: got address %02x%02x%02x%02x%02x%02x\n", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]));
|
|
}
|
|
|
|
// Add multicast address
|
|
void AO_enable_multicast(uint32 addr)
|
|
{
|
|
if (net_open)
|
|
ether_do_add_multicast(Mac2HostAddr(addr));
|
|
}
|
|
|
|
// Disable multicast address
|
|
void AO_disable_multicast(uint32 addr)
|
|
{
|
|
if (net_open)
|
|
ether_do_del_multicast(Mac2HostAddr(addr));
|
|
}
|
|
|
|
// Transmit one packet
|
|
void AO_transmit_packet(uint32 mp)
|
|
{
|
|
if (net_open) {
|
|
switch (ether_do_write(mp)) {
|
|
case noErr:
|
|
num_tx_packets++;
|
|
break;
|
|
case excessCollsns:
|
|
num_tx_buffer_full++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy packet data from message block to linear buffer
|
|
static inline int ether_arg_to_buffer(uint32 mp, uint8 *p)
|
|
{
|
|
return ether_msgb_to_buffer(mp, p);
|
|
}
|
|
|
|
// Ethernet interrupt
|
|
void EtherIRQ(void)
|
|
{
|
|
D(bug("EtherIRQ\n"));
|
|
num_ether_irq++;
|
|
|
|
OTEnterInterrupt();
|
|
ether_do_interrupt();
|
|
OTLeaveInterrupt();
|
|
|
|
// Acknowledge interrupt to reception thread
|
|
D(bug(" EtherIRQ done\n"));
|
|
sem_post(&int_ack);
|
|
}
|
|
#else
|
|
// Add multicast address
|
|
int16 ether_add_multicast(uint32 pb)
|
|
{
|
|
return ether_do_add_multicast(Mac2HostAddr(pb + eMultiAddr));
|
|
}
|
|
|
|
// Disable multicast address
|
|
int16 ether_del_multicast(uint32 pb)
|
|
{
|
|
return ether_do_del_multicast(Mac2HostAddr(pb + eMultiAddr));
|
|
}
|
|
|
|
// Transmit one packet
|
|
int16 ether_write(uint32 wds)
|
|
{
|
|
return ether_do_write(wds);
|
|
}
|
|
|
|
// Copy packet data from WDS to linear buffer
|
|
static inline int ether_arg_to_buffer(uint32 wds, uint8 *p)
|
|
{
|
|
return ether_wds_to_buffer(wds, p);
|
|
}
|
|
|
|
// Dispatch packet to protocol handler
|
|
static void ether_dispatch_packet(uint32 p, uint32 length)
|
|
{
|
|
// Get packet type
|
|
uint16 type = ReadMacInt16(p + 12);
|
|
|
|
// Look for protocol
|
|
uint16 search_type = (type <= 1500 ? 0 : type);
|
|
if (net_protocols.find(search_type) == net_protocols.end())
|
|
return;
|
|
uint32 handler = net_protocols[search_type];
|
|
|
|
// No default handler
|
|
if (handler == 0)
|
|
return;
|
|
|
|
// Copy header to RHA
|
|
Mac2Mac_memcpy(ether_data + ed_RHA, p, 14);
|
|
D(bug(" header %08x%04x %08x%04x %04x\n", ReadMacInt32(ether_data + ed_RHA), ReadMacInt16(ether_data + ed_RHA + 4), ReadMacInt32(ether_data + ed_RHA + 6), ReadMacInt16(ether_data + ed_RHA + 10), ReadMacInt16(ether_data + ed_RHA + 12)));
|
|
|
|
// Call protocol handler
|
|
M68kRegisters r;
|
|
r.d[0] = type; // Packet type
|
|
r.d[1] = length - 14; // Remaining packet length (without header, for ReadPacket)
|
|
r.a[0] = p + 14; // Pointer to packet (Mac address, for ReadPacket)
|
|
r.a[3] = ether_data + ed_RHA + 14; // Pointer behind header in RHA
|
|
r.a[4] = ether_data + ed_ReadPacket; // Pointer to ReadPacket/ReadRest routines
|
|
D(bug(" calling protocol handler %08x, type %08x, length %08x, data %08x, rha %08x, read_packet %08x\n", handler, r.d[0], r.d[1], r.a[0], r.a[3], r.a[4]));
|
|
Execute68k(handler, &r);
|
|
}
|
|
|
|
// Ethernet interrupt
|
|
void EtherInterrupt(void)
|
|
{
|
|
D(bug("EtherIRQ\n"));
|
|
ether_do_interrupt();
|
|
|
|
// Acknowledge interrupt to reception thread
|
|
D(bug(" EtherIRQ done\n"));
|
|
sem_post(&int_ack);
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Reset
|
|
*/
|
|
|
|
void ether_reset(void)
|
|
{
|
|
net_protocols.clear();
|
|
}
|
|
|
|
|
|
/*
|
|
* Add multicast address
|
|
*/
|
|
|
|
static int16 ether_do_add_multicast(uint8 *addr)
|
|
{
|
|
switch (net_if_type) {
|
|
case NET_IF_ETHERTAP:
|
|
case NET_IF_SHEEPNET:
|
|
if (ioctl(fd, SIOCADDMULTI, addr) < 0) {
|
|
D(bug("WARNING: Couldn't enable multicast address\n"));
|
|
if (net_if_type == NET_IF_ETHERTAP) {
|
|
return noErr;
|
|
} else {
|
|
return eMultiErr;
|
|
}
|
|
}
|
|
return noErr;
|
|
default:
|
|
return noErr;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Delete multicast address
|
|
*/
|
|
|
|
static int16 ether_do_del_multicast(uint8 *addr)
|
|
{
|
|
switch (net_if_type) {
|
|
case NET_IF_ETHERTAP:
|
|
case NET_IF_SHEEPNET:
|
|
if (ioctl(fd, SIOCDELMULTI, addr) < 0) {
|
|
D(bug("WARNING: Couldn't disable multicast address\n"));
|
|
return eMultiErr;
|
|
}
|
|
return noErr;
|
|
default:
|
|
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
|
|
*/
|
|
|
|
static int16 ether_do_write(uint32 arg)
|
|
{
|
|
// 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_arg_to_buffer(arg, p);
|
|
|
|
#if MONITOR
|
|
bug("Sending Ethernet packet:\n");
|
|
for (int i=0; i<len; i++) {
|
|
bug("%02x ", packet[i]);
|
|
}
|
|
bug("\n");
|
|
#endif
|
|
|
|
// Transmit packet
|
|
#ifdef HAVE_SLIRP
|
|
if (net_if_type == NET_IF_SLIRP) {
|
|
const int slirp_input_fd = slirp_input_fds[1];
|
|
write(slirp_input_fd, &len, sizeof(len));
|
|
write(slirp_input_fd, packet, len);
|
|
return noErr;
|
|
} else
|
|
#endif
|
|
if (write(fd, packet, len) < 0) {
|
|
D(bug("WARNING: Couldn't transmit packet\n"));
|
|
return excessCollsns;
|
|
} else
|
|
return noErr;
|
|
}
|
|
|
|
|
|
/*
|
|
* Start UDP packet reception thread
|
|
*/
|
|
|
|
bool ether_start_udp_thread(int socket_fd)
|
|
{
|
|
fd = socket_fd;
|
|
udp_tunnel = true;
|
|
return start_thread();
|
|
}
|
|
|
|
|
|
/*
|
|
* Stop UDP packet reception thread
|
|
*/
|
|
|
|
void ether_stop_udp_thread(void)
|
|
{
|
|
stop_thread();
|
|
fd = -1;
|
|
}
|
|
|
|
|
|
/*
|
|
* SLIRP output buffer glue
|
|
*/
|
|
|
|
#ifdef HAVE_SLIRP
|
|
int slirp_can_output(void)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
void slirp_output(const uint8 *packet, int len)
|
|
{
|
|
write(slirp_output_fd, packet, len);
|
|
}
|
|
|
|
void *slirp_receive_func(void *arg)
|
|
{
|
|
const int slirp_input_fd = slirp_input_fds[0];
|
|
|
|
for (;;) {
|
|
// Wait for packets to arrive
|
|
fd_set rfds, wfds, xfds;
|
|
int nfds;
|
|
struct timeval tv;
|
|
|
|
// ... in the input queue
|
|
FD_ZERO(&rfds);
|
|
FD_SET(slirp_input_fd, &rfds);
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 0;
|
|
if (select(slirp_input_fd + 1, &rfds, NULL, NULL, &tv) > 0) {
|
|
int len;
|
|
read(slirp_input_fd, &len, sizeof(len));
|
|
uint8 packet[1516];
|
|
assert(len <= sizeof(packet));
|
|
read(slirp_input_fd, packet, len);
|
|
slirp_input(packet, len);
|
|
}
|
|
|
|
// ... in the output queue
|
|
nfds = -1;
|
|
FD_ZERO(&rfds);
|
|
FD_ZERO(&wfds);
|
|
FD_ZERO(&xfds);
|
|
int timeout = slirp_select_fill(&nfds, &rfds, &wfds, &xfds);
|
|
#if ! USE_SLIRP_TIMEOUT
|
|
timeout = 10000;
|
|
#endif
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = timeout;
|
|
if (select(nfds + 1, &rfds, &wfds, &xfds, &tv) >= 0)
|
|
slirp_select_poll(&rfds, &wfds, &xfds);
|
|
|
|
#ifdef HAVE_PTHREAD_TESTCANCEL
|
|
// Explicit cancellation point if select() was not covered
|
|
// This seems to be the case on MacOS X 10.2
|
|
pthread_testcancel();
|
|
#endif
|
|
}
|
|
return NULL;
|
|
}
|
|
#else
|
|
int slirp_can_output(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void slirp_output(const uint8 *packet, int len)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Packet reception thread
|
|
*/
|
|
|
|
static void *receive_func(void *arg)
|
|
{
|
|
for (;;) {
|
|
|
|
// Wait for packets to arrive
|
|
#if USE_POLL
|
|
struct pollfd pf = {fd, POLLIN, 0};
|
|
int res = poll(&pf, 1, -1);
|
|
#else
|
|
fd_set rfds;
|
|
FD_ZERO(&rfds);
|
|
FD_SET(fd, &rfds);
|
|
// A NULL timeout could cause select() to block indefinitely,
|
|
// even if it is supposed to be a cancellation point [MacOS X]
|
|
struct timeval tv = { 0, 20000 };
|
|
int res = select(fd + 1, &rfds, NULL, NULL, &tv);
|
|
#ifdef HAVE_PTHREAD_TESTCANCEL
|
|
pthread_testcancel();
|
|
#endif
|
|
if (res == 0 || (res == -1 && errno == EINTR))
|
|
continue;
|
|
#endif
|
|
if (res <= 0)
|
|
break;
|
|
|
|
if (ether_driver_opened) {
|
|
// Trigger Ethernet interrupt
|
|
D(bug(" packet received, triggering Ethernet interrupt\n"));
|
|
SetInterruptFlag(INTFLAG_ETHER);
|
|
TriggerInterrupt();
|
|
|
|
// Wait for interrupt acknowledge by EtherInterrupt()
|
|
sem_wait(&int_ack);
|
|
} else
|
|
Delay_usec(20000);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* Ethernet interrupt - activate deferred tasks to call IODone or protocol handlers
|
|
*/
|
|
|
|
void ether_do_interrupt(void)
|
|
{
|
|
// Call protocol handler for received packets
|
|
EthernetPacket ether_packet;
|
|
uint32 packet = ether_packet.addr();
|
|
ssize_t length;
|
|
for (;;) {
|
|
|
|
#ifndef SHEEPSHAVER
|
|
if (udp_tunnel) {
|
|
|
|
// Read packet from socket
|
|
struct sockaddr_in from;
|
|
socklen_t from_len = sizeof(from);
|
|
length = recvfrom(fd, Mac2HostAddr(packet), 1514, 0, (struct sockaddr *)&from, &from_len);
|
|
if (length < 14)
|
|
break;
|
|
ether_udp_read(packet, length, &from);
|
|
|
|
} else
|
|
#endif
|
|
{
|
|
|
|
// Read packet from sheep_net device
|
|
#if defined(__linux__)
|
|
length = read(fd, Mac2HostAddr(packet), net_if_type == NET_IF_ETHERTAP ? 1516 : 1514);
|
|
#else
|
|
length = read(fd, Mac2HostAddr(packet), 1514);
|
|
#endif
|
|
if (length < 14)
|
|
break;
|
|
|
|
#if MONITOR
|
|
bug("Receiving Ethernet packet:\n");
|
|
for (int i=0; i<length; i++) {
|
|
bug("%02x ", ReadMacInt8(packet + i));
|
|
}
|
|
bug("\n");
|
|
#endif
|
|
|
|
// Pointer to packet data (Ethernet header)
|
|
uint32 p = packet;
|
|
#if defined(__linux__)
|
|
if (net_if_type == NET_IF_ETHERTAP) {
|
|
p += 2; // Linux ethertap has two random bytes before the packet
|
|
length -= 2;
|
|
}
|
|
#endif
|
|
|
|
// Dispatch packet
|
|
ether_dispatch_packet(p, length);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper function for port forwarding
|
|
static int get_str_sep(char *buf, int buf_size, const char **pp, int sep)
|
|
{
|
|
const char *p, *p1;
|
|
int len;
|
|
p = *pp;
|
|
p1 = strchr(p, sep);
|
|
if (!p1)
|
|
return -1;
|
|
len = p1 - p;
|
|
p1++;
|
|
if (buf_size > 0) {
|
|
if (len > buf_size - 1)
|
|
len = buf_size - 1;
|
|
memcpy(buf, p, len);
|
|
buf[len] = '\0';
|
|
}
|
|
*pp = p1;
|
|
return 0;
|
|
}
|
|
|
|
// Set up port forwarding for slirp
|
|
static void slirp_add_redirs()
|
|
{
|
|
int index = 0;
|
|
const char *str;
|
|
while ((str = PrefsFindString("redir", index++)) != NULL) {
|
|
slirp_add_redir(str);
|
|
}
|
|
}
|
|
|
|
// Add a port forward/redirection for slirp
|
|
static int slirp_add_redir(const char *redir_str)
|
|
{
|
|
// code adapted from qemu source
|
|
struct in_addr guest_addr = {0};
|
|
int host_port, guest_port;
|
|
const char *p;
|
|
char buf[256];
|
|
int is_udp;
|
|
char *end;
|
|
char str[256];
|
|
|
|
p = redir_str;
|
|
if (!p || get_str_sep(buf, sizeof(buf), &p, ':') < 0) {
|
|
goto fail_syntax;
|
|
}
|
|
if (!strcmp(buf, "tcp") || buf[0] == '\0') {
|
|
is_udp = 0;
|
|
} else if (!strcmp(buf, "udp")) {
|
|
is_udp = 1;
|
|
} else {
|
|
goto fail_syntax;
|
|
}
|
|
|
|
if (get_str_sep(buf, sizeof(buf), &p, ':') < 0) {
|
|
goto fail_syntax;
|
|
}
|
|
host_port = strtol(buf, &end, 0);
|
|
if (*end != '\0' || host_port < 1 || host_port > 65535) {
|
|
goto fail_syntax;
|
|
}
|
|
|
|
if (get_str_sep(buf, sizeof(buf), &p, ':') < 0) {
|
|
goto fail_syntax;
|
|
}
|
|
// 0.0.0.0 doesn't seem to work, so default to a client address
|
|
// if none is specified
|
|
if (buf[0] == '\0' ?
|
|
!inet_aton(CTL_LOCAL, &guest_addr) :
|
|
!inet_aton(buf, &guest_addr)) {
|
|
goto fail_syntax;
|
|
}
|
|
|
|
guest_port = strtol(p, &end, 0);
|
|
if (*end != '\0' || guest_port < 1 || guest_port > 65535) {
|
|
goto fail_syntax;
|
|
}
|
|
|
|
if (slirp_redir(is_udp, host_port, guest_addr, guest_port) < 0) {
|
|
sprintf(str, "could not set up host forwarding rule '%s'", redir_str);
|
|
WarningAlert(str);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
|
|
fail_syntax:
|
|
sprintf(str, "invalid host forwarding rule '%s'", redir_str);
|
|
WarningAlert(str);
|
|
return -1;
|
|
}
|