diff --git a/src/interface.c b/src/interface.c index 7c420a4..484e6f5 100644 --- a/src/interface.c +++ b/src/interface.c @@ -1,5 +1,5 @@ /* IPXWrapper - Interface functions - * Copyright (C) 2011-2021 Daniel Collins + * Copyright (C) 2011-2023 Daniel Collins * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published by @@ -24,6 +24,7 @@ #include #include "interface.h" +#include "ipxwrapper.h" #include "common.h" #include "config.h" @@ -523,43 +524,50 @@ void ipx_interfaces_init(void) free(ip_ifaces); } - /* Virtual IPX interfaces... */ - - log_printf(LOG_INFO, "Listing IPX interfaces:"); - log_printf(LOG_INFO, "--"); - - ipx_interface_t *ipx_root = get_ipx_interfaces(), *ipx; - - if(!ipx_root) + if(ipx_encap_type == ENCAP_TYPE_DOSBOX) { - log_printf(LOG_INFO, "No IPX interfaces detected!"); - log_printf(LOG_INFO, "--"); + log_printf(LOG_INFO, "Using DOSBox server: %s port %hu", + main_config.dosbox_server_addr, main_config.dosbox_server_port); } - - DL_FOREACH(ipx_root, ipx) - { - char net[ADDR32_STRING_SIZE]; - addr32_string(net, ipx->ipx_net); + else{ + /* Virtual IPX interfaces... */ - char node[ADDR48_STRING_SIZE]; - addr48_string(node, ipx->ipx_node); + log_printf(LOG_INFO, "Listing IPX interfaces:"); + log_printf(LOG_INFO, "--"); - log_printf(LOG_INFO, "Network: %s", net); - log_printf(LOG_INFO, "Node: %s", node); + ipx_interface_t *ipx_root = get_ipx_interfaces(), *ipx; - ipx_interface_ip_t *ip; - - DL_FOREACH(ipx->ipaddr, ip) + if(!ipx_root) { - log_printf(LOG_INFO, "IP address: %s", inet_ntoa(*((struct in_addr*)&(ip->ipaddr)))); - log_printf(LOG_INFO, "Netmask: %s", inet_ntoa(*((struct in_addr*)&(ip->netmask)))); - log_printf(LOG_INFO, "Broadcast: %s", inet_ntoa(*((struct in_addr*)&(ip->bcast)))); + log_printf(LOG_INFO, "No IPX interfaces detected!"); + log_printf(LOG_INFO, "--"); } - log_printf(LOG_INFO, "--"); + DL_FOREACH(ipx_root, ipx) + { + char net[ADDR32_STRING_SIZE]; + addr32_string(net, ipx->ipx_net); + + char node[ADDR48_STRING_SIZE]; + addr48_string(node, ipx->ipx_node); + + log_printf(LOG_INFO, "Network: %s", net); + log_printf(LOG_INFO, "Node: %s", node); + + ipx_interface_ip_t *ip; + + DL_FOREACH(ipx->ipaddr, ip) + { + log_printf(LOG_INFO, "IP address: %s", inet_ntoa(*((struct in_addr*)&(ip->ipaddr)))); + log_printf(LOG_INFO, "Netmask: %s", inet_ntoa(*((struct in_addr*)&(ip->netmask)))); + log_printf(LOG_INFO, "Broadcast: %s", inet_ntoa(*((struct in_addr*)&(ip->bcast)))); + } + + log_printf(LOG_INFO, "--"); + } + + free_ipx_interface_list(&ipx_root); } - - free_ipx_interface_list(&ipx_root); } /* Release any resources used by the IPX interface cache. */ diff --git a/src/interface.h b/src/interface.h index 6295be4..288c4c4 100644 --- a/src/interface.h +++ b/src/interface.h @@ -1,5 +1,5 @@ /* IPXWrapper - Interface header - * Copyright (C) 2011-2021 Daniel Collins + * Copyright (C) 2011-2023 Daniel Collins * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published by @@ -81,7 +81,7 @@ extern enum main_config_encap_type ipx_encap_type; enum dosbox_state { DOSBOX_DISCONNECTED, - DOSBOX_RESOLVING, + /* DOSBOX_RESOLVING, */ DOSBOX_REGISTERING, DOSBOX_CONNECTED, }; diff --git a/src/ipxwrapper_stubs.txt b/src/ipxwrapper_stubs.txt index d2b308f..a1eea56 100644 --- a/src/ipxwrapper_stubs.txt +++ b/src/ipxwrapper_stubs.txt @@ -35,3 +35,4 @@ r_getpeername:4 inet_ntoa:4 __WSAFDIsSet:4 r_WSAAsyncSelect:4 +gethostbyname:4 diff --git a/src/router.c b/src/router.c index 857e9a5..e14499e 100644 --- a/src/router.c +++ b/src/router.c @@ -57,17 +57,20 @@ static HANDLE router_thread = NULL; SOCKET shared_socket = -1; SOCKET private_socket = -1; -#define DOSBOX_CONNECT_TIMEOUT_SECS 10 - struct sockaddr_in dosbox_server_addr; -static time_t dosbox_connect_begin; static HANDLE dosbox_ready_event = NULL; -static int dosbox_next_registration_request_at; -static int dosbox_registration_retry_interval_ms; +static uint64_t dosbox_next_connection_attempt_at; +static const unsigned int dosbox_connect_retry_interval_ms = 10000; -static const int INITIAL_DOSBOX_REGISTRATION_RETRY_INTERVAL_MS = 250; -static const int MAX_DOSBOX_REGISTRATION_RETRY_INTERVAL_MS = 8000; +static uint64_t dosbox_next_registration_request_at; +static unsigned int dosbox_registration_retry_interval_ms; +static uint64_t dosbox_registration_timeout_at; + +static const unsigned int INITIAL_DOSBOX_REGISTRATION_RETRY_INTERVAL_MS = 250; +static const unsigned int MAX_DOSBOX_REGISTRATION_RETRY_INTERVAL_MS = 8000; + +static const unsigned int DOSBOX_REGISTRATION_TIMEOUT_MS = 30000; static void _send_dosbox_registration_request(void); static DWORD router_main(void *arg); @@ -155,18 +158,9 @@ void router_init(void) abort(); } - /* TODO: Support DNS. Do this async somewhere within router_main. */ - _init_socket(&private_socket, 0, FALSE, FALSE); - dosbox_server_addr.sin_family = AF_INET; - dosbox_server_addr.sin_addr.s_addr = inet_addr(main_config.dosbox_server_addr); - dosbox_server_addr.sin_port = htons(main_config.dosbox_server_port); - - dosbox_next_registration_request_at = 0; - dosbox_registration_retry_interval_ms = INITIAL_DOSBOX_REGISTRATION_RETRY_INTERVAL_MS; - - dosbox_state = DOSBOX_REGISTERING; + dosbox_next_connection_attempt_at = 0; } else{ _init_socket(&shared_socket, main_config.udp_port, TRUE, TRUE); @@ -519,8 +513,7 @@ static void _handle_dosbox_registration_response(novell_ipx_packet *packet, size /* || packet->type != 2) */ { /* Doesn't look valid. */ - log_printf(LOG_ERROR, "Got invalid registration response from DOSBox server!"); - abort(); + log_printf(LOG_ERROR, "Got invalid registration response from DOSBox server, ignoring"); } dosbox_local_netnum = addr32_in(packet->dest_net); @@ -546,7 +539,7 @@ static void _handle_dosbox_recv(novell_ipx_packet *packet, size_t packet_size) if(packet_size < sizeof(novell_ipx_packet) || ntohs(packet->length) != packet_size) { /* Doesn't look valid. */ - log_printf(LOG_ERROR, "Recieved invalid IPX packet from DOSBox server, dropping"); + log_printf(LOG_ERROR, "Recieved invalid IPX packet from DOSBox server, ignoring"); return; } @@ -758,20 +751,32 @@ static DWORD router_main(void *arg) { DWORD wait_ms = 1000; - if(ipx_encap_type == ENCAP_TYPE_DOSBOX && dosbox_state == DOSBOX_REGISTERING) + if(ipx_encap_type == ENCAP_TYPE_DOSBOX) { - uint64_t now = get_ticks(); - - if(now >= dosbox_next_registration_request_at) + if(dosbox_state == DOSBOX_DISCONNECTED) { - wait_ms = 0; - } - else{ - wait_ms = dosbox_next_registration_request_at - 1; + uint64_t now = get_ticks(); - if(wait_ms > 1000) + if(now >= dosbox_next_connection_attempt_at) { - wait_ms = 1000; + wait_ms = 0; + } + else{ + wait_ms = dosbox_next_connection_attempt_at - now; + wait_ms = min(wait_ms, 1000); + } + } + else if(dosbox_state == DOSBOX_REGISTERING) + { + uint64_t now = get_ticks(); + + if(now >= dosbox_next_registration_request_at) + { + wait_ms = 0; + } + else{ + wait_ms = dosbox_next_registration_request_at - now; + wait_ms = min(wait_ms, 1000); } } } @@ -815,10 +820,48 @@ static DWORD router_main(void *arg) } } + if(ipx_encap_type == ENCAP_TYPE_DOSBOX && dosbox_state == DOSBOX_DISCONNECTED) + { + if(get_ticks() >= dosbox_next_connection_attempt_at) + { + struct hostent *host = gethostbyname(main_config.dosbox_server_addr); + + uint64_t now = get_ticks(); + + if(host != NULL) + { + dosbox_server_addr.sin_family = AF_INET; + memcpy(&(dosbox_server_addr.sin_addr), host->h_addr, 4); + dosbox_server_addr.sin_port = htons(main_config.dosbox_server_port); + + log_printf(LOG_INFO, "Resolved DOSBox server address %s, connecting...\n", inet_ntoa(dosbox_server_addr.sin_addr)); + + dosbox_next_registration_request_at = 0; + dosbox_registration_retry_interval_ms = INITIAL_DOSBOX_REGISTRATION_RETRY_INTERVAL_MS; + + dosbox_registration_timeout_at = now + DOSBOX_REGISTRATION_TIMEOUT_MS; + + dosbox_state = DOSBOX_REGISTERING; + } + else{ + DWORD error = WSAGetLastError(); + log_printf(LOG_ERROR, "Error resolving %s: %s (%u)", + main_config.dosbox_server_addr, w32_error(error), (unsigned int)(error)); + + dosbox_next_connection_attempt_at = now + dosbox_connect_retry_interval_ms; + } + } + } + if(ipx_encap_type == ENCAP_TYPE_DOSBOX && dosbox_state == DOSBOX_REGISTERING) { uint64_t now = get_ticks(); + if(now >= dosbox_registration_timeout_at) + { + log_printf(LOG_ERROR, "Connection to DOSBox server %s timed out", inet_ntoa(dosbox_server_addr.sin_addr)); + dosbox_state = DOSBOX_DISCONNECTED; + } if(now >= dosbox_next_registration_request_at) { _send_dosbox_registration_request(); diff --git a/tests/15-interfaces.t b/tests/15-interfaces.t index 2fe8033..243dd24 100644 --- a/tests/15-interfaces.t +++ b/tests/15-interfaces.t @@ -266,10 +266,10 @@ describe "IPXWrapper" => sub }; }; - describe "using DOSBox UDP encapsulation" => sub + my $dosbox_server; + + describe "using DOSBox UDP encapsulation (via IP address)" => sub { - my $dosbox_server; - before all => sub { reg_delete_key($remote_ip_a, "HKCU\\Software\\IPXWrapper"); @@ -277,6 +277,7 @@ describe "IPXWrapper" => sub reg_set_string($remote_ip_a, "HKCU\\Software\\IPXWrapper", "dosbox_server_addr", $local_ip_a); reg_set_dword( $remote_ip_a, "HKCU\\Software\\IPXWrapper", "dosbox_server_port", $dosbox_port); + $dosbox_server = undef; $dosbox_server = IPXWrapper::DOSBoxServer->new($dosbox_port); @expected_addrs = ( @@ -316,6 +317,90 @@ describe "IPXWrapper" => sub }; }; }; + + describe "using DOSBox UDP encapsulation (via DNS name)" => sub + { + before all => sub + { + reg_delete_key($remote_ip_a, "HKCU\\Software\\IPXWrapper"); + reg_set_dword( $remote_ip_a, "HKCU\\Software\\IPXWrapper", "use_pcap", ENCAP_TYPE_DOSBOX); + reg_set_string($remote_ip_a, "HKCU\\Software\\IPXWrapper", "dosbox_server_addr", "dosbox-ipv4.com"); + reg_set_dword( $remote_ip_a, "HKCU\\Software\\IPXWrapper", "dosbox_server_port", $dosbox_port); + + $dosbox_server = undef; + $dosbox_server = IPXWrapper::DOSBoxServer->new($dosbox_port); + + @expected_addrs = ( + { + # The node number is randomly selected by the DOSBox server + # when each client connects. + + net => "00:00:00:00", + maxpkt => DOSBOX_MAX_DATA_SIZE, + }, + ); + }; + + after all => sub + { + $dosbox_server = undef; + }; + + # Duplicate of common getsockopt block to skip the default interface selection + # logic test (because there is only ever one interface here). + describe "getsockopt" => sub + { + it "returns correct addresses" => sub + { + my @addrs = getsockopt_interfaces($remote_ip_a); + cmp_hashes_partial(\@addrs, \@expected_addrs); + }; + + it "returns correct IPX_MAX_ADAPTER_NUM" => sub + { + my @addrs = getsockopt_interfaces($remote_ip_a); + + my $output = run_remote_cmd($remote_ip_a, "Z:\\tools\\list-interfaces.exe"); + my ($got_num) = ($output =~ m/^IPX_MAX_ADAPTER_NUM = (\d+)$/m); + + is($got_num, (scalar @addrs)); + }; + }; + }; + + describe "using DOSBox UDP encapsulation (server is down)" => sub + { + before all => sub + { + reg_delete_key($remote_ip_a, "HKCU\\Software\\IPXWrapper"); + reg_set_dword( $remote_ip_a, "HKCU\\Software\\IPXWrapper", "use_pcap", ENCAP_TYPE_DOSBOX); + reg_set_string($remote_ip_a, "HKCU\\Software\\IPXWrapper", "dosbox_server_addr", $local_ip_a); + reg_set_dword( $remote_ip_a, "HKCU\\Software\\IPXWrapper", "dosbox_server_port", $dosbox_port); + + $dosbox_server = undef; + }; + + # Duplicate of common getsockopt block to skip the default interface selection + # logic test (because there is only ever one interface here). + describe "getsockopt" => sub + { + it "returns no addresses" => sub + { + my @addrs = getsockopt_interfaces($remote_ip_a); + cmp_deeply(\@addrs, []); + }; + + it "returns correct IPX_MAX_ADAPTER_NUM" => sub + { + my @addrs = getsockopt_interfaces($remote_ip_a); + + my $output = run_remote_cmd($remote_ip_a, "Z:\\tools\\list-interfaces.exe"); + my ($got_num) = ($output =~ m/^IPX_MAX_ADAPTER_NUM = (\d+)$/m); + + is($got_num, 0); + }; + }; + }; }; sub get_first_addr_node diff --git a/tests/30-dosbox-ipx.t b/tests/30-dosbox-ipx.t index c803dee..34bd458 100644 --- a/tests/30-dosbox-ipx.t +++ b/tests/30-dosbox-ipx.t @@ -45,7 +45,7 @@ describe "IPXWrapper using DOSBox UDP encapsulation" => sub { reg_delete_key($remote_ip_a, "HKCU\\Software\\IPXWrapper"); reg_set_dword( $remote_ip_a, "HKCU\\Software\\IPXWrapper", "use_pcap", ENCAP_TYPE_DOSBOX); - reg_set_string($remote_ip_a, "HKCU\\Software\\IPXWrapper", "dosbox_server_addr", $local_ip_a); + reg_set_string($remote_ip_a, "HKCU\\Software\\IPXWrapper", "dosbox_server_addr", "dosbox-ipv4.com"); reg_set_dword( $remote_ip_a, "HKCU\\Software\\IPXWrapper", "dosbox_server_port", $dosbox_port); };