///////////////////////////////////////////////////////////////////////// // $Id$ ///////////////////////////////////////////////////////////////////////// // // Copyright (C) 2011 Heikki Lindholm // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library 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 // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // // eth_slirp.cc - slirp backend to eth #define BX_PLUGGABLE #include "iodev.h" #include "netmod.h" #if BX_NETWORKING && BX_NETMOD_SLIRP #include #include #include #include #include #include #include #include /* ntohs, htons */ #define LOG_THIS netdev-> #define BX_ETH_SLIRP_LOGGING 0 #if defined(_MSC_VER) #pragma pack(push, 1) #elif defined(__MWERKS__) && defined(macintosh) #pragma options align=packed #endif // this should not be smaller than an arp reply with an ethernet header #define MIN_RX_PACKET_LEN 60 #define ETHERNET_MAC_ADDR_LEN 6 #define ETHERNET_TYPE_IPV4 0x0800 #define ETHERNET_TYPE_ARP 0x0806 typedef struct ethernet_header { #if defined(_MSC_VER) && (_MSC_VER>=1300) __declspec(align(1)) #endif Bit8u dst_mac_addr[ETHERNET_MAC_ADDR_LEN]; Bit8u src_mac_addr[ETHERNET_MAC_ADDR_LEN]; Bit16u type; } #if !defined(_MSC_VER) GCC_ATTRIBUTE((packed)) #endif ethernet_header_t; #define ARP_OPCODE_REQUEST 1 #define ARP_OPCODE_REPLY 2 typedef struct arp_header { #if defined(_MSC_VER) && (_MSC_VER>=1300) __declspec(align(1)) #endif Bit16u hw_addr_space; Bit16u proto_addr_space; Bit8u hw_addr_len; Bit8u proto_addr_len; Bit16u opcode; /* HW address of sender */ /* Protocol address of sender */ /* HW address of target*/ /* Protocol address of target */ } #if !defined(_MSC_VER) GCC_ATTRIBUTE((packed)) #endif arp_header_t; #if defined(_MSC_VER) #pragma pack(pop) #elif defined(__MWERKS__) && defined(macintosh) #pragma options align=reset #endif const Bit8u default_host_ipv4addr[4] = {10, 0, 2, 2}; const Bit8u default_dns_ipv4addr[4] = {10, 0, 2, 3}; const Bit8u default_guest_ipv4addr[4] = {10, 0, 2, 15}; const Bit8u broadcast_ipv4addr[3][4] = { { 0, 0, 0, 0}, {255,255,255,255}, { 10, 0, 2,255}, }; #define SLIP_END 192 #define SLIP_ESC 219 #define SLIP_ESC_END 220 #define SLIP_ESC_ESC 221 static size_t encode_slip(Bit8u *src, Bit8u *dst, size_t src_len) { Bit8u *dst_start = dst; Bit8u *src_end = src + src_len; while (src < src_end) { switch (*src) { case SLIP_END: *dst++ = SLIP_ESC; *dst++ = SLIP_ESC_END; break; case SLIP_ESC: *dst++ = SLIP_ESC; *dst++ = SLIP_ESC_ESC; break; default: *dst++ = *src; break; } src++; } *dst++ = SLIP_END; return dst - dst_start; } static int decode_slip(Bit8u *src, size_t *src_len, Bit8u *dst, size_t *dst_len) { Bit8u *dst_start = dst; Bit8u *src_start = src; Bit8u *src_end = src + *src_len; int got_packet = 0; if (*src_len == 0) { *dst_len = 0; return 0; } while ((src < (src_end - 1)) && (!got_packet)) { switch (*src) { case SLIP_END: if (dst != dst_start) // discard empty packets got_packet = 1; src++; break; case SLIP_ESC: switch (*++src) { case SLIP_ESC_ESC: *dst++ = SLIP_ESC; src++; break; case SLIP_ESC_END: *dst++ = SLIP_END; src++; break; default: *dst++ = *src++; break; } break; default: *dst++ = *src++; break; } } if ((!got_packet) && (src < src_end)) { switch (*src) { case SLIP_END: got_packet = 1; src++; break; case SLIP_ESC: break; default: *dst++ = *src++; break; } } *src_len = src - src_start; *dst_len = dst - dst_start; return got_packet; } class bx_slirp_pktmover_c : public eth_pktmover_c { public: bx_slirp_pktmover_c(const char *netif, const char *macaddr, eth_rx_handler_t rxh, eth_rx_status_t rxstat, bx_devmodel_c *dev, const char *script); void sendpkt(void *buf, unsigned io_len); private: pid_t slirp_pid; int slirp_pipe_fds[2]; Bit8u slip_output_buffer[4096]; // TODO: reasonable size for these? Bit8u slip_input_buffer[4096]; size_t slip_input_buffer_filled; size_t slip_input_buffer_decoded; Bit8u reply_buffer[1024]; int pending_reply_size; dhcp_cfg_t dhcp; tftp_data_t tftp; int rx_timer_index; unsigned netdev_speed; unsigned tx_time; static void rx_timer_handler(void *); void rx_timer(); bx_bool handle_ipv4(const Bit8u *buf, unsigned len); void handle_arp(void *buf, unsigned len); void prepare_builtin_reply(unsigned type); #if BX_ETH_SLIRP_LOGGING FILE *pktlog_txt; #endif }; class bx_slirp_locator_c : public eth_locator_c { public: bx_slirp_locator_c(void) : eth_locator_c("slirp") {} protected: eth_pktmover_c *allocate(const char *netif, const char *macaddr, eth_rx_handler_t rxh, eth_rx_status_t rxstat, bx_devmodel_c *dev, const char *script) { return (new bx_slirp_pktmover_c(netif, macaddr, rxh, rxstat, dev, script)); } } bx_slirp_match; bx_slirp_pktmover_c::bx_slirp_pktmover_c(const char *netif, const char *macaddr, eth_rx_handler_t rxh, eth_rx_status_t rxstat, bx_devmodel_c *dev, const char *script) { int flags; this->netdev = dev; BX_INFO(("slirp network driver")); if (socketpair(AF_UNIX, SOCK_STREAM, 0, slirp_pipe_fds)) BX_PANIC(("socketpair() failed: %s", strerror(errno))); // mark our end of the pipe non-blocking because of the timer based poll flags = fcntl(slirp_pipe_fds[0], F_GETFL); if (flags == -1) BX_PANIC(("fcntl(,F_GETFL) failed: %s", strerror(errno))); flags |= O_NONBLOCK; if (fcntl (slirp_pipe_fds[0], F_SETFL, flags) != 0) BX_PANIC(("fcntl(,F_SETFL,) failed: %s", strerror(errno))); slirp_pid = fork(); if (slirp_pid == -1) { BX_PANIC(("fork() failed: %s", strerror(errno))); } else if (slirp_pid == 0) { int ret; int nfd; nfd = open("/dev/null", O_RDWR); if (nfd != -1) { dup2(nfd, STDERR_FILENO); } if (dup2(slirp_pipe_fds[1], STDIN_FILENO) == -1) { BX_PANIC(("dup2() failed: %s", strerror(errno))); } /* XXX slirp seems to use stdin bidirectionally * instead of using stdout for SLIP output */ if (dup2(slirp_pipe_fds[1], STDOUT_FILENO) == -1) { BX_PANIC(("dup2() failed: %s", strerror(errno))); } close(slirp_pipe_fds[0]); ret = execlp(script == NULL ? "slirp" : script, script == NULL ? "slirp" : script, /*"-d", "-1", "-S",*/ NULL); if (ret == -1) { BX_PANIC(("execlp() failed: %s", strerror(errno))); } } this->rxh = rxh; this->rxstat = rxstat; strcpy(this->tftp.rootdir, netif); this->tftp.tid = 0; this->tftp.write = 0; Bit32u status = this->rxstat(this->netdev) & BX_NETDEV_SPEED; this->netdev_speed = (status == BX_NETDEV_1GBIT) ? 1000 : (status == BX_NETDEV_100MBIT) ? 100 : 10; this->rx_timer_index = bx_pc_system.register_timer(this, this->rx_timer_handler, 1000, 1, 1, "eth_slirp"); memcpy(&dhcp.guest_macaddr[0], macaddr, ETHERNET_MAC_ADDR_LEN); // ensure the slirp host has a different mac address memcpy(&dhcp.host_macaddr[0], macaddr, ETHERNET_MAC_ADDR_LEN); dhcp.host_macaddr[5] ^= 0x03; memcpy(&dhcp.host_ipv4addr[0], &default_host_ipv4addr[0], 4); memcpy(&dhcp.guest_ipv4addr[0], &broadcast_ipv4addr[1][0], 4); dhcp.default_guest_ipv4addr = default_guest_ipv4addr; memcpy(&dhcp.dns_ipv4addr[0], &default_dns_ipv4addr[0], 4); pending_reply_size = 0; slip_input_buffer_filled = slip_input_buffer_decoded = 0; close(slirp_pipe_fds[1]); #if BX_ETH_SLIRP_LOGGING pktlog_txt = fopen("ne2k-pktlog.txt", "wb"); if (!pktlog_txt) BX_PANIC(("ne2k-pktlog.txt failed")); fprintf(pktlog_txt, "slirp packetmover readable log file\n"); fprintf(pktlog_txt, "host MAC address = "); int i; for (i=0; i<6; i++) fprintf(pktlog_txt, "%02x%s", 0xff & host_mac_addr[i], i<5?":" : "\n"); fprintf(pktlog_txt, "guest MAC address = "); for (i=0; i<6; i++) fprintf(pktlog_txt, "%02x%s", 0xff & guest_mac_addr[i], i<5?":" : "\n"); fprintf(pktlog_txt, "--\n"); fflush(pktlog_txt); #endif } void bx_slirp_pktmover_c::handle_arp(void *buf, unsigned len) { arp_header_t *arphdr = (arp_header_t *)((Bit8u *)buf + sizeof(ethernet_header_t)); if (pending_reply_size > 0) return; if ((ntohs(arphdr->hw_addr_space) != 0x0001) || (ntohs(arphdr->proto_addr_space) != 0x0800) || (arphdr->hw_addr_len != ETHERNET_MAC_ADDR_LEN) || (arphdr->proto_addr_len != 4)) { BX_ERROR(("Unhandled ARP message hw: %04x (%d) proto: %04x (%d)\n", ntohs(arphdr->hw_addr_space), arphdr->hw_addr_len, ntohs(arphdr->proto_addr_space), arphdr->proto_addr_len)); return; } arp_header_t *arprhdr = (arp_header_t *)((Bit8u *)reply_buffer + sizeof(ethernet_header_t)); switch(ntohs(arphdr->opcode)) { case ARP_OPCODE_REQUEST: // Slirp uses addresses x.x.x.0 - x.x.x.3 if (((Bit8u *)arphdr)[27] > 3) break; memset(reply_buffer, 0, MIN_RX_PACKET_LEN); arprhdr->hw_addr_space = htons(0x0001); arprhdr->proto_addr_space = htons(0x0800); arprhdr->hw_addr_len = ETHERNET_MAC_ADDR_LEN; arprhdr->proto_addr_len = 4; arprhdr->opcode = htons(ARP_OPCODE_REPLY); memcpy((Bit8u *)arprhdr+8, dhcp.host_macaddr, ETHERNET_MAC_ADDR_LEN); memcpy((Bit8u *)arprhdr+14, (Bit8u *)arphdr+24, 4); memcpy((Bit8u *)arprhdr+18, dhcp.guest_macaddr, ETHERNET_MAC_ADDR_LEN); memcpy((Bit8u *)arprhdr+24, (Bit8u *)arphdr+14, 4); pending_reply_size = MIN_RX_PACKET_LEN; prepare_builtin_reply(ETHERNET_TYPE_ARP); break; case ARP_OPCODE_REPLY: break; default: break; } } // detect and handle DHCP request (partly copy & paste from eth_vnet.cc) bx_bool bx_slirp_pktmover_c::handle_ipv4(const Bit8u *buf, unsigned len) { unsigned total_len; unsigned fragment_flags; unsigned fragment_offset; unsigned ipproto; unsigned l3header_len; const Bit8u *l4pkt; unsigned l4pkt_len; unsigned udp_sourceport; unsigned udp_targetport; unsigned udp_reply_size; // guest-to-host IPv4 if (len < (14U+20U)) { return 0; } if ((buf[14+0] & 0xf0) != 0x40) { return 0; } l3header_len = ((unsigned)(buf[14+0] & 0x0f) << 2); if (l3header_len != 20) { return 0; } if (len < (14U+l3header_len)) return 0; if (ip_checksum(&buf[14],l3header_len) != (Bit16u)0xffff) { return 0; } total_len = get_net2(&buf[14+2]); if (memcmp(&buf[14+16],dhcp.host_ipv4addr, 4) && memcmp(&buf[14+16],broadcast_ipv4addr[0],4) && memcmp(&buf[14+16],broadcast_ipv4addr[1],4) && memcmp(&buf[14+16],broadcast_ipv4addr[2],4)) { return 0; } fragment_flags = (unsigned)buf[14+6] >> 5; fragment_offset = ((unsigned)get_net2(&buf[14+6]) & 0x1fff) << 3; ipproto = buf[14+9]; if ((fragment_flags & 0x1) || (fragment_offset != 0)) { return 0; } else { l4pkt = &buf[14 + l3header_len]; l4pkt_len = total_len - l3header_len; } if (ipproto == 0x11) { // guest-to-host UDP IPv4 if (l4pkt_len < 8) return 0; udp_sourceport = get_net2(&l4pkt[0]); udp_targetport = get_net2(&l4pkt[2]); if ((udp_targetport == 67) || (udp_targetport == 69)) { // BOOTP & TFTP if (udp_targetport == 67) { // BOOTP udp_reply_size = process_dhcp(netdev, &l4pkt[8], l4pkt_len-8, &reply_buffer[42], &dhcp); } else { udp_reply_size = process_tftp(netdev, &l4pkt[8], l4pkt_len-8, udp_sourceport, &reply_buffer[42], &tftp); } if (udp_reply_size > 0) { pending_reply_size = udp_reply_size + 42; // host-to-guest UDP IPv4: pseudo-header reply_buffer[22] = 0; reply_buffer[23] = 0x11; // UDP put_net2(&reply_buffer[24], 8U+udp_reply_size); memcpy(&reply_buffer[26], dhcp.host_ipv4addr, 4); memcpy(&reply_buffer[30], dhcp.guest_ipv4addr, 4); // udp header put_net2(&reply_buffer[34], udp_targetport); put_net2(&reply_buffer[36], udp_sourceport); put_net2(&reply_buffer[38], 8U+udp_reply_size); put_net2(&reply_buffer[40], 0); put_net2(&reply_buffer[40], ip_checksum(&reply_buffer[22], 12U+8U+udp_reply_size) ^ (Bit16u)0xffff); // ip header memset(&reply_buffer[14], 0, 20); reply_buffer[14] = 0x45; reply_buffer[15] = 0x00; put_net2(&reply_buffer[16], 20U+8U+udp_reply_size); put_net2(&reply_buffer[18], 1); reply_buffer[20] = 0x00; reply_buffer[21] = 0x00; reply_buffer[22] = 0x07; // TTL reply_buffer[23] = 0x11; // UDP // host-to-guest IPv4 reply_buffer[14] = (reply_buffer[14] & 0x0f) | 0x40; l3header_len = ((unsigned)(reply_buffer[14] & 0x0f) << 2); memcpy(&reply_buffer[26], &dhcp.host_ipv4addr[0], 4); memcpy(&reply_buffer[30], &dhcp.guest_ipv4addr[0], 4); put_net2(&reply_buffer[24], 0); put_net2(&reply_buffer[24], ip_checksum(&reply_buffer[14], l3header_len) ^ (Bit16u)0xffff); prepare_builtin_reply(ETHERNET_TYPE_IPV4); } // don't forward DHCP / TFTP requests to Slirp return 1; } } return 0; } void bx_slirp_pktmover_c::sendpkt(void *buf, unsigned io_len) { size_t len; ethernet_header_t *ethhdr = (ethernet_header_t *)buf; #if BX_ETH_SLIRP_LOGGING write_pktlog_txt(pktlog_txt, (Bit8u*)buf, io_len, 0); #endif this->tx_time = (64 + 96 + 4 * 8 + io_len * 8) / this->netdev_speed; switch (ntohs(ethhdr->type)) { case ETHERNET_TYPE_IPV4: if (!handle_ipv4((Bit8u*)buf, io_len)) { len = encode_slip((Bit8u *)buf+sizeof(ethernet_header_t), slip_output_buffer, io_len-sizeof(ethernet_header_t)); len = write(slirp_pipe_fds[0], slip_output_buffer, len); } break; case ETHERNET_TYPE_ARP: handle_arp(buf, io_len); break; default: break; } } void bx_slirp_pktmover_c::prepare_builtin_reply(unsigned type) { ethernet_header_t *ethhdr; unsigned rx_time; ethhdr = (ethernet_header_t *)reply_buffer; memcpy(ethhdr->dst_mac_addr, dhcp.guest_macaddr, ETHERNET_MAC_ADDR_LEN); memcpy(ethhdr->src_mac_addr, dhcp.host_macaddr, ETHERNET_MAC_ADDR_LEN); ethhdr->type = htons(type); rx_time = (64 + 96 + 4 * 8 + pending_reply_size * 8) / this->netdev_speed; bx_pc_system.activate_timer(this->rx_timer_index, this->tx_time + rx_time + 100, 0); } void bx_slirp_pktmover_c::rx_timer_handler(void *this_ptr) { bx_slirp_pktmover_c *class_ptr = (bx_slirp_pktmover_c *)this_ptr; class_ptr->rx_timer(); } void bx_slirp_pktmover_c::rx_timer() { Bit8u *packet; Bit8u padded_packet[MIN_RX_PACKET_LEN]; ethernet_header_t *ethhdr; size_t packet_len; if (pending_reply_size > 0) { #if BX_ETH_SLIRP_LOGGING write_pktlog_txt(pktlog_txt, reply_buffer, pending_reply_size, 1); #endif if (this->rxstat(this->netdev) & BX_NETDEV_RXREADY) { this->rxh(this->netdev, reply_buffer, pending_reply_size); } else { BX_ERROR(("device not ready to receive data")); } pending_reply_size = 0; // restore timer bx_pc_system.activate_timer(this->rx_timer_index, 1000, 1); return; } Bit8u *buf = slip_input_buffer + sizeof(ethernet_header_t); ssize_t n; int got_packet; size_t ilen, olen, pos; if (slip_input_buffer_filled + sizeof(ethernet_header_t) < sizeof(slip_input_buffer)) { n = read(slirp_pipe_fds[0], buf+slip_input_buffer_filled, (sizeof(slip_input_buffer) - sizeof(ethernet_header_t)) - slip_input_buffer_filled); if (n < 1) return; slip_input_buffer_filled += n; } packet = (Bit8u *)slip_input_buffer; ethhdr = (ethernet_header_t *)packet; memcpy(ethhdr->dst_mac_addr, dhcp.guest_macaddr, ETHERNET_MAC_ADDR_LEN); memcpy(ethhdr->src_mac_addr, dhcp.host_macaddr, ETHERNET_MAC_ADDR_LEN); ethhdr->type = htons(ETHERNET_TYPE_IPV4); olen = 0; pos = slip_input_buffer_decoded; do { ilen = slip_input_buffer_filled - slip_input_buffer_decoded; got_packet = decode_slip(buf + pos, &ilen, buf + slip_input_buffer_decoded, &olen); pos += ilen; slip_input_buffer_filled -= ilen; slip_input_buffer_filled += olen; slip_input_buffer_decoded += olen; if (got_packet) { packet_len = sizeof(ethernet_header_t) + slip_input_buffer_decoded; if (packet_len < MIN_RX_PACKET_LEN) { packet = padded_packet; bzero(packet, MIN_RX_PACKET_LEN); memcpy(packet, slip_input_buffer, packet_len); packet_len = MIN_RX_PACKET_LEN; } #if BX_ETH_SLIRP_LOGGING write_pktlog_txt(pktlog_txt, packet, packet_len, 1); #endif (*rxh)(this->netdev, packet, packet_len); slip_input_buffer_filled -= slip_input_buffer_decoded; slip_input_buffer_decoded = 0; } } while (got_packet); if ((slip_input_buffer_filled - slip_input_buffer_decoded) > 0) memmove(slip_input_buffer + slip_input_buffer_decoded, slip_input_buffer + pos, (slip_input_buffer_filled - slip_input_buffer_decoded)); } #endif /* if BX_NETWORKING && BX_NETMOD_SLIRP */