/* * 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 #include #include #include #include #include #include #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN # include # include #else # include #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, };