/* Created on: Sep 15, 2018 Copyright 2018 flyinghead This file is part of reicast. reicast 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. reicast 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 reicast. If not, see . */ #if defined(_WIN32) && !defined(FD_SETSIZE) /* The pico thread waits on all of its sockets at once; Winsock's own * limit for a select() is 64. */ #define FD_SETSIZE 256 #endif #if !defined(_MSC_VER) && !defined(TARGET_NO_THREADS) #include "stdclass.h" #include "hw/sh4/sh4_sched.h" #ifdef __MINGW32__ #define _POSIX_SOURCE #endif #ifdef __LIBRETRO__ #include #endif extern "C" { #include #include #include #include #include #include #include } #include "network/net_platform.h" #include "types.h" #include "picoppp.h" #include "netservice.h" #include "netthread.h" #include "network/miniupnp.h" #include "reios/reios.h" #include #include #define RESOLVER1_OPENDNS_COM "208.67.222.222" #define AFO_ORIG_IP 0x83f2fb3f // 63.251.242.131 in network order #define IGP_ORIG_IP 0xef2bd2cc // 204.210.43.239 in network order static pico_device *pico_dev; /* Bytes between the emulated modem (emulation thread) and the PPP stack * (pico thread). Each ring has one writer and one reader and takes no * lock. in_ring is kept small on purpose: when it fills, the stack waits * for the modem to read, which is the flow control the link needs. */ #define IN_RING_BYTES 1024 #define OUT_RING_BYTES 16384 static retro_spsc_t in_ring; /* stack -> modem */ static retro_spsc_t out_ring; /* modem -> stack */ static bool rings_ready; /* emulation thread only */ /* Where the stack sleeps while in_ring is full. */ static cEventCount in_ring_ec; /* Set by a network thread that does not sleep on in_ring_ec (DCNet) when * in_ring had no room: the modem wakes it once it has read some. */ static retro_atomic_int_t in_ring_blocked; /* Ethernet frames, for the broadband adapter. The adapter is emulated on * the emulation thread and the stack runs on the pico thread, and neither * may call into the other: the adapter's registers, its receive ring in * guest memory and its interrupt belong to one, the stack's queues to the * other. So a frame crosses as bytes in a ring, a 16-bit length and then * the frame, written whole or not at all. */ #define ETH_RING_BYTES 65536 #define ETH_FRAME_MAX NET_ETH_FRAME_MAX static retro_spsc_t eth_out_ring; /* adapter -> stack */ static retro_spsc_t eth_in_ring; /* stack -> adapter */ /* Set by the pico thread when the adapter's ring had no room for a frame; * the emulation thread wakes it once it has taken some out. */ static retro_atomic_int_t eth_in_blocked; /* What the thread is for, set before it starts: the broadband adapter * rather than the modem, DCNet rather than picoTCP. */ static bool thread_bba; static bool thread_dcnet; /* PPP: the stack says nothing until the Dreamcast has sent something. What * it sends before is dropped: Windows CE games, and a V.42 negotiation, * would take it for line noise. (pico thread) */ static bool ppp_passive; /* The pico thread does not tick on a timer. It waits, in select(), for the * things that give it work: its native sockets, the stack's next timer, * and this socket, which the emulation thread writes a byte to when it has * handed the stack something (modem bytes, an Ethernet frame) or wants the * thread to stop. It is a datagram socket on the loopback interface * connected to itself. * * So that the emulation thread does not make a system call for every byte * the modem sends, it only writes when the pico thread says it is waiting. * pico_kicks counts what has been handed over: the pico thread notes it * before it looks for work and, having said it is about to wait, does not * wait if the count has moved since. */ static sock_t wake_sock = INVALID_SOCKET; /* made by the emulation thread before the pico thread starts */ static retro_atomic_int_t pico_waiting; static retro_atomic_int_t pico_kicks; static void wake_pico() { retro_atomic_fetch_add_int(&pico_kicks, 1); if (retro_atomic_load_acquire_int(&pico_waiting)) send(wake_sock, "", 1, L_MSG_NOSIGNAL); } static pico_ip4 dcaddr; static pico_ip4 dnsaddr; static pico_socket *pico_tcp_socket, *pico_udp_socket; static pico_ip4 afo_ip; /* The most a native socket may be owed before the connection is given up. */ #define OUT_BUFFER_MAX (1024 * 1024) struct socket_pair { socket_pair() : pico_sock(nullptr), native_sock(INVALID_SOCKET) {} socket_pair(pico_socket *pico_sock, sock_t native_sock) : pico_sock(pico_sock), native_sock(native_sock) {} ~socket_pair() { if (pico_sock != nullptr) pico_socket_close(pico_sock); if (native_sock != INVALID_SOCKET) closesocket(native_sock); } socket_pair(socket_pair &&) = default; socket_pair(const socket_pair&) = delete; socket_pair& operator=(const socket_pair&) = delete; pico_socket *pico_sock; sock_t native_sock; std::vector in_buffer; bool shutdown = false; std::vector out_buffer; // from the Dreamcast, not yet taken by the native socket bool shut_wr = false; // the Dreamcast has closed its side: passed on once out_buffer has gone bool reading = false; // read_from_dc_socket is taking what the Dreamcast sent: a close waits for it /* Sends the native socket what it has not taken yet and then @len bytes * more. What it does not take now is kept, and the thread is woken when * the socket has room for it (see wait_for_work). False if the * connection is lost, or more is waiting than a live peer would leave. */ bool send_native(const char *data, size_t len) { const bool kept = !out_buffer.empty(); if (kept) { out_buffer.insert(out_buffer.end(), data, data + len); data = &out_buffer[0]; len = out_buffer.size(); } int r = (int)send(native_sock, data, len, L_MSG_NOSIGNAL); if (r < 0) { if (get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK) return false; r = 0; } if (kept) out_buffer.erase(out_buffer.begin(), out_buffer.begin() + r); else out_buffer.assign(data + r, data + len); pass_close(); return out_buffer.size() <= OUT_BUFFER_MAX; } /* Passes the Dreamcast's close on, once nothing it sent before closing * is left to read or to send. */ void pass_close() { if (shut_wr && !reading && out_buffer.empty()) { shut_wr = false; if (native_sock != INVALID_SOCKET) ::shutdown(native_sock, SHUT_WR); } } void receive_native() { size_t len; const char *data; char buf[536]; if (!in_buffer.empty()) { len = in_buffer.size(); data = &in_buffer[0]; } else { if (native_sock == INVALID_SOCKET) { if (!shutdown && pico_sock->q_out.size == 0) { pico_socket_shutdown(pico_sock, PICO_SHUT_RDWR); shutdown = true; } return; } int r = (int)recv(native_sock, buf, sizeof(buf), 0); if (r == 0) { INFO_LOG(MODEM, "Socket[%d] recv(%zd) returned 0 -> EOF", short_be(pico_sock->remote_port), sizeof(buf)); closesocket(native_sock); native_sock = INVALID_SOCKET; return; } if (r < 0) { if (get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK) { perror("recv tcp socket"); closesocket(native_sock); native_sock = INVALID_SOCKET; } return; } len = r; data = buf; } // What is waiting in in_buffer went through here when it was received if (data == buf && pico_sock->remote_port == short_be(5011) && len >= 5) { // Visual Concepts sport games if (buf[0] == 1) memcpy(&buf[1], &pico_sock->local_addr.ip4.addr, 4); } int r2 = pico_socket_send(pico_sock, data, (int)len); if (r2 < 0) INFO_LOG(MODEM, "error TCP sending: %s", strerror(pico_err)); else if (r2 < (int)len) { if (r2 > 0 || in_buffer.empty()) { len -= r2; std::vector remain(len); memcpy(&remain[0], &data[r2], len); std::swap(in_buffer, remain); } } else { in_buffer.clear(); } } }; // tcp sockets static std::map tcp_sockets; static std::map tcp_connecting_sockets; // udp sockets: src port -> socket fd static std::map udp_sockets; struct GamePortList { const char *gameId[10]; uint16_t udpPorts[10]; uint16_t tcpPorts[10]; }; static GamePortList GamesPorts[] = { { // Alien Front Online { "MK-51171" }, { 7980 }, { }, }, { // ChuChu Rocket { "MK-51049", "HDR-0039", "MK-5104950" }, { 9789 }, { }, }, { { "MK-51037", "HDR-0106", // Daytona USA "HDR-0073", // Sega Tetris "GENERIC", "T44501M" // Golf Shiyouyo 2 // (the dreamcastlive patched versions are id'ed as GENERIC) }, { 12079, 20675 }, { }, }, { // Dee Dee Planet { "HDR-0041" }, { 9879 }, { }, }, { // Driving Strikers online demo { "IND-161053" }, { 30099 }, { }, }, { // Floigan Bros { "MK-51114" }, { }, { 37001 }, }, { // Internet Game Pack { "MK-51138" }, { 5656 }, { 5011, 10500, 10501, 10502, 10503 }, }, { // NBA 2K1,2K2 / NFL 2K1,2K2 / NCAA 2K2 { "MK-51063", "HDR-0150", // NBA 2K1 "MK-51178", "HDR-0197", "MK-5117850", // NBA 2K2 "MK-51062", "HDR-0144", // NFL 2K1 "MK-51168", "HDR-0196", // NFL 2K2 "MK-51176" }, // NCAA 2K2 { 5502, 5503, 5656 }, { 5011, 6666 }, }, { // The Next Tetris { "T40214N", "T17717D 50" }, { 3512 }, { 3512 }, }, { // Ooga Booga { "MK-51140" }, { 6001 }, { }, }, { // PBA Tour Bowling 2001 { "T26702N" }, { 2300, 6500, 47624, 13139 }, // FIXME 2300-2400 ? { 2300, 47624 }, // FIXME 2300-2400 ? }, { // Planet Ring { "MK-5114864", "MK-5112550" }, { 7648, 1285, 1028 }, { }, }, { // StarLancer { "T40209N", "T17723D 05" }, { 2300, 6500, 47624 }, // FIXME 2300-2400 ? { 2300, 47624 }, // FIXME 2300-2400 ? }, { // World Series Baseball 2K2 { "MK-51152", "HDR-0198" }, { 37171, 13713 }, { }, }, { // Worms World Party { "T22904N", "T7016D 50" }, { }, { 17219 }, }, }; // listening port -> socket fd static std::map tcp_listening_sockets; static bool pico_stack_inited; static retro_atomic_int_t pico_thread_running; extern "C" { extern int dont_reject_opt_vj_hack; }; static void read_native_sockets(); void get_host_by_name(const char *name, pico_ip4 dnsaddr); int get_dns_answer(pico_ip4 *address, pico_ip4 dnsaddr); sock_t get_dns_socket(void); static int modem_read(pico_device *dev, void *data, int len) { int r = (int)retro_spsc_read(&out_ring, data, len); if (r > 0) ppp_passive = false; return r; } static int modem_write(pico_device *dev, const void *data, int len) { const u8 *p = (const u8 *)data; int left = len; if (ppp_passive) return len; while (left > 0) { int key; size_t n = retro_spsc_write(&in_ring, p, left); p += n; left -= (int)n; if (left == 0) break; /* Full: sleep until the modem has read some of it. */ key = in_ring_ec.Prepare(); if (!retro_atomic_load_acquire_int(&pico_thread_running)) { in_ring_ec.Cancel(); return 0; } if (retro_spsc_write_avail(&in_ring) > 0) in_ring_ec.Cancel(); else in_ring_ec.Commit(key, 5000); } return len; } void write_pico(u8 b) { if (rings_ready) { retro_spsc_write(&out_ring, &b, 1); wake_pico(); } } int read_pico() { u8 b; if (!rings_ready || retro_spsc_read(&in_ring, &b, 1) != 1) return -1; in_ring_ec.Notify(); if (retro_atomic_load_acquire_int(&in_ring_blocked)) { retro_atomic_store_release_int(&in_ring_blocked, 0); wake_pico(); } return b; } static void read_from_dc_socket(socket_pair& pair) { char buf[1510]; int r; /* When the Dreamcast closes with bytes still unread, the stack says so * from inside the read that comes next, whatever that read leaves * behind. The close is not passed on from there (see tcp_callback): it * would go out ahead of the bytes this read returns, and of the ones * after them. So once a close is owed, all there is gets read here. */ pair.reading = true; do { r = pico_socket_read(pair.pico_sock, buf, sizeof(buf)); if (r > 0 && !pair.send_native(buf, r)) { pico_socket *pico_sock = pair.pico_sock; perror("tcp_callback send"); tcp_sockets.erase(pico_sock); return; } /* All there is: a read event can stand for more than one read's * worth, and no other comes until more arrives (what the native * socket does not take is kept: see send_native()) */ } while (r > 0); pair.reading = false; pair.pass_close(); } static void tcp_callback(uint16_t ev, pico_socket *s) { if (ev & PICO_SOCK_EV_RD) { auto it = tcp_sockets.find(s); if (it == tcp_sockets.end()) { if (tcp_connecting_sockets.find(s) == tcp_connecting_sockets.end()) INFO_LOG(MODEM, "Unknown socket: remote port %d", short_be(s->remote_port)); } else { read_from_dc_socket(it->second); } } if (ev & PICO_SOCK_EV_CONN) { pico_ip4 orig; uint16_t port; char peer[30]; int yes = 1; pico_socket *sock_a = pico_socket_accept(s, &orig, &port); if (sock_a == NULL) { // Also called for child sockets if (tcp_sockets.find(s) == tcp_sockets.end()) INFO_LOG(MODEM, "pico_socket_accept: %s\n", strerror(pico_err)); } else { pico_ipv4_to_string(peer, sock_a->local_addr.ip4.addr); //printf("Connection established from port %d to %s:%d\n", short_be(port), peer, short_be(sock_a->local_port)); pico_socket_setoption(sock_a, PICO_TCP_NODELAY, &yes); pico_tcp_set_linger(sock_a, 10000); /* Set keepalive options */ // uint32_t ka_val = 5; // pico_socket_setoption(sock_a, PICO_SOCKET_OPT_KEEPCNT, &ka_val); // ka_val = 30000; // pico_socket_setoption(sock_a, PICO_SOCKET_OPT_KEEPIDLE, &ka_val); // ka_val = 5000; // pico_socket_setoption(sock_a, PICO_SOCKET_OPT_KEEPINTVL, &ka_val); sock_t sockfd = selectable_socket(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); if (!VALID(sockfd)) { perror("socket"); } else { sockaddr_in serveraddr; memset(&serveraddr, 0, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = sock_a->local_addr.ip4.addr; if (serveraddr.sin_addr.s_addr == AFO_ORIG_IP // Alien Front Online || serveraddr.sin_addr.s_addr == IGP_ORIG_IP) // Internet Game Pack { serveraddr.sin_addr.s_addr = afo_ip.addr; // same ip for both for now } serveraddr.sin_port = sock_a->local_port; set_non_blocking(sockfd); set_no_sigpipe(sockfd); if (connect(sockfd, (sockaddr *)&serveraddr, sizeof(serveraddr)) < 0) { if (get_last_error() != EINPROGRESS && get_last_error() != L_EWOULDBLOCK) { pico_ipv4_to_string(peer, sock_a->local_addr.ip4.addr); INFO_LOG(MODEM, "TCP connection to %s:%d failed: %s", peer, short_be(sock_a->local_port), strerror(get_last_error())); closesocket(sockfd); } else tcp_connecting_sockets[sock_a] = sockfd; } else { set_tcp_nodelay(sockfd); tcp_sockets.emplace(std::piecewise_construct, std::forward_as_tuple(sock_a), std::forward_as_tuple(sock_a, sockfd)); } } } } if (ev & PICO_SOCK_EV_FIN) { auto it = tcp_sockets.find(s); if (it != tcp_sockets.end()) { tcp_sockets.erase(it); } else { auto it2 = tcp_connecting_sockets.find(s); if (it2 != tcp_connecting_sockets.end()) { closesocket(it2->second); tcp_connecting_sockets.erase(it2); } else INFO_LOG(MODEM, "PICO_SOCK_EV_FIN: Unknown socket: remote port %d", short_be(s->remote_port)); } } if (ev & PICO_SOCK_EV_ERR) { INFO_LOG(MODEM, "Socket error received: %s", strerror(pico_err)); auto it = tcp_sockets.find(s); if (it == tcp_sockets.end()) INFO_LOG(MODEM, "PICO_SOCK_EV_ERR: Unknown socket: remote port %d", short_be(s->remote_port)); else tcp_sockets.erase(it); } if (ev & PICO_SOCK_EV_CLOSE) { auto it = tcp_sockets.find(s); if (it == tcp_sockets.end()) { INFO_LOG(MODEM, "PICO_SOCK_EV_CLOSE: Unknown socket: remote port %d", short_be(s->remote_port)); } else { // Not before the bytes still waiting for the native socket have // gone, nor those of a read this comes from the middle of if (!it->second.out_buffer.empty() || it->second.reading) it->second.shut_wr = true; else if (it->second.native_sock != INVALID_SOCKET) shutdown(it->second.native_sock, SHUT_WR); pico_socket_shutdown(s, PICO_SHUT_RD); } } // if (ev & PICO_SOCK_EV_WR) // { // } } static sock_t find_udp_socket(uint16_t src_port) { auto it = udp_sockets.find(src_port); if (it != udp_sockets.end()) return it->second; sock_t sockfd = selectable_socket(socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)); if (!VALID(sockfd)) { perror("socket"); return -1; } #ifndef _WIN32 fcntl(sockfd, F_SETFL, O_NONBLOCK); #else u_long optl = 1; ioctlsocket(sockfd, FIONBIO, &optl); #endif // The port the Dreamcast sends from, when it can be had: a server may // answer to the port the game is known to use and not to the one the // datagram came from (Toy Racer). Otherwise any port the system has. sockaddr_in saddr; memset(&saddr, 0, sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = INADDR_ANY; saddr.sin_port = src_port; if (::bind(sockfd, (sockaddr *)&saddr, sizeof(saddr)) < 0) { INFO_LOG(MODEM, "UDP port %d cannot be had: using another", short_be(src_port)); saddr.sin_port = 0; if (::bind(sockfd, (sockaddr *)&saddr, sizeof(saddr)) < 0) perror("bind"); } // FIXME Need to clean up at some point? udp_sockets[src_port] = sockfd; return sockfd; } static void udp_callback(uint16_t ev, pico_socket *s) { if (ev & PICO_SOCK_EV_RD) { char buf[1510]; pico_ip4 src_addr; uint16_t src_port; pico_msginfo msginfo; int r = 0; while (true) { r = pico_socket_recvfrom_extended(s, buf, sizeof(buf), &src_addr.addr, &src_port, &msginfo); if (r <= 0) { if (r < 0) INFO_LOG(MODEM, "error UDP recv: %s", strerror(pico_err)); break; } DEBUG_LOG(MODEM, "udp_callback(read) recvd port %d: %d bytes", short_be(src_port), r); sock_t sockfd = find_udp_socket(src_port); if (VALID(sockfd)) { sockaddr_in dst_addr; socklen_t addr_len = sizeof(dst_addr); memset(&dst_addr, 0, sizeof(dst_addr)); dst_addr.sin_family = AF_INET; dst_addr.sin_addr.s_addr = msginfo.local_addr.ip4.addr; dst_addr.sin_port = msginfo.local_port; if (sendto(sockfd, buf, r, L_MSG_NOSIGNAL, (const sockaddr *)&dst_addr, addr_len) < 0) perror("sendto udp socket"); } } } if (ev & PICO_SOCK_EV_ERR) { INFO_LOG(MODEM, "UDP Callback error received"); } } static void read_native_sockets() { int r; sockaddr_in src_addr; socklen_t addr_len; // Accept incoming TCP connections for (auto it = tcp_listening_sockets.begin(); it != tcp_listening_sockets.end(); it++) { addr_len = sizeof(src_addr); memset(&src_addr, 0, addr_len); sock_t sockfd = selectable_socket(accept(it->second, (sockaddr *)&src_addr, &addr_len)); if (!VALID(sockfd)) { if (get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK) perror("accept"); continue; } //printf("Incoming TCP connection from %08x to port %d\n", src_addr.sin_addr.s_addr, short_be(it->first)); pico_socket *ps = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_TCP, &tcp_callback); if (ps == NULL) { INFO_LOG(MODEM, "pico_socket_open failed: error %d", pico_err); closesocket(sockfd); continue; } ps->local_addr.ip4.addr = src_addr.sin_addr.s_addr; ps->local_port = src_addr.sin_port; if (pico_socket_connect(ps, &dcaddr.addr, it->first) != 0) { INFO_LOG(MODEM, "pico_socket_connect failed: error %d", pico_err); closesocket(sockfd); pico_socket_close(ps); continue; } set_non_blocking(sockfd); set_tcp_nodelay(sockfd); set_no_sigpipe(sockfd); tcp_sockets.emplace(std::piecewise_construct, std::forward_as_tuple(ps), std::forward_as_tuple(ps, sockfd)); } // Check connecting outbound TCP sockets fd_set write_fds; FD_ZERO(&write_fds); int max_fd = -1; for (auto it = tcp_connecting_sockets.begin(); it != tcp_connecting_sockets.end(); it++) { FD_SET(it->second, &write_fds); max_fd = std::max(max_fd, (int)it->second); } timeval tv; tv.tv_sec = 0; tv.tv_usec = 0; if (select(max_fd + 1, NULL, &write_fds, NULL, &tv) > 0) { for (auto it = tcp_connecting_sockets.begin(); it != tcp_connecting_sockets.end(); ) { if (!FD_ISSET(it->second, &write_fds)) { it++; continue; } #ifdef _WIN32 char value; #else int value; #endif socklen_t l = sizeof(int); if (getsockopt(it->second, SOL_SOCKET, SO_ERROR, &value, &l) < 0 || value) { char peer[30]; pico_ipv4_to_string(peer, it->first->local_addr.ip4.addr); INFO_LOG(MODEM, "TCP connection to %s:%d failed: %s", peer, short_be(it->first->local_port), strerror(get_last_error())); pico_socket_close(it->first); closesocket(it->second); } else { set_tcp_nodelay(it->second); auto added = tcp_sockets.emplace(std::piecewise_construct, std::forward_as_tuple(it->first), std::forward_as_tuple(it->first, it->second)); read_from_dc_socket(added.first->second); } it = tcp_connecting_sockets.erase(it); } } char buf[1500]; pico_msginfo msginfo; // Read UDP sockets for (auto it = udp_sockets.begin(); it != udp_sockets.end(); it++) { if (!VALID(it->second)) continue; addr_len = sizeof(src_addr); memset(&src_addr, 0, addr_len); r = (int)recvfrom(it->second, buf, sizeof(buf), 0, (sockaddr *)&src_addr, &addr_len); if (r > 0) { msginfo.dev = pico_dev; msginfo.tos = 0; msginfo.ttl = 0; msginfo.local_addr.ip4.addr = src_addr.sin_addr.s_addr; msginfo.local_port = src_addr.sin_port; DEBUG_LOG(MODEM, "read_native_sockets UDP received %d bytes from %08x:%d", r, long_be(msginfo.local_addr.ip4.addr), short_be(msginfo.local_port)); int r2 = pico_socket_sendto_extended(pico_udp_socket, buf, r, &dcaddr, it->first, &msginfo); if (r2 < r) INFO_LOG(MODEM, "error UDP sending to %d: %s", short_be(it->first), strerror(pico_err)); } else if (r < 0 && get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK) { perror("recvfrom udp socket"); continue; } } // Read TCP sockets for (auto it = tcp_sockets.begin(); it != tcp_sockets.end(); ) { if (!it->second.out_buffer.empty() && VALID(it->second.native_sock) && !it->second.send_native(nullptr, 0)) { perror("tcp socket send"); it = tcp_sockets.erase(it); continue; } it->second.receive_native(); if (it->second.pico_sock == nullptr) it = tcp_sockets.erase(it); else it++; } } static void close_native_sockets() { for (auto it = udp_sockets.begin(); it != udp_sockets.end(); it++) closesocket(it->second); udp_sockets.clear(); tcp_sockets.clear(); for (auto it = tcp_connecting_sockets.begin(); it != tcp_connecting_sockets.end(); it++) { pico_socket_close(it->first); closesocket(it->second); } tcp_connecting_sockets.clear(); } static int modem_set_speed(pico_device *dev, uint32_t speed) { return 0; } static void check_dns_entries() { static uint32_t dns_query_start = 0; static uint32_t dns_query_attempts = 0; if (afo_ip.addr == 0) { if (!dns_query_start) { dns_query_start = PICO_TIME_MS(); get_host_by_name("auriga.segasoft.com", dnsaddr); // Alien Front Online server } else { if (get_dns_answer(&afo_ip, dnsaddr) == 0) { dns_query_attempts = 0; dns_query_start = 0; char afoip[16]; pico_ipv4_to_string(afoip, afo_ip.addr); INFO_LOG(MODEM, "AFO server IP is %s", afoip); } else { if (PICO_TIME_MS() - dns_query_start > 1000) { if (++dns_query_attempts >= 5) { u32 addr; pico_string_to_ipv4("146.185.135.179", &addr); // Default address memcpy(&afo_ip.addr, &addr, sizeof(addr)); dns_query_attempts = 0; } else // Retry dns_query_start = 0; } } } } } static pico_device *pico_eth_create() { pico_device *eth = (pico_device *)PICO_ZALLOC(sizeof(pico_device)); if (!eth) return nullptr; const u8 mac_addr[6] = { 0xc, 0xa, 0xf, 0xe, 0, 1 }; if (0 != pico_device_init(eth, "ETHPEER", mac_addr)) return nullptr; DEBUG_LOG(NETWORK, "Device %s created", eth->name); return eth; } // A capture of every frame of the adapter, for debugging: never in a release //#define BBA_PCAPNG_DUMP static FILE *pcapngDump; static void dumpFrame(const u8 *frame, u32 size) { #ifdef BBA_PCAPNG_DUMP if (pcapngDump == nullptr) { const char *home = getenv("HOME"); if (home == nullptr) return; std::string path = home + std::string("/bba.pcapng"); pcapngDump = fopen(path.c_str(), "wb"); if (pcapngDump == nullptr) return; u32 blockType = 0x0A0D0D0A; // Section Header Block fwrite(&blockType, sizeof(blockType), 1, pcapngDump); u32 blockLen = 28; fwrite(&blockLen, sizeof(blockLen), 1, pcapngDump); u32 magic = 0x1A2B3C4D; fwrite(&magic, sizeof(magic), 1, pcapngDump); u32 version = 1; // 1.0 fwrite(&version, sizeof(version), 1, pcapngDump); u64 sectionLength = ~0; // unspecified fwrite(§ionLength, sizeof(sectionLength), 1, pcapngDump); fwrite(&blockLen, sizeof(blockLen), 1, pcapngDump); blockType = 1; // Interface Description Block fwrite(&blockType, sizeof(blockType), 1, pcapngDump); blockLen = 20; fwrite(&blockLen, sizeof(blockLen), 1, pcapngDump); const u32 linkType = 1; // Ethernet fwrite(&linkType, sizeof(linkType), 1, pcapngDump); const u32 snapLen = 0; // no limit fwrite(&snapLen, sizeof(snapLen), 1, pcapngDump); // TODO options? if name, ip/mac address fwrite(&blockLen, sizeof(blockLen), 1, pcapngDump); } const u32 blockType = 3; // Simple Packet Block fwrite(&blockType, sizeof(blockType), 1, pcapngDump); u32 roundedSize = ((size + 3) & ~3) + 16; fwrite(&roundedSize, sizeof(roundedSize), 1, pcapngDump); fwrite(&size, sizeof(size), 1, pcapngDump); fwrite(frame, 1, size, pcapngDump); fwrite(frame, 1, roundedSize - size - 16, pcapngDump); fwrite(&roundedSize, sizeof(roundedSize), 1, pcapngDump); #endif } static void closeDumpFile() { if (pcapngDump != nullptr) { fclose(pcapngDump); pcapngDump = nullptr; } } /* Emulation thread: the adapter has sent a frame. */ void pico_receive_eth_frame(const u8 *frame, u32 size) { u8 buf[2 + ETH_FRAME_MAX]; if (!rings_ready || size == 0 || size > ETH_FRAME_MAX) return; // No room: dropped, as a switch with a full queue would if (retro_spsc_write_avail(ð_out_ring) < size + 2) return; buf[0] = (u8)size; buf[1] = (u8)(size >> 8); memcpy(&buf[2], frame, size); retro_spsc_write(ð_out_ring, buf, size + 2); wake_pico(); } /* Pico thread: give the stack the frames the adapter has sent. */ /* No more than this many in one go. A guest that sends without stopping * refills the ring as fast as it is emptied: taken until the ring was * empty, its frames kept this thread here for good, and the stack's * timers and the sockets - which come after - never had their turn (an * ARP request in such a flood got its first answer and no other). */ #define ETH_FRAMES_A_PASS 256 /* True if there may be more waiting: the caller comes back for them once * the rest has had its turn, without sleeping first. */ static bool take_eth_frames() { u8 buf[ETH_FRAME_MAX]; u8 len[2]; for (int taken = 0; taken < ETH_FRAMES_A_PASS; taken++) { if (retro_spsc_read(ð_out_ring, len, 2) != 2) return false; const u32 size = len[0] | (len[1] << 8); if (size > ETH_FRAME_MAX || retro_spsc_read(ð_out_ring, buf, size) != size) return false; dumpFrame(buf, size); if (pico_dev != nullptr) pico_stack_recv(pico_dev, buf, size); } return true; } /* Pico thread: the stack has a frame for the adapter. */ static int send_eth_frame(pico_device *dev, void *data, int len) { u8 buf[2 + ETH_FRAME_MAX]; if (len <= 0 || len > ETH_FRAME_MAX) return len; // nothing the adapter could take: let the stack drop it if (retro_spsc_write_avail(ð_in_ring) < (size_t)len + 2) { // The stack keeps the frame and tries again when it is next woken retro_atomic_store_release_int(ð_in_blocked, 1); if (retro_spsc_write_avail(ð_in_ring) < (size_t)len + 2) return 0; } dumpFrame((const u8 *)data, len); buf[0] = (u8)len; buf[1] = (u8)(len >> 8); memcpy(&buf[2], data, len); retro_spsc_write(ð_in_ring, buf, len + 2); return len; } /* Emulation thread: the next frame the stack has sent the adapter, into * @frame (ETH_FRAME_MAX bytes). Its size, 0 if there is none. */ u32 net_eth_take(u8 *frame) { u8 len[2]; if (!rings_ready || retro_spsc_read(ð_in_ring, len, 2) != 2) return 0; u32 size = len[0] | (len[1] << 8); // (frames are written whole: this cannot be short) if (size > ETH_FRAME_MAX || retro_spsc_read(ð_in_ring, frame, size) != size) size = 0; if (retro_atomic_load_acquire_int(ð_in_blocked)) { retro_atomic_store_release_int(ð_in_blocked, 0); wake_pico(); } return size; } /* Waits until the thread has something to do: a native socket to read, * accept on or finish connecting, a timer of the stack's come due, * something handed over by the emulation thread, or the order to stop. * @kicks is pico_kicks as it was before the thread last looked for work. */ static void wait_for_work(int kicks) { fd_set readable, writable; timeval tv; char drain[64]; int max_fd = (int)wake_sock; int idle = pico_stack_idle_ms(); // The last tick moved a frame: there may be more if (idle == 0) return; // Never longer than a second, whatever the stack's timers say if (idle < 0 || idle > 1000) idle = 1000; FD_ZERO(&readable); FD_ZERO(&writable); FD_SET(wake_sock, &readable); for (auto& it : udp_sockets) if (VALID(it.second)) { FD_SET(it.second, &readable); max_fd = std::max(max_fd, (int)it.second); } for (auto& it : tcp_listening_sockets) { FD_SET(it.second, &readable); max_fd = std::max(max_fd, (int)it.second); } for (auto& it : tcp_sockets) { // One with bytes the stack has not taken yet is not read until it has if (VALID(it.second.native_sock) && it.second.in_buffer.empty()) { FD_SET(it.second.native_sock, &readable); max_fd = std::max(max_fd, (int)it.second.native_sock); } // One that is owed bytes: woken when it has room for them if (VALID(it.second.native_sock) && !it.second.out_buffer.empty()) { FD_SET(it.second.native_sock, &writable); max_fd = std::max(max_fd, (int)it.second.native_sock); } } for (auto& it : tcp_connecting_sockets) { FD_SET(it.second, &writable); max_fd = std::max(max_fd, (int)it.second); } if (afo_ip.addr == 0 && VALID(get_dns_socket())) { FD_SET(get_dns_socket(), &readable); max_fd = std::max(max_fd, (int)get_dns_socket()); } tv.tv_sec = idle / 1000; tv.tv_usec = (idle % 1000) * 1000; retro_atomic_fetch_add_int(&pico_waiting, 1); if (retro_atomic_load_acquire_int(&pico_thread_running) && retro_atomic_load_acquire_int(&pico_kicks) == kicks) select(max_fd + 1, &readable, &writable, NULL, &tv); retro_atomic_store_release_int(&pico_waiting, 0); while (recv(wake_sock, drain, sizeof(drain), 0) > 0) ; } /* For DCNet (dcnet.cpp), which runs on this thread in place of the stack: * the same rings, the same way of waiting. */ bool netthread_running() { return retro_atomic_load_acquire_int(&pico_thread_running) != 0; } int netthread_kicks() { return retro_atomic_load_acquire_int(&pico_kicks); } size_t netthread_modem_take(u8 *buf, size_t len) { return retro_spsc_read(&out_ring, buf, len); } size_t netthread_modem_give(const u8 *buf, size_t len) { size_t n = retro_spsc_write(&in_ring, buf, len); if (n < len) { // Full: the modem wakes the thread when it has read some (read_pico()) retro_atomic_store_release_int(&in_ring_blocked, 1); n += retro_spsc_write(&in_ring, buf + n, len - n); } return n; } u32 netthread_eth_take(u8 *frame) { u8 len[2]; if (retro_spsc_read(ð_out_ring, len, 2) != 2) return 0; const u32 size = len[0] | (len[1] << 8); if (size > ETH_FRAME_MAX || retro_spsc_read(ð_out_ring, frame, size) != size) return 0; return size; } bool netthread_eth_give(const u8 *frame, u32 size) { return send_eth_frame(nullptr, (void *)frame, (int)size) != 0; } void netthread_wait(int kicks, fd_set *readable, fd_set *writable, int max_fd, int timeout_ms) { timeval tv; char drain[64]; FD_SET(wake_sock, readable); max_fd = std::max(max_fd, (int)wake_sock); tv.tv_sec = timeout_ms / 1000; tv.tv_usec = (timeout_ms % 1000) * 1000; retro_atomic_fetch_add_int(&pico_waiting, 1); if (retro_atomic_load_acquire_int(&pico_thread_running) && retro_atomic_load_acquire_int(&pico_kicks) == kicks) select(max_fd + 1, readable, writable, NULL, &tv); retro_atomic_store_release_int(&pico_waiting, 0); while (recv(wake_sock, drain, sizeof(drain), 0) > 0) ; } /* The IPv4 address of @host, 0 if it has none. (This can take seconds: the * emulation thread never waits for the network thread but at its next * start, see stop_pico().) */ u32 netthread_resolve(const char *host) { u32 ip = 0; addrinfo hints; addrinfo *res = nullptr; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; if (getaddrinfo(host, nullptr, &hints, &res) == 0 && res != nullptr) { for (addrinfo *ai = res; ai != nullptr; ai = ai->ai_next) if (ai->ai_family == AF_INET && ai->ai_addrlen >= sizeof(sockaddr_in)) { ip = ((const sockaddr_in *)ai->ai_addr)->sin_addr.s_addr; break; } freeaddrinfo(res); } return ip; } /* The Dreamcast's DNS server: the one asked for, the current default for * the old one */ static void find_dns_server() { std::string name = settings.network.dns; if (name == "46.101.91.123") name = "dns.flyca.st"; u32 ip = 0; if (pico_string_to_ipv4(name.c_str(), &ip) != 0 || ip == 0) ip = netthread_resolve(name.c_str()); if (ip != 0) { char s[16]; memcpy(&dnsaddr.addr, &ip, sizeof(ip)); pico_ipv4_to_string(s, dnsaddr.addr); NOTICE_LOG(MODEM, "%s IP is %s", name.c_str(), s); } else { pico_string_to_ipv4("46.101.91.123", &ip); memcpy(&dnsaddr.addr, &ip, sizeof(ip)); WARN_LOG(MODEM, "Can't resolve %s. Using 46.101.91.123", name.c_str()); } } static void *pico_thread_func(void *) { if (thread_dcnet) { dcnet_run(thread_bba); return NULL; } const bool bba = thread_bba; // (the PPP stack's first words are to wait for the Dreamcast's) ppp_passive = !bba; if (!pico_stack_inited) { pico_stack_init(); pico_stack_inited = true; #if _WIN32 static WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0) WARN_LOG(MODEM, "WSAStartup failed"); #endif } // Find the network ports for the current game const GamePortList *ports = nullptr; std::string gameId(ip_meta.product_number, sizeof(ip_meta.product_number)); gameId = trim_trailing_ws(gameId); for (u32 i = 0; i < ARRAY_SIZE(GamesPorts) && ports == nullptr; i++) { const auto& game = GamesPorts[i]; for (u32 j = 0; j < ARRAY_SIZE(game.gameId) && game.gameId[j] != nullptr; j++) { if (gameId == game.gameId[j]) { NOTICE_LOG(MODEM, "Found network ports for game %s", gameId.c_str()); ports = &game; break; } } } // Web TV requires the VJ compression option, which picotcp doesn't support. // This hack allows WebTV to connect although the correct fix would // be to implement VJ compression. dont_reject_opt_vj_hack = gameId == "6107117" ? 1 : 0; // Initialize miniupnpc and map network ports MiniUPnP upnp; bool upnp_mapped = false; if (ports != nullptr) { if (!upnp.Init()) WARN_LOG(MODEM, "UPNP Init failed"); else { upnp_mapped = true; for (u32 i = 0; i < ARRAY_SIZE(ports->udpPorts) && ports->udpPorts[i] != 0; i++) if (!upnp.AddPortMapping(ports->udpPorts[i], false)) WARN_LOG(MODEM, "UPNP AddPortMapping UDP %d failed", ports->udpPorts[i]); for (u32 i = 0; i < ARRAY_SIZE(ports->tcpPorts) && ports->tcpPorts[i] != 0; i++) if (!upnp.AddPortMapping(ports->tcpPorts[i], true)) WARN_LOG(MODEM, "UPNP AddPortMapping TCP %d failed", ports->tcpPorts[i]); } } u32 addr; find_dns_server(); // Create ppp/eth device if (!bba) { // PPP pico_dev = pico_ppp_create(); if (!pico_dev) return NULL; pico_string_to_ipv4("192.168.167.2", &addr); memcpy(&dcaddr.addr, &addr, sizeof(addr)); pico_ppp_set_peer_ip(pico_dev, dcaddr); pico_string_to_ipv4("192.168.167.1", &addr); pico_ip4 ipaddr; memcpy(&ipaddr.addr, &addr, sizeof(addr)); pico_ppp_set_ip(pico_dev, ipaddr); pico_ppp_set_dns1(pico_dev, dnsaddr); pico_ppp_set_serial_read(pico_dev, modem_read); pico_ppp_set_serial_write(pico_dev, modem_write); pico_ppp_set_serial_set_speed(pico_dev, modem_set_speed); pico_dev->proxied = 1; pico_ppp_connect(pico_dev); } else { // Ethernet pico_dev = pico_eth_create(); if (pico_dev == nullptr) return nullptr; pico_dev->send = &send_eth_frame; pico_dev->proxied = 1; pico_string_to_ipv4("192.168.169.1", &addr); pico_ip4 ipaddr; memcpy(&ipaddr.addr, &addr, sizeof(addr)); pico_string_to_ipv4("255.255.255.0", &addr); pico_ip4 netmask; memcpy(&netmask.addr, &addr, sizeof(addr)); pico_ipv4_link_add(pico_dev, ipaddr, netmask); // dreamcast IP pico_string_to_ipv4("192.168.169.2", &addr); memcpy(&dcaddr.addr, &addr, sizeof(addr)); pico_dhcp_server_setting dhcpSettings{ 0 }; dhcpSettings.dev = pico_dev; dhcpSettings.server_ip = ipaddr; dhcpSettings.lease_time = long_be(24 * 60 * 60); // seconds dhcpSettings.netmask = netmask; dhcpSettings.pool_start = addr; dhcpSettings.pool_end = addr; dhcpSettings.pool_next = addr; dhcpSettings.dns_server.addr = dnsaddr.addr; if (pico_dhcp_server_initiate(&dhcpSettings) != 0) WARN_LOG(MODEM, "DHCP server init failed"); } pico_udp_socket = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_UDP, &udp_callback); if (pico_udp_socket == NULL) { INFO_LOG(MODEM, "error opening UDP socket: %s", strerror(pico_err)); return nullptr; } int yes = 1; pico_ip4 inaddr_any = {0}; uint16_t listen_port = 0; int ret = pico_socket_bind(pico_udp_socket, &inaddr_any, &listen_port); if (ret < 0) INFO_LOG(MODEM, "error binding UDP socket to port %u: %s", short_be(listen_port), strerror(pico_err)); pico_tcp_socket = pico_socket_open(PICO_PROTO_IPV4, PICO_PROTO_TCP, &tcp_callback); if (pico_tcp_socket == NULL) { INFO_LOG(MODEM, "error opening TCP socket: %s", strerror(pico_err)); } pico_socket_setoption(pico_tcp_socket, PICO_TCP_NODELAY, &yes); ret = pico_socket_bind(pico_tcp_socket, &inaddr_any, &listen_port); if (ret < 0) { INFO_LOG(MODEM, "error binding TCP socket to port %u: %s", short_be(listen_port), strerror(pico_err)); } else { if (pico_socket_listen(pico_tcp_socket, 10) != 0) INFO_LOG(MODEM, "error listening on port %u", short_be(listen_port)); } // Open listening sockets sockaddr_in saddr; socklen_t saddr_len = sizeof(saddr); memset(&saddr, 0, sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = INADDR_ANY; if (ports != nullptr) { for (u32 i = 0; i < ARRAY_SIZE(ports->udpPorts) && ports->udpPorts[i] != 0; i++) { // Bound to the port by find_udp_socket find_udp_socket(short_be(ports->udpPorts[i])); } for (u32 i = 0; i < ARRAY_SIZE(ports->tcpPorts) && ports->tcpPorts[i] != 0; i++) { uint16_t port = short_be(ports->tcpPorts[i]); saddr.sin_port = port; sock_t sockfd = selectable_socket(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); if (!VALID(sockfd)) { perror("socket"); continue; } set_reuse_addr(sockfd); if (::bind(sockfd, (sockaddr *)&saddr, saddr_len) < 0) { perror("bind"); closesocket(sockfd); continue; } if (listen(sockfd, 5) < 0) { perror("listen"); closesocket(sockfd); continue; } set_non_blocking(sockfd); tcp_listening_sockets[port] = sockfd; } } while (retro_atomic_load_acquire_int(&pico_thread_running)) { const int kicks = retro_atomic_load_acquire_int(&pico_kicks); const bool more_frames = take_eth_frames(); read_native_sockets(); pico_stack_tick(); check_dns_entries(); if (!more_frames) wait_for_work(kicks); } for (auto it = tcp_listening_sockets.begin(); it != tcp_listening_sockets.end(); it++) closesocket(it->second); // The next connection must not wait on, or accept from, these numbers tcp_listening_sockets.clear(); close_native_sockets(); pico_socket_close(pico_tcp_socket); pico_socket_close(pico_udp_socket); if (pico_dev) { if (!bba) { pico_ppp_destroy(pico_dev); } else { closeDumpFile(); pico_dhcp_server_destroy(pico_dev); pico_device_destroy(pico_dev); } pico_dev = nullptr; } pico_stack_tick(); if (upnp_mapped) upnp.Term(); return NULL; } static cThread pico_thread(pico_thread_func, NULL); static bool make_wake_socket() { sockaddr_in addr; socklen_t len = sizeof(addr); if (VALID(wake_sock)) return true; #ifdef _WIN32 WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0) return false; #endif sock_t sock = selectable_socket(socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)); if (!VALID(sock)) return false; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); if (::bind(sock, (sockaddr *)&addr, sizeof(addr)) < 0 || getsockname(sock, (sockaddr *)&addr, &len) < 0 || ::connect(sock, (sockaddr *)&addr, sizeof(addr)) < 0) { closesocket(sock); return false; } set_non_blocking(sock); wake_sock = sock; return true; } bool start_pico() { if (retro_atomic_load_relaxed_int(&pico_thread_running)) return false; // The last one, if it has not ended yet, is about to pico_thread.WaitToEnd(); /* The pico thread is not running, so both rings are this thread's * alone: make them, or empty them of the last connection's bytes. */ if (!rings_ready) { if (!retro_spsc_init(&in_ring, IN_RING_BYTES)) return false; if (!retro_spsc_init(&out_ring, OUT_RING_BYTES)) { retro_spsc_free(&in_ring); return false; } if (!retro_spsc_init(ð_out_ring, ETH_RING_BYTES)) { retro_spsc_free(&in_ring); retro_spsc_free(&out_ring); return false; } if (!retro_spsc_init(ð_in_ring, ETH_RING_BYTES)) { retro_spsc_free(&in_ring); retro_spsc_free(&out_ring); retro_spsc_free(ð_out_ring); return false; } rings_ready = true; } else { retro_spsc_clear(&in_ring); retro_spsc_clear(&out_ring); retro_spsc_clear(ð_out_ring); retro_spsc_clear(ð_in_ring); } retro_atomic_store_release_int(ð_in_blocked, 0); retro_atomic_store_release_int(&in_ring_blocked, 0); if (!make_wake_socket()) return false; thread_bba = net_device == NET_DEVICE_BBA; { std::string gameId(ip_meta.product_number, sizeof(ip_meta.product_number)); gameId = trim_trailing_ws(gameId); thread_dcnet = net_dcnet_option // these games' servers are reached through DCNet only || gameId == "HDR-0113" // Power Smash || gameId == "HDR-0091"; // Pro Yakyuu Team de Asobou Net! if (thread_dcnet) NOTICE_LOG(MODEM, "Network through DCNet"); } retro_atomic_store_release_int(&pico_waiting, 0); retro_atomic_store_release_int(&pico_thread_running, 1); pico_thread.Start(); return true; } /* The thread is told to stop and is not waited for: it may be looking up a * name or talking to the router (UPnP), which can take seconds. It is waited * for before the next one starts, and when the machine stops. */ void stop_pico() { retro_atomic_store_release_int(&pico_thread_running, 0); in_ring_ec.Notify(); if (VALID(wake_sock)) send(wake_sock, "", 1, L_MSG_NOSIGNAL); } void net_thread_join() { stop_pico(); pico_thread.WaitToEnd(); } #else #include "types.h" bool start_pico() { return false; } void stop_pico() { } void net_thread_join() { } void write_pico(u8 b) { } int read_pico() { return -1; } u32 net_eth_take(u8 *frame) { return 0; } void pico_receive_eth_frame(const u8 *frame, u32 size) { } #endif