diff --git a/Makefile b/Makefile index 273fafa..e4fa65e 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,7 @@ VERSION := git IPXWRAPPER_DEPS := src/ipxwrapper.o src/winsock.o src/ipxwrapper_stubs.o src/log.o src/common.o \ src/interface.o src/router.o src/ipxwrapper.def src/addrcache.o src/config.o src/addr.o \ - src/firewall.o src/wpcap_stubs.o + src/firewall.o src/wpcap_stubs.o src/ethernet.o BIN_FILES := $(shell cat manifest.bin.txt) SRC_FILES := $(shell cat manifest.src.txt) diff --git a/manifest.src.txt b/manifest.src.txt index f9b64aa..2de0b9a 100644 --- a/manifest.src.txt +++ b/manifest.src.txt @@ -17,6 +17,8 @@ src/common.c src/common.h src/config.c src/config.h +src/ethernet.c +src/ethernet.h src/directplay.c src/dpwsockx.def src/dpwsockx_stubs.txt diff --git a/src/ethernet.c b/src/ethernet.c new file mode 100644 index 0000000..8438140 --- /dev/null +++ b/src/ethernet.c @@ -0,0 +1,208 @@ +/* IPXWrapper - Ethernet frame handling + * Copyright (C) 2017 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 + * the Free Software Foundation. + * + * This program 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 + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +/* This file implements three types of functions: + * + * XXX_frame_size + * + * Returns the size of a whole frame and IPX packet with the given number of + * bytes of payload. + * + * Returns zero if the payload is too large to fit in a frame of this format. + * + * XXX_frame_pack + * + * Serialises a frame and IPX packet to the given buffer, which must be at + * least as large as the size returned by the corresponding XXX_frame_size() + * function. + * + * XXX_frame_unpack + * + * Deserialises a frame, giving the inner IPX packet and its size. Returns + * true on success, false if the frame was invalid. +*/ + +#define WINSOCK_API_LINKAGE +#include + +#include +#include +#include + +#include "addr.h" +#include "ethernet.h" + +#define ETHERTYPE_IPX 0x8137 + +typedef struct ethernet_header ethernet_header; + +struct ethernet_header +{ + unsigned char dest_mac[6]; + unsigned char src_mac[6]; + + union { + /* Depends on frame type. */ + uint16_t ethertype; + uint16_t length; + }; +} __attribute__((__packed__)); + +static void _pack_ipx_packet(void *buf, + uint8_t type, + addr32_t src_net, addr48_t src_node, uint16_t src_socket, + addr32_t dst_net, addr48_t dst_node, uint16_t dst_socket, + const void *payload, size_t payload_len) +{ + novell_ipx_packet *packet = buf; + + packet->checksum = 0xFFFF; + packet->length = htons(sizeof(novell_ipx_packet) + payload_len); + packet->hops = 0; + packet->type = type; + + addr32_out(packet->dest_net, dst_net); + addr48_out(packet->dest_node, dst_node); + packet->dest_socket = dst_socket; + + addr32_out(packet->src_net, src_net); + addr48_out(packet->src_node, src_node); + packet->src_socket = src_socket; + + memcpy(packet->data, payload, payload_len); +} + +size_t ethII_frame_size(size_t ipx_payload_len) +{ + if(ipx_payload_len > NOVELL_IPX_PACKET_MAX_PAYLOAD) + { + return 0; + } + + return sizeof(ethernet_header) + + sizeof(novell_ipx_packet) + + ipx_payload_len; +} + +void ethII_frame_pack(void *frame_buffer, + uint8_t type, + addr32_t src_net, addr48_t src_node, uint16_t src_socket, + addr32_t dst_net, addr48_t dst_node, uint16_t dst_socket, + const void *payload, size_t payload_len) +{ + ethernet_header *eth_h = frame_buffer; + + addr48_out(eth_h->dest_mac, dst_node); + eth_h->ethertype = htons(ETHERTYPE_IPX); + + _pack_ipx_packet(eth_h + 1, + type, + src_net, src_node, src_socket, + dst_net, dst_node, dst_socket, + payload, payload_len); +} + +bool ethII_frame_unpack(const novell_ipx_packet **packet, size_t *packet_len, const void *frame_data, size_t frame_len) +{ + if(frame_len < sizeof(ethernet_header) + sizeof(novell_ipx_packet)) + { + /* Frame is too small to contain all the necessary headers. */ + return false; + } + + const ethernet_header *eth_h = frame_data; + + if(ntohs(eth_h->ethertype) != ETHERTYPE_IPX) + { + /* The ethertype field isn't IPX. */ + return false; + } + + *packet = (const novell_ipx_packet*)(eth_h + 1); + *packet_len = frame_len - sizeof(ethernet_header); + + return true; +} + +size_t novell_frame_size(size_t ipx_payload_len) +{ + static const size_t OVERHEAD + = sizeof(ethernet_header) + + sizeof(novell_ipx_packet); + + if(ipx_payload_len > NOVELL_IPX_PACKET_MAX_PAYLOAD + || ipx_payload_len > (1500 - OVERHEAD)) + { + return 0; + } + + return OVERHEAD + ipx_payload_len; +} + +void novell_frame_pack(void *frame_buffer, + uint8_t type, + addr32_t src_net, addr48_t src_node, uint16_t src_socket, + addr32_t dst_net, addr48_t dst_node, uint16_t dst_socket, + const void *payload, size_t payload_len) +{ + ethernet_header *eth_h = frame_buffer; + + addr48_out(eth_h->dest_mac, dst_node); + eth_h->length = htons(sizeof(novell_ipx_packet) + payload_len); + + _pack_ipx_packet(eth_h + 1, + type, + src_net, src_node, src_socket, + dst_net, dst_node, dst_socket, + payload, payload_len); +} + +bool novell_frame_unpack(const novell_ipx_packet **packet, size_t *packet_len, const void *frame_data, size_t frame_len) +{ + if(frame_len < sizeof(ethernet_header) + sizeof(novell_ipx_packet)) + { + /* Frame is too small to contain all the necessary headers. */ + return false; + } + + const ethernet_header *eth_h = frame_data; + + uint16_t payload_len = ntohs(eth_h->length); + + if(payload_len > 1500) + { + /* Payload too big, probably an Ethernet II frame. */ + return false; + } + else if(payload_len < sizeof(novell_ipx_packet)) + { + /* Too small to hold an IPX header. */ + return false; + } + else if(payload_len > frame_len - sizeof(ethernet_header)) + { + /* Payload length runs past the end of frame_len, was the frame + * somehow truncated? + */ + return false; + } + + *packet = (const novell_ipx_packet*)(eth_h + 1); + *packet_len = payload_len; + + return true; +} diff --git a/src/ethernet.h b/src/ethernet.h new file mode 100644 index 0000000..d63b497 --- /dev/null +++ b/src/ethernet.h @@ -0,0 +1,88 @@ +/* IPXWrapper - Ethernet frame handling + * Copyright (C) 2017 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 + * the Free Software Foundation. + * + * This program 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 + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef IPXWRAPPER_ETHERNET_H +#define IPXWRAPPER_ETHERNET_H + +#include +#include + +#include "addr.h" + +/* An IPX packet in its normal form on the wire */ + +#define NOVELL_IPX_PACKET_MAX_PAYLOAD ((uint16_t)(0xFFFF) - sizeof(novell_ipx_packet)) + +typedef struct novell_ipx_packet novell_ipx_packet; + +struct novell_ipx_packet { + uint16_t checksum; + uint16_t length; + uint8_t hops; + uint8_t type; + + unsigned char dest_net[4]; + unsigned char dest_node[6]; + uint16_t dest_socket; + + unsigned char src_net[4]; + unsigned char src_node[6]; + uint16_t src_socket; + + unsigned char data[0]; +} __attribute__((__packed__)); + +/* This file declares three types of functions: + * + * XXX_frame_size + * + * Returns the size of a whole frame and IPX packet with the given number of + * bytes of payload. + * + * Returns zero if the payload is too large to fit in a frame of this format. + * + * XXX_frame_pack + * + * Serialises a frame and IPX packet to the given buffer, which must be at + * least as large as the size returned by the corresponding XXX_frame_size() + * function. + * + * XXX_frame_unpack + * + * Deserialises a frame, giving the inner IPX packet and its size. Returns + * true on success, false if the frame was invalid. +*/ + +size_t ethII_frame_size(size_t ipx_payload_len); +void ethII_frame_pack(void *frame_buffer, + uint8_t type, + addr32_t src_net, addr48_t src_node, uint16_t src_socket, + addr32_t dest_net, addr48_t dest_node, uint16_t dest_socket, + const void *payload, size_t payload_len); +bool ethII_frame_unpack(const novell_ipx_packet **packet, size_t *packet_len, + const void *frame_data, size_t frame_len); + +size_t novell_frame_size(size_t ipx_payload_len); +void novell_frame_pack(void *frame_buffer, + uint8_t type, + addr32_t src_net, addr48_t src_node, uint16_t src_socket, + addr32_t dest_net, addr48_t dest_node, uint16_t dest_socket, + const void *payload, size_t payload_len); +bool novell_frame_unpack(const novell_ipx_packet **packet, size_t *packet_len, + const void *frame_data, size_t frame_len); + +#endif /* !IPXWRAPPER_ETHERNET_H */ diff --git a/src/ipxwrapper.h b/src/ipxwrapper.h index 33207fc..ea34dec 100644 --- a/src/ipxwrapper.h +++ b/src/ipxwrapper.h @@ -90,41 +90,6 @@ struct ipx_packet { char data[1]; } __attribute__((__packed__)); -typedef struct real_ipx_packet real_ipx_packet_t; - -struct real_ipx_packet { - uint16_t checksum; - uint16_t length; - uint8_t hops; - uint8_t type; - - unsigned char dest_net[4]; - unsigned char dest_node[6]; - uint16_t dest_socket; - - unsigned char src_net[4]; - unsigned char src_node[6]; - uint16_t src_socket; - - unsigned char data[0]; -} __attribute__((__packed__)); - -typedef struct ethernet_frame ethernet_frame_t; - -struct ethernet_frame -{ - unsigned char dest_mac[6]; - unsigned char src_mac[6]; - - union { - /* Depends on frame type. */ - uint16_t ethertype; - uint16_t length; - }; - - real_ipx_packet_t packet; -} __attribute__((__packed__)); - #define IPX_MAGIC_SPXLOOKUP 1 typedef struct spxlookup_req spxlookup_req_t; diff --git a/src/router.c b/src/router.c index f6447f7..2a94dc1 100644 --- a/src/router.c +++ b/src/router.c @@ -29,6 +29,7 @@ #include "ipxwrapper.h" #include "interface.h" #include "addrcache.h" +#include "ethernet.h" static bool router_running = false; static WSAEVENT router_event = WSA_INVALID_EVENT; @@ -497,67 +498,42 @@ static void _handle_pcap_frame(u_char *user, const struct pcap_pkthdr *pkt_heade { ipx_interface_t *iface = (ipx_interface_t*)(user); - if(pkt_header->caplen < sizeof(ethernet_frame_t)) + const novell_ipx_packet *ipx; + size_t ipx_len; + + switch(main_config.frame_type) { - /* Frame isn't big enough to contain a full IPX header. */ - return; + case FRAME_TYPE_ETH_II: + if(!ethII_frame_unpack(&ipx, &ipx_len, pkt_data, pkt_header->caplen)) + { + return; + } + + break; + + case FRAME_TYPE_NOVELL: + if(!novell_frame_unpack(&ipx, &ipx_len, pkt_data, pkt_header->caplen)) + { + return; + } + + break; } - ethernet_frame_t *frame = (ethernet_frame_t*)(pkt_data); - - uint16_t ipx_packet_len = ntohs(frame->packet.length); - - if(main_config.frame_type == FRAME_TYPE_ETH_II) - { - /* Configured for standard Ethernet. */ - - if(ntohs(frame->ethertype) != 0x8137) - { - /* The ethertype field isn't IPX. */ - return; - } - } - else if(main_config.frame_type == FRAME_TYPE_NOVELL) - { - /* Configured for Novell "raw" Ethernet. */ - - uint16_t eth_payload_len = ntohs(frame->length); - - if(eth_payload_len > 1500) - { - /* Too big, must be an Ethernet II frame (or garbage). */ - return; - } - else if(eth_payload_len < sizeof(frame->packet)) - { - /* Too small to hold an IPX header. */ - return; - } - else if(eth_payload_len < ipx_packet_len) - { - /* Too small to hold the IPX payload within. */ - return; - } - } - else{ - /* Unknown frame type configured. */ - abort(); - } - - if(frame->packet.checksum != 0xFFFF) + if(ipx->checksum != 0xFFFF) { /* The "checksum" field doesn't have the magic IPX value. */ return; } - if(ipx_packet_len > (pkt_header->caplen - (sizeof(*frame) - sizeof(frame->packet)))) + if(ntohs(ipx->length) > ipx_len) { /* The "length" field in the IPX header is too big. */ return; } { - addr48_t dest = addr48_in(frame->packet.dest_node); + addr48_t dest = addr48_in(ipx->dest_node); addr48_t bcast = addr48_in((unsigned char[]){0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}); if(dest != iface->mac_addr && dest != bcast) @@ -569,17 +545,17 @@ static void _handle_pcap_frame(u_char *user, const struct pcap_pkthdr *pkt_heade } } - _deliver_packet(frame->packet.type, - addr32_in(frame->packet.src_net), - addr48_in(frame->packet.src_node), - frame->packet.src_socket, + _deliver_packet(ipx->type, + addr32_in(ipx->src_net), + addr48_in(ipx->src_node), + ipx->src_socket, - addr32_in(frame->packet.dest_net), - addr48_in(frame->packet.dest_node), - frame->packet.dest_socket, + addr32_in(ipx->dest_net), + addr48_in(ipx->dest_node), + ipx->dest_socket, - frame->packet.data, - (ntohs(frame->packet.length) - sizeof(real_ipx_packet_t))); + ipx->data, + (ntohs(ipx->length) - sizeof(novell_ipx_packet))); } static DWORD router_main(void *arg) diff --git a/src/winsock.c b/src/winsock.c index 893de37..ec37edc 100644 --- a/src/winsock.c +++ b/src/winsock.c @@ -29,6 +29,7 @@ #include "interface.h" #include "router.h" #include "addrcache.h" +#include "ethernet.h" struct sockaddr_ipx_ext { short sa_family; @@ -49,9 +50,22 @@ static size_t strsize(void *str, bool unicode) static int _max_ipx_payload(void) { - return ipx_use_pcap - ? (ETHERNET_MTU - sizeof(real_ipx_packet_t)) - : MAX_DATA_SIZE; + if(ipx_use_pcap) + { + /* TODO: Use real interface MTU */ + + switch(main_config.frame_type) + { + case FRAME_TYPE_ETH_II: + case FRAME_TYPE_NOVELL: + return 1500 - (14 + sizeof(novell_ipx_packet)); + } + + abort(); + } + else{ + return MAX_DATA_SIZE; + } } #define PUSH_NAME(name) \ @@ -1198,64 +1212,64 @@ static DWORD ipx_send_packet( ipx_interface_t *iface = ipx_interface_by_addr(src_net, src_node); if(iface) { - size_t packet_size = sizeof(real_ipx_packet_t) + data_size; - size_t frame_size = sizeof(ethernet_frame_t) + data_size; - ethernet_frame_t *frame = malloc(frame_size); + /* Calculate the frame size and check we can actually + * fit this much data in it. + */ + + size_t frame_size; + + switch(main_config.frame_type) + { + case FRAME_TYPE_ETH_II: + frame_size = ethII_frame_size(data_size); + break; + + case FRAME_TYPE_NOVELL: + frame_size = novell_frame_size(data_size); + break; + } + + /* TODO: Check frame_size against interface MTU */ + + if(frame_size == 0) + { + log_printf(LOG_ERROR, + "Tried sending a %u byte packet, too large for the selected frame type", + (unsigned int)(data_size)); + + return WSAEMSGSIZE; + } + + log_printf(LOG_DEBUG, "...frame size = %u", (unsigned int)(frame_size)); + + /* Serialise the frame. */ + + void *frame = malloc(frame_size); if(!frame) { return ERROR_OUTOFMEMORY; } - log_printf(LOG_DEBUG, "...packet size = %u, frame size = %u", - (unsigned int)(packet_size), (unsigned int)(frame_size)); - - addr48_out(frame->dest_mac, dest_node); - addr48_out(frame->src_mac, iface->mac_addr); - - if(main_config.frame_type == FRAME_TYPE_ETH_II) + switch(main_config.frame_type) { - /* Configured for standard Ethernet. */ - frame->ethertype = htons(0x8137); - } - else if(main_config.frame_type == FRAME_TYPE_NOVELL) - { - /* Configured for Novell "raw" Ethernet. */ - - if(packet_size > 1500) - { - /* Can't fit a payload this big into a - * Novell Ethernet frame. - */ + case FRAME_TYPE_ETH_II: + ethII_frame_pack(frame, + type, + src_net, src_node, src_socket, + dest_net, dest_node, dest_socket, + data, data_size); + break; - log_printf(LOG_ERROR, - "Tried sending a %u byte packet in a Novell (\"raw\") Ethernet frame", - (unsigned int)(packet_size)); - - free(frame); - return WSAEMSGSIZE; - } - - frame->length = htons(packet_size); - } - else{ - /* Unknown frame type configured. */ - abort(); + case FRAME_TYPE_NOVELL: + novell_frame_pack(frame, + type, + src_net, src_node, src_socket, + dest_net, dest_node, dest_socket, + data, data_size); + break; } - frame->packet.checksum = 0xFFFF; - frame->packet.length = htons(packet_size); - frame->packet.hops = 0; - frame->packet.type = type; - - addr32_out(frame->packet.dest_net, dest_net); - addr48_out(frame->packet.dest_node, dest_node); - frame->packet.dest_socket = dest_socket; - - addr32_out(frame->packet.src_net, src_net); - addr48_out(frame->packet.src_node, src_node); - frame->packet.src_socket = src_socket; - - memcpy(frame->packet.data, data, data_size); + /* Transmit the frame. */ if(pcap_sendpacket(iface->pcap, (void*)(frame), frame_size) == 0) {