Co-Authored-By: Alexander Babikov <2708460+lemondrops@users.noreply.github.com> Co-Authored-By: cold-brewed <47337035+cold-brewed@users.noreply.github.com>
499 lines
18 KiB
C
499 lines
18 KiB
C
/*
|
|
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
|
* running old operating systems and software designed for IBM
|
|
* PC systems and compatibles from 1981 through fairly recent
|
|
* system designs based on the PCI bus.
|
|
*
|
|
* This file is part of the 86Box distribution.
|
|
*
|
|
* Network Switch network driver
|
|
*
|
|
*
|
|
*
|
|
* Authors: cold-brewed
|
|
*
|
|
* Copyright 2024 cold-brewed
|
|
*/
|
|
|
|
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <errno.h>
|
|
#ifdef _WIN32
|
|
# define WIN32_LEAN_AND_MEAN
|
|
# include <windows.h>
|
|
# include <winsock2.h>
|
|
#else
|
|
# include <poll.h>
|
|
#endif
|
|
|
|
#define HAVE_STDARG_H
|
|
#include <86box/86box.h>
|
|
#include <86box/device.h>
|
|
#include <86box/thread.h>
|
|
#include <86box/timer.h>
|
|
#include <86box/network.h>
|
|
#include <86box/net_event.h>
|
|
#include "netswitch.h"
|
|
#include "networkmessage.pb.h"
|
|
|
|
enum {
|
|
NET_EVENT_STOP = 0,
|
|
NET_EVENT_TX,
|
|
NET_EVENT_RX,
|
|
NET_EVENT_SWITCH,
|
|
NET_EVENT_MAX
|
|
};
|
|
|
|
/* Special define for the windows portion. We only need to poll up to
|
|
* NET_EVENT_SWITCH. NET_EVENT_SWITCH gives us a different NET_EVENT_MAX
|
|
* excluding the others, and windows does not like polling events that
|
|
* do not exist. */
|
|
#define NET_EVENT_WIN_MAX NET_EVENT_SWITCH
|
|
|
|
#define SWITCH_PKT_BATCH NET_QUEUE_LEN
|
|
/* In µs, how often to send a keepalive and perform connection maintenance */
|
|
#define SWITCH_KEEPALIVE_INTERVAL 5000000
|
|
/* In ms, how long until we consider a connection gone? */
|
|
#define SWITCH_MAX_INTERVAL 10000
|
|
|
|
typedef struct {
|
|
void *nsconn;
|
|
uint8_t mac_addr[6];
|
|
netcard_t *card;
|
|
thread_t *poll_tid;
|
|
net_evt_t tx_event;
|
|
net_evt_t stop_event;
|
|
netpkt_t pktv[SWITCH_PKT_BATCH];
|
|
pc_timer_t stats_timer;
|
|
pc_timer_t maintenance_timer;
|
|
ns_rx_packet_t rx_packet;
|
|
char switch_type[16];
|
|
#ifdef _WIN32
|
|
HANDLE sock_event;
|
|
#endif
|
|
} net_netswitch_t;
|
|
|
|
// Used for debugging, needs to be moved to an official location
|
|
void print_packet(const netpkt_t netpkt) {
|
|
if(netpkt.len == 0) {
|
|
net_switch_log("Something is wrong, len is %d\n", netpkt.len);
|
|
return;
|
|
}
|
|
/* Temporarily disable log suppression for packet dumping to allow specific formatting */
|
|
pclog_toggle_suppr();
|
|
uint8_t linebuff[17] = "\0";
|
|
char src_mac_buf[32] = "";
|
|
char dst_mac_buf[32] = "";
|
|
for(int m_i=0; m_i < 6; m_i++) {
|
|
char src_octet[4];
|
|
char dst_octet[4];
|
|
snprintf(src_octet, sizeof(src_octet), "%02X%s", netpkt.data[m_i+6], m_i < 5 ? ":" : "");
|
|
strncat(src_mac_buf, src_octet, sizeof (src_mac_buf) - 1);
|
|
|
|
snprintf(dst_octet, sizeof(dst_octet), "%02X%s", netpkt.data[m_i], m_i < 5 ? ":" : "");
|
|
strncat(dst_mac_buf, dst_octet, sizeof (dst_mac_buf) - 1);
|
|
}
|
|
net_switch_log("%s -> %s\n\n", src_mac_buf, dst_mac_buf);
|
|
|
|
// Payload length (bytes 12-13 with zero index)
|
|
uint16_t payload_length = (netpkt.data[12] & 0xFF) << 8;
|
|
payload_length |= (netpkt.data[13] & 0xFF);
|
|
const uint16_t actual_length = netpkt.len - 14;
|
|
if(payload_length <= 1500) {
|
|
// 802.3 / 802.2
|
|
net_switch_log("Payload length according to frame: %i\n", payload_length);
|
|
// remaining length of packet (len - 14) to calculate padding
|
|
net_switch_log("Actual payload length: %i\n", actual_length);
|
|
if(payload_length <=46 ) {
|
|
net_switch_log("Likely has %d bytes padding\n", actual_length - payload_length);
|
|
}
|
|
} else {
|
|
// Type II
|
|
net_switch_log("EtherType: 0x%04X\n", payload_length);
|
|
}
|
|
// actual packet size
|
|
net_switch_log("Full frame size: %i\n", netpkt.len);
|
|
net_switch_log("\n");
|
|
|
|
for(int i=0; i< netpkt.len; i++) {
|
|
|
|
net_switch_log("%02x ", netpkt.data[i]);
|
|
if ((netpkt.data[i] < 0x20) || (netpkt.data[i] > 0x7e)) {
|
|
linebuff[i % 16] = '.';
|
|
} else {
|
|
linebuff[i % 16] = netpkt.data[i];
|
|
}
|
|
|
|
if( (i+1) % 8 == 0) {
|
|
net_switch_log(" ");
|
|
}
|
|
|
|
if( (i+1) % 16 == 0) {
|
|
net_switch_log("| %s |\n", (char *)linebuff);
|
|
linebuff[0] = '\0';
|
|
}
|
|
|
|
// last char?
|
|
if(i+1 == netpkt.len) {
|
|
const int togo = 16 - (i % 16);
|
|
for(int remaining = 0; remaining < togo-1; remaining++) {
|
|
// This would represent the byte display and the space
|
|
net_switch_log(" ");
|
|
}
|
|
// spacing between byte groupings
|
|
if(togo > 8) {
|
|
net_switch_log(" ");
|
|
}
|
|
linebuff[(i % 16) +1] = '\0';
|
|
net_switch_log(" | %s", (char *)linebuff);
|
|
|
|
for(int remaining = 0; remaining < togo-1; remaining++) {
|
|
// This would represent the remaining bytes on the right
|
|
net_switch_log(" ");
|
|
}
|
|
net_switch_log(" |\n");
|
|
}
|
|
}
|
|
net_switch_log("\n");
|
|
pclog_toggle_suppr();
|
|
}
|
|
|
|
#ifdef ENABLE_NET_SWITCH_STATS
|
|
static void
|
|
stats_timer(void *priv)
|
|
{
|
|
/* Get the device state structure. */
|
|
net_netswitch_t *netswitch = priv;
|
|
const NSCONN *nsconn = netswitch->nsconn;
|
|
net_switch_log("Max (frame / packet) TX (%zu/%zu) RX (%zu/%zu)\n",
|
|
nsconn->stats.max_tx_frame, nsconn->stats.max_tx_packet,
|
|
nsconn->stats.max_rx_frame, nsconn->stats.max_rx_packet);
|
|
net_switch_log("Last ethertype (TX/RX) (%02x%02x/%02x%02x)\n", nsconn->stats.last_tx_ethertype[0], nsconn->stats.last_tx_ethertype[1],
|
|
nsconn->stats.last_rx_ethertype[0], nsconn->stats.last_rx_ethertype[1]);
|
|
net_switch_log("Packet totals (all/tx/rx/would fragment/max vec) (%zu/%zu/%zu/%zu/%i)\n", nsconn->stats.total_tx_packets + nsconn->stats.total_rx_packets,
|
|
nsconn->stats.total_tx_packets, nsconn->stats.total_rx_packets, nsconn->stats.total_fragments, nsconn->stats.max_vec);
|
|
net_switch_log("---\n");
|
|
/* Restart the timer */
|
|
timer_on_auto(&netswitch->stats_timer, 60000000);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
maintenance_timer(void *priv)
|
|
{
|
|
/* Get the device state structure. */
|
|
net_netswitch_t *netswitch = (net_netswitch_t *) priv;
|
|
NSCONN *nsconn = (NSCONN *) netswitch->nsconn;
|
|
if (!ns_send_control(nsconn, MessageType_MESSAGE_TYPE_KEEPALIVE)) {
|
|
net_switch_log("Failed to send keepalive packet\n");
|
|
}
|
|
const int64_t interval = ns_get_current_millis() - nsconn->last_packet_stamp;
|
|
// net_switch_log("Last packet time: %lld ago\n", interval);
|
|
// net_switch_log("Last packet time: %lld ago\n", interval);
|
|
|
|
/* A timeout has likely occurred, try to fix the connection if type is REMOTE */
|
|
if((interval > SWITCH_MAX_INTERVAL) && nsconn->switch_type == SWITCH_TYPE_REMOTE) {
|
|
/* FIXME: This is really rough, needs moar logic */
|
|
nsconn->client_state = CONNECTING;
|
|
net_switch_log("We appear to be disconnected, attempting to reconnect\n");
|
|
/* TODO: Proper connect function! This is duplicated code */
|
|
if(!ns_send_control(nsconn, MessageType_MESSAGE_TYPE_CONNECT_REQUEST)) {
|
|
/* TODO: Failure */
|
|
}
|
|
}
|
|
/* Restart the timer */
|
|
timer_on_auto(&netswitch->maintenance_timer, SWITCH_KEEPALIVE_INTERVAL);
|
|
}
|
|
|
|
/* Lots of #ifdef madness here thanks to the polling differences on windows */
|
|
static void
|
|
net_netswitch_thread(void *priv)
|
|
{
|
|
net_netswitch_t *net_netswitch = (net_netswitch_t *) priv;
|
|
NSCONN *nsconn = (NSCONN *) net_netswitch->nsconn;
|
|
bool status;
|
|
char switch_type[32];
|
|
snprintf(switch_type, sizeof(switch_type), "%s", nsconn->switch_type == SWITCH_TYPE_REMOTE ? "Remote" : "Local");
|
|
|
|
net_switch_log("%s Net Switch: polling started.\n", switch_type);
|
|
|
|
#ifdef _WIN32
|
|
WSAEventSelect(ns_pollfd(net_netswitch->nsconn), net_netswitch->sock_event, FD_READ);
|
|
|
|
HANDLE events[NET_EVENT_MAX];
|
|
events[NET_EVENT_STOP] = net_event_get_handle(&net_netswitch->stop_event);
|
|
events[NET_EVENT_TX] = net_event_get_handle(&net_netswitch->tx_event);
|
|
events[NET_EVENT_RX] = net_netswitch->sock_event;
|
|
|
|
bool run = true;
|
|
#else
|
|
struct pollfd pfd[NET_EVENT_MAX];
|
|
pfd[NET_EVENT_STOP].fd = net_event_get_fd(&net_netswitch->stop_event);
|
|
pfd[NET_EVENT_STOP].events = POLLIN | POLLPRI;
|
|
|
|
pfd[NET_EVENT_TX].fd = net_event_get_fd(&net_netswitch->tx_event);
|
|
pfd[NET_EVENT_TX].events = POLLIN | POLLPRI;
|
|
|
|
pfd[NET_EVENT_RX].fd = ns_pollfd(net_netswitch->nsconn);
|
|
pfd[NET_EVENT_RX].events = POLLIN | POLLPRI;
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
while (run) {
|
|
int ret = WaitForMultipleObjects(NET_EVENT_WIN_MAX, events, FALSE, INFINITE);
|
|
|
|
switch (ret - WAIT_OBJECT_0) {
|
|
#else
|
|
while (1) {
|
|
poll(pfd, NET_EVENT_MAX, -1);
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
case NET_EVENT_STOP:
|
|
net_event_clear(&net_netswitch->stop_event);
|
|
run = false;
|
|
break;
|
|
case NET_EVENT_TX:
|
|
#else
|
|
if (pfd[NET_EVENT_STOP].revents & POLLIN) {
|
|
net_event_clear(&net_netswitch->stop_event);
|
|
break;
|
|
}
|
|
if (pfd[NET_EVENT_TX].revents & POLLIN) {
|
|
#endif
|
|
net_event_clear(&net_netswitch->tx_event);
|
|
|
|
const int packets = network_tx_popv(net_netswitch->card, net_netswitch->pktv, SWITCH_PKT_BATCH);
|
|
if (packets > nsconn->stats.max_vec) {
|
|
nsconn->stats.max_vec = packets;
|
|
}
|
|
for (int i = 0; i < packets; i++) {
|
|
// net_switch_log("%d packet(s) to send\n", packets);
|
|
data_packet_info_t packet_info = get_data_packet_info(&net_netswitch->pktv[i], net_netswitch->mac_addr);
|
|
/* Temporarily disable log suppression for packet logging */
|
|
pclog_toggle_suppr();
|
|
net_switch_log("%s Net Switch: TX: %s\n", switch_type, packet_info.printable);
|
|
pclog_toggle_suppr();
|
|
#if defined(NET_PRINT_PACKET_TX) || defined(NET_PRINT_PACKET_ALL)
|
|
print_packet(net_netswitch->pktv[i]);
|
|
#endif
|
|
/* Only send if we're in a connected state (always true for local) */
|
|
if(ns_connected(net_netswitch->nsconn)) {
|
|
const ssize_t nc = ns_send_pb(net_netswitch->nsconn, &net_netswitch->pktv[i], 0);
|
|
if (nc < 1) {
|
|
perror("Got");
|
|
net_switch_log("%s Net Switch: Problem, no bytes sent. Got back %i\n", switch_type, nc);
|
|
}
|
|
}
|
|
}
|
|
#ifdef _WIN32
|
|
break;
|
|
case NET_EVENT_RX:
|
|
#else
|
|
}
|
|
if (pfd[NET_EVENT_RX].revents & POLLIN) {
|
|
#endif
|
|
|
|
/* Packets are available for reading */
|
|
status = ns_recv_pb(net_netswitch->nsconn, &net_netswitch->rx_packet, NET_MAX_FRAME, 0);
|
|
if (!status) {
|
|
net_switch_log("Receive packet failed. Skipping.\n");
|
|
continue;
|
|
}
|
|
|
|
/* These types are handled in the backend and don't need to be considered */
|
|
if (is_control_packet(&net_netswitch->rx_packet) || is_fragment_packet(&net_netswitch->rx_packet)) {
|
|
continue;
|
|
}
|
|
data_packet_info_t packet_info = get_data_packet_info(&net_netswitch->rx_packet.pkt, net_netswitch->mac_addr);
|
|
#if defined(NET_PRINT_PACKET_RX) || defined(NET_PRINT_PACKET_ALL)
|
|
print_packet(net_netswitch->rx_packet.pkt);
|
|
#endif
|
|
/*
|
|
* Accept packets that are
|
|
* Unicast for us
|
|
* Broadcasts that are not from us
|
|
* All other packets *if* promiscuous mode is enabled (excluding our own)
|
|
*/
|
|
if (packet_info.is_packet_for_me || (packet_info.is_broadcast && !packet_info.is_packet_from_me)) {
|
|
/* Temporarily disable log suppression for packet logging */
|
|
pclog_toggle_suppr();
|
|
net_switch_log("%s Net Switch: RX: %s\n", switch_type, packet_info.printable);
|
|
pclog_toggle_suppr();
|
|
network_rx_put_pkt(net_netswitch->card, &net_netswitch->rx_packet.pkt);
|
|
} else if (packet_info.is_packet_from_me) {
|
|
net_switch_log("%s Net Switch: Got my own packet... ignoring\n", switch_type);
|
|
} else {
|
|
/* Not our packet. Pass it along if promiscuous mode is enabled. */
|
|
if (ns_flags(net_netswitch->nsconn) & FLAGS_PROMISC) {
|
|
net_switch_log("%s Net Switch: Got packet from %s (not mine, promiscuous is set, getting)\n", switch_type, packet_info.src_mac_h);
|
|
network_rx_put_pkt(net_netswitch->card, &net_netswitch->rx_packet.pkt);
|
|
} else {
|
|
net_switch_log("%s Net Switch: RX: %s (not mine, dest %s != %s, promiscuous not set, ignoring)\n", switch_type, packet_info.printable, packet_info.dest_mac_h, packet_info.my_mac_h);
|
|
}
|
|
}
|
|
#ifdef _WIN32
|
|
break;
|
|
}
|
|
#else
|
|
}
|
|
#endif
|
|
}
|
|
|
|
net_switch_log("%s Net Switch: polling stopped.\n", switch_type);
|
|
}
|
|
|
|
void
|
|
net_netswitch_error(char *errbuf, const char *message) {
|
|
strncpy(errbuf, message, NET_DRV_ERRBUF_SIZE);
|
|
net_switch_log("Net Switch: %s\n", message);
|
|
}
|
|
|
|
void *
|
|
net_netswitch_init(const netcard_t *card, const uint8_t *mac_addr, void *priv, char *netdrv_errbuf)
|
|
{
|
|
net_switch_log("Net Switch: Init\n");
|
|
|
|
netcard_conf_t *netcard = (netcard_conf_t *) priv;
|
|
|
|
ns_flags_t flags = FLAGS_NONE;
|
|
ns_type_t switch_type;
|
|
|
|
const int net_type = netcard->net_type;
|
|
if(net_type == NET_TYPE_NRSWITCH) {
|
|
net_switch_log("Switch type: Remote\n");
|
|
switch_type = SWITCH_TYPE_REMOTE;
|
|
} else if (net_type == NET_TYPE_NMSWITCH) {
|
|
net_switch_log("Switch type: Local Multicast\n");
|
|
switch_type = SWITCH_TYPE_LOCAL;
|
|
if(netcard->promisc_mode) {
|
|
flags |= FLAGS_PROMISC;
|
|
}
|
|
} else {
|
|
net_switch_log("Failed: Unknown net switch type %d\n", net_type);
|
|
return NULL;
|
|
}
|
|
|
|
// FIXME: Only here during dev. This would be an error otherwise (hostname not specified)
|
|
if(strlen(netcard->nrs_hostname) == 0) {
|
|
strncpy(netcard->nrs_hostname, "127.0.0.1", 128 - 1);
|
|
}
|
|
|
|
net_netswitch_t *net_netswitch = calloc(1, sizeof(net_netswitch_t));
|
|
net_netswitch->card = (netcard_t *) card;
|
|
memcpy(net_netswitch->mac_addr, mac_addr, sizeof(net_netswitch->mac_addr));
|
|
snprintf(net_netswitch->switch_type, sizeof(net_netswitch->switch_type), "%s", net_type == NET_TYPE_NRSWITCH ? "Remote" : "Local");
|
|
|
|
// net_switch_log("%s Net Switch: mode: %d, group %d, hostname %s len %lu\n", net_netswitch->switch_type, netcard->promisc_mode, netcard->switch_group, netcard->nrs_hostname, strlen(netcard->nrs_hostname));
|
|
|
|
struct ns_open_args ns_args;
|
|
ns_args.type = switch_type;
|
|
/* Setting FLAGS_PROMISC here lets all packets through except the ones from us */
|
|
ns_args.flags = flags;
|
|
/* This option sets which switch group you want to be a part of.
|
|
* Functionally equivalent to being plugged into a different switch */
|
|
ns_args.group = netcard->switch_group;
|
|
/* You could also set the client_id here. If 0, it will be generated. */
|
|
ns_args.client_id = 0;
|
|
memcpy(ns_args.mac_addr, net_netswitch->mac_addr, 6);
|
|
/* The remote switch hostname */
|
|
strncpy(ns_args.nrs_hostname, netcard->nrs_hostname, sizeof(ns_args.nrs_hostname) - 1);
|
|
|
|
net_switch_log("%s Net Switch: Starting up virtual switch with group %d, flags %d\n", net_netswitch->switch_type, ns_args.group, ns_args.flags);
|
|
|
|
if ((net_netswitch->nsconn = ns_open(&ns_args)) == NULL) {
|
|
char buf[NET_DRV_ERRBUF_SIZE];
|
|
/* We're using some errnos for our own purposes */
|
|
switch (errno) {
|
|
case EFAULT:
|
|
snprintf(buf, NET_DRV_ERRBUF_SIZE, "Unable to open switch group %d: Cannot resolve remote switch hostname %s", ns_args.group, ns_args.nrs_hostname);
|
|
break;
|
|
default:
|
|
snprintf(buf, NET_DRV_ERRBUF_SIZE, "Unable to open switch group %d (%s)", ns_args.group, strerror(errno));
|
|
break;
|
|
|
|
}
|
|
net_netswitch_error(netdrv_errbuf, buf);
|
|
free(net_netswitch);
|
|
return NULL;
|
|
}
|
|
|
|
for (int i = 0; i < SWITCH_PKT_BATCH; i++) {
|
|
net_netswitch->pktv[i].data = calloc(1, NET_MAX_FRAME);
|
|
}
|
|
net_netswitch->rx_packet.pkt.data = calloc(1, NET_MAX_FRAME);
|
|
|
|
net_event_init(&net_netswitch->tx_event);
|
|
net_event_init(&net_netswitch->stop_event);
|
|
#ifdef _WIN32
|
|
net_netswitch->sock_event = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
#endif
|
|
net_netswitch->poll_tid = thread_create(net_netswitch_thread, net_netswitch);
|
|
|
|
/* Add the timers */
|
|
#ifdef ENABLE_NET_SWITCH_STATS
|
|
timer_add(&net_netswitch->stats_timer, stats_timer, net_netswitch, 0);
|
|
timer_on_auto(&net_netswitch->stats_timer, 5000000);
|
|
#endif
|
|
timer_add(&net_netswitch->maintenance_timer, maintenance_timer, net_netswitch, 0);
|
|
timer_on_auto(&net_netswitch->maintenance_timer, SWITCH_KEEPALIVE_INTERVAL);
|
|
|
|
/* Send join message. Return status not checked here. */
|
|
ns_send_control(net_netswitch->nsconn, MessageType_MESSAGE_TYPE_JOIN);
|
|
|
|
return net_netswitch;
|
|
}
|
|
|
|
void
|
|
net_netswitch_in_available(void *priv)
|
|
{
|
|
net_netswitch_t *net_netswitch = (net_netswitch_t *) priv;
|
|
net_event_set(&net_netswitch->tx_event);
|
|
}
|
|
|
|
void
|
|
net_netswitch_close(void *priv)
|
|
{
|
|
if (priv == NULL)
|
|
return;
|
|
|
|
net_netswitch_t *net_netswitch = (net_netswitch_t *) priv;
|
|
|
|
net_switch_log("%s Net Switch: closing.\n", net_netswitch->switch_type);
|
|
|
|
/* Tell the thread to terminate. */
|
|
net_event_set(&net_netswitch->stop_event);
|
|
|
|
/* Wait for the thread to finish. */
|
|
net_switch_log("%s Net Switch: waiting for thread to end...\n", net_netswitch->switch_type);
|
|
thread_wait(net_netswitch->poll_tid);
|
|
net_switch_log("%s Net Switch: thread ended\n", net_netswitch->switch_type);
|
|
|
|
for (int i = 0; i < SWITCH_PKT_BATCH; i++) {
|
|
free(net_netswitch->pktv[i].data);
|
|
}
|
|
free(net_netswitch->rx_packet.pkt.data);
|
|
|
|
net_event_close(&net_netswitch->tx_event);
|
|
net_event_close(&net_netswitch->stop_event);
|
|
|
|
#ifdef _WIN32
|
|
WSACleanup();
|
|
#endif
|
|
|
|
ns_close(net_netswitch->nsconn);
|
|
free(net_netswitch);
|
|
}
|
|
|
|
const netdrv_t net_netswitch_drv = {
|
|
.notify_in = &net_netswitch_in_available,
|
|
.init = &net_netswitch_init,
|
|
.close = &net_netswitch_close,
|
|
.priv = NULL,
|
|
};
|