974 lines
36 KiB
C
974 lines
36 KiB
C
/*
|
|
* 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 backend
|
|
*
|
|
*
|
|
*
|
|
* Authors: cold-brewed
|
|
*
|
|
* Copyright 2024 cold-brewed
|
|
*/
|
|
|
|
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <wchar.h>
|
|
#include <stdbool.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#ifdef _WIN32
|
|
# define WIN32_LEAN_AND_MEAN
|
|
# include <windows.h>
|
|
# include <winsock2.h>
|
|
#else
|
|
# include <poll.h>
|
|
# include <netdb.h>
|
|
#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 <86box/random.h>
|
|
#include <sys/time.h>
|
|
#include "netswitch.h"
|
|
#include "pb_encode.h"
|
|
#include "pb_decode.h"
|
|
|
|
#include "networkmessage.pb.h"
|
|
|
|
bool ns_socket_setup(NSCONN *conn) {
|
|
|
|
if(conn == NULL) {
|
|
errno=EINVAL;
|
|
return false;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
// Initialize Windows Socket API with the given version.
|
|
WSADATA wsaData;
|
|
if (WSAStartup(MAKEWORD(2, 0), &wsaData)) {
|
|
perror("WSAStartup");
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
/* Create the "main" socket
|
|
* Local mode: the listener socket for multicast packets
|
|
* Remote mode: the "main" socket for send and receive */
|
|
conn->fddata = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (conn->fddata < 0) {
|
|
perror("socket");
|
|
return false;
|
|
}
|
|
|
|
/* Here things diverge depending on local or remote type */
|
|
if(conn->switch_type == SWITCH_TYPE_LOCAL) {
|
|
|
|
/* Set socket options - allow multiple sockets to use the same address */
|
|
u_int on = 1;
|
|
if (setsockopt(conn->fddata, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0) {
|
|
perror("Reusing ADDR failed");
|
|
return false;
|
|
}
|
|
#ifndef _WIN32
|
|
/* ... and same port number
|
|
* Not needed on windows because SO_REUSEPORT doesn't exist there. However, the same
|
|
* functionality comes along with SO_REUSEADDR. */
|
|
if (setsockopt(conn->fddata, SOL_SOCKET, SO_REUSEPORT, (char *) &on, sizeof(on)) < 0) {
|
|
perror("Reusing PORT failed");
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
memset(&conn->addr, 0, sizeof(conn->addr));
|
|
conn->addr.sin_family = AF_INET;
|
|
conn->addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
conn->addr.sin_port = htons(conn->local_multicast_port);
|
|
|
|
/* Bind to receive address */
|
|
if (bind(conn->fddata, (struct sockaddr *) &conn->addr, sizeof(conn->addr)) < 0) {
|
|
perror("bind");
|
|
return false;
|
|
}
|
|
|
|
/* Request to join multicast group */
|
|
/*** NOTE: intermittent airplane (non-connected wifi) failures with 239.255.86.86 - needs more investigation */
|
|
struct ip_mreq mreq;
|
|
mreq.imr_multiaddr.s_addr = inet_addr(conn->mcast_group);
|
|
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
|
|
if (setsockopt(conn->fddata, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, sizeof(mreq)) < 0) {
|
|
perror("setsockopt");
|
|
return false;
|
|
}
|
|
|
|
/* Now create the outgoing data socket */
|
|
conn->fdout = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (conn->fdout < 0) {
|
|
perror("out socket");
|
|
return false;
|
|
}
|
|
|
|
/* Set up destination address */
|
|
memset(&conn->outaddr, 0, sizeof(conn->outaddr));
|
|
conn->outaddr.sin_family = AF_INET;
|
|
conn->outaddr.sin_addr.s_addr = inet_addr(conn->mcast_group);
|
|
conn->outaddr.sin_port = htons(conn->local_multicast_port);
|
|
} else if (conn->switch_type == SWITCH_TYPE_REMOTE) {
|
|
/* Remote switch path */
|
|
int status;
|
|
struct addrinfo hints;
|
|
struct addrinfo *servinfo;
|
|
char connect_ip[128] = "\0";
|
|
|
|
memset(&hints, 0, sizeof hints);
|
|
hints.ai_family = AF_INET;
|
|
hints.ai_socktype = SOCK_DGRAM;
|
|
hints.ai_flags = AI_PASSIVE; // not sure?
|
|
|
|
if((status = getaddrinfo(conn->nrs_hostname, NULL, &hints, &servinfo)) != 0) {
|
|
net_switch_log("getaddrinfo error: %s\n", gai_strerror(status));
|
|
errno=EFAULT;
|
|
return false;
|
|
}
|
|
|
|
for(const struct addrinfo *p = servinfo; p != NULL; p = p->ai_next) { // NOLINT (only want the first result)
|
|
/* Take the first result, ipv4 since AF_INET was set in the hints */
|
|
const struct sockaddr_in *ipv4 = (struct sockaddr_in *) p->ai_addr;
|
|
const void *addr = &(ipv4->sin_addr);
|
|
inet_ntop(p->ai_family, addr, connect_ip, sizeof connect_ip);
|
|
break;
|
|
}
|
|
freeaddrinfo(servinfo);
|
|
|
|
if(strlen(connect_ip) == 0) {
|
|
/* Couldn't look up the hostname */
|
|
net_switch_log("Hostname lookup failure?\n");
|
|
errno=EFAULT;
|
|
return false;
|
|
}
|
|
|
|
/* Set up local socket address and port */
|
|
memset(&conn->addr, 0, sizeof(conn->addr));
|
|
conn->addr.sin_family = AF_INET;
|
|
conn->addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
conn->addr.sin_port = htons(conn->remote_source_port);
|
|
|
|
/* Bind to receive address. Try the first 100 ports to allow the use of multiple systems simultaneously */
|
|
for(int i=0; i<100; i++) {
|
|
if(i==99) {
|
|
net_switch_log("Unable to find an available port to bind\n");
|
|
return false;
|
|
}
|
|
if (bind(conn->fddata, (struct sockaddr *) &conn->addr, sizeof(conn->addr)) < 0) {
|
|
net_switch_log("local port %d unavailable, trying next..\n", conn->remote_source_port);
|
|
conn->remote_source_port += 1;
|
|
conn->addr.sin_port = htons(conn->remote_source_port);
|
|
continue ;
|
|
} else {
|
|
net_switch_log("** Local port for net remote switch is %d\n", conn->remote_source_port);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* Set up remote address and port */
|
|
memset(&conn->outaddr, 0, sizeof(conn->outaddr));
|
|
conn->outaddr.sin_family = AF_INET;
|
|
conn->outaddr.sin_addr.s_addr = inet_addr(connect_ip);
|
|
conn->outaddr.sin_port = htons(conn->remote_network_port);
|
|
|
|
/* In remote mode the file descriptor for send (fdout) is the same as receive */
|
|
conn->fdout = conn->fddata;
|
|
|
|
} else {
|
|
errno=EINVAL;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
NSCONN *
|
|
ns_open(struct ns_open_args *open_args) {
|
|
struct nsconn *conn=NULL;
|
|
|
|
/* Each "group" is really just the base port + group number
|
|
* A different group effectively gets you a different switch
|
|
* Clamp the group at MAX_SWITCH_GROUP */
|
|
if(open_args->group > MAX_SWITCH_GROUP) {
|
|
open_args->group = MAX_SWITCH_GROUP;
|
|
}
|
|
// FIXME: hardcoded for testing
|
|
char *mcast_group = "239.255.86.86"; // Admin scope
|
|
// char *mcast_group = "224.0.0.86"; // Local scope
|
|
|
|
if ( (conn=calloc(1,sizeof(struct nsconn)))==NULL) {
|
|
errno=ENOMEM;
|
|
return NULL;
|
|
}
|
|
|
|
/* Type */
|
|
conn->switch_type = open_args->type;
|
|
|
|
/* Allocate the fragment buffer */
|
|
for (int i = 0; i < FRAGMENT_BUFFER_LENGTH; i++) {
|
|
conn->fragment_buffer[i] = calloc(1, sizeof(ns_fragment_t));
|
|
/* Set the default size to 0 and null data buffer to indicate it is unused.
|
|
* The data buffer will be allocated as needed. */
|
|
conn->fragment_buffer[i]->size = 0;
|
|
conn->fragment_buffer[i]->data = NULL;
|
|
}
|
|
// net_switch_log("Fragment buffers: %d total, %d each\n", FRAGMENT_BUFFER_LENGTH, MAX_FRAME_SEND_SIZE);
|
|
|
|
snprintf(conn->mcast_group, MAX_MCAST_GROUP_LEN, "%s", mcast_group);
|
|
conn->flags = open_args->flags;
|
|
|
|
/* Increment the multicast port by the switch group number. Each group is
|
|
* just a different port. */
|
|
conn->local_multicast_port = open_args->group + NET_SWITCH_MULTICAST_PORT;
|
|
conn->remote_network_port = NET_SWITCH_REMOTE_PORT;
|
|
/* Source ports for remote switch will start here and be incremented until an available port is found */
|
|
conn->remote_source_port = NET_SWITCH_REMOTE_PORT + NET_SWITCH_RECV_PORT_OFFSET;
|
|
|
|
/* Remote switch hostname */
|
|
strncpy(conn->nrs_hostname, open_args->nrs_hostname, sizeof(conn->nrs_hostname) - 1);
|
|
|
|
/* Switch type */
|
|
if(conn->switch_type == SWITCH_TYPE_REMOTE) {
|
|
net_switch_log("Connecting to remote %s:%d, initial local port %d, group %d\n", conn->nrs_hostname, conn->remote_network_port, conn->remote_source_port, open_args->group);
|
|
} else {
|
|
net_switch_log("Opening IP %s, port %d, group %d\n", mcast_group, conn->local_multicast_port, open_args->group);
|
|
}
|
|
|
|
/* Client state, disconnected by default.
|
|
* Primarily used in remote mode */
|
|
conn->client_state = DISCONNECTED;
|
|
|
|
/* Client ID. Generate the ID if set to zero. */
|
|
if(open_args->client_id == 0) {
|
|
conn->client_id = ns_gen_client_id();
|
|
}
|
|
|
|
/* MAC address is set from the emulated card */
|
|
memcpy(conn->mac_addr, open_args->mac_addr, PB_MAC_ADDR_SIZE);
|
|
|
|
/* Protocol version */
|
|
conn->version = NS_PROTOCOL_VERSION;
|
|
|
|
if(!ns_socket_setup(conn)) {
|
|
goto fail;
|
|
}
|
|
|
|
if (conn->switch_type == SWITCH_TYPE_REMOTE) {
|
|
/* Perhaps one day do the entire handshake process here */
|
|
if(!ns_send_control(conn, MessageType_MESSAGE_TYPE_CONNECT_REQUEST)) {
|
|
goto fail;
|
|
}
|
|
conn->client_state = CONNECTING;
|
|
net_switch_log("Client state is now CONNECTING\n");
|
|
} else {
|
|
conn->client_state = LOCAL;
|
|
}
|
|
|
|
/* Initialize sequence numbers */
|
|
conn->sequence = 1;
|
|
conn->remote_sequence = 1;
|
|
|
|
/* Initialize stats */
|
|
conn->stats.max_tx_frame = 0;
|
|
conn->stats.max_tx_packet = 0;
|
|
conn->stats.max_rx_frame = 0;
|
|
conn->stats.max_rx_packet = 0;
|
|
conn->stats.total_rx_packets = 0;
|
|
conn->stats.total_tx_packets = 0;
|
|
conn->stats.total_fragments = 0;
|
|
conn->stats.max_vec = 0;
|
|
memcpy(conn->stats.last_tx_ethertype, (uint8_t []) { 0, 0}, sizeof(conn->stats.last_tx_ethertype));
|
|
memcpy(conn->stats.last_rx_ethertype, (uint8_t []) { 0, 0}, sizeof(conn->stats.last_rx_ethertype));
|
|
|
|
/* Assuming all went well we have our sockets */
|
|
return conn;
|
|
|
|
/* Cleanup */
|
|
fail:
|
|
for (int i = 0; i < FRAGMENT_BUFFER_LENGTH; i++) {
|
|
free(conn->fragment_buffer[i]);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
ns_pollfd(const NSCONN *conn) {
|
|
if (conn->fddata != 0)
|
|
return conn->fddata;
|
|
else {
|
|
errno=EBADF;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
ssize_t
|
|
ns_sock_recv(const NSCONN *conn,void *buf, const size_t len, const int flags) {
|
|
if (fd_valid(conn->fddata))
|
|
return recv(conn->fddata,buf,len,0);
|
|
else {
|
|
errno=EBADF;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
ssize_t
|
|
ns_sock_send(NSCONN *conn,const void *buf, const size_t len, const int flags) {
|
|
if (fd_valid(conn->fddata)) {
|
|
/* Use the outgoing socket for sending, set elsewhere:
|
|
* Remote mode: same as sending
|
|
* Local mode: different from sending */
|
|
return sendto(conn->fdout, buf, len, 0, (struct sockaddr *) &conn->outaddr, sizeof(conn->outaddr));
|
|
} else {
|
|
errno=EBADF;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
ssize_t
|
|
ns_send_pb(NSCONN *conn, const netpkt_t *packet,int flags) {
|
|
|
|
NetworkMessage network_message = NetworkMessage_init_zero;
|
|
uint8_t fragment_count;
|
|
|
|
/* Do we need to fragment? First, determine how many packets we will be sending */
|
|
if(packet->len <= MAX_FRAME_SEND_SIZE) {
|
|
fragment_count = 1;
|
|
// net_switch_log("No Fragmentation. Frame size %d is less than max size %d\n", packet->len, MAX_FRAME_SEND_SIZE);
|
|
} else {
|
|
/* Since we're using integer math and the remainder is
|
|
* discarded we'll add one to the result *unless* the result can be evenly divided. */
|
|
const uint8_t extra = (packet->len % MAX_FRAME_SEND_SIZE) == 0 ? 0 : 1;
|
|
fragment_count = (packet->len / MAX_FRAME_SEND_SIZE) + extra;
|
|
// net_switch_log("Fragmentation required, frame size %d exceeds max size %d\n", packet->len, MAX_FRAME_SEND_SIZE);
|
|
}
|
|
|
|
/* Loop here for each fragment. Send each fragment. In the even that the packet is *not* a fragment (regular data packet)
|
|
* this will only execute once. */
|
|
const uint32_t fragment_sequence = conn->sequence;
|
|
const int64_t packet_timestamp = ns_get_current_millis();
|
|
for (uint8_t fragment_index = 0; fragment_index < fragment_count; fragment_index++) {
|
|
uint8_t buffer[NET_SWITCH_BUFFER_LENGTH];
|
|
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
|
|
#ifdef ENABLE_NET_SWITCH_PB_FILE_DEBUG
|
|
uint8_t file_buffer[NET_SWITCH_BUFFER_LENGTH];
|
|
/* file_stream used for debugging and writing the message to a file */
|
|
pb_ostream_t file_stream = pb_ostream_from_buffer(file_buffer, sizeof(file_buffer));
|
|
#endif
|
|
/* Single frame is type DATA, fragments are FRAGMENT */
|
|
network_message.message_type = fragment_count > 1 ? MessageType_MESSAGE_TYPE_FRAGMENT : MessageType_MESSAGE_TYPE_DATA;
|
|
network_message.client_id = conn->client_id;
|
|
network_message.timestamp = packet_timestamp;
|
|
network_message.version = conn->version;
|
|
|
|
/* Need some additional data if we're a fragment */
|
|
if(fragment_count > 1) {
|
|
network_message.fragment.total = fragment_count;
|
|
network_message.fragment.id = fragment_sequence;
|
|
network_message.fragment.sequence = fragment_index + 1;
|
|
network_message.has_fragment = true;
|
|
}
|
|
|
|
/* TODO: Better / real ack logic. Needs its own function. Currently just putting in dummy data. */
|
|
network_message.ack.id = 1;
|
|
network_message.ack.history = 1;
|
|
network_message.has_ack = true;
|
|
network_message.sequence = conn->sequence;
|
|
|
|
/* Frame data must be allocated */
|
|
network_message.frame = calloc(1, PB_BYTES_ARRAY_T_ALLOCSIZE(packet->len));
|
|
|
|
/* Calculate offsets based on our position in the fragment.
|
|
* For anything other than the *last* packet, we'll have a max frame size */
|
|
uint16_t copy_length;
|
|
const uint16_t copy_offset = fragment_index * MAX_FRAME_SEND_SIZE;
|
|
if(fragment_index == (fragment_count - 1)) {
|
|
copy_length = packet->len % MAX_FRAME_SEND_SIZE == 0 ? MAX_FRAME_SEND_SIZE : packet->len % MAX_FRAME_SEND_SIZE;
|
|
} else {
|
|
copy_length = MAX_FRAME_SEND_SIZE;
|
|
}
|
|
if(fragment_count > 1) {
|
|
// net_switch_log("Fragment %d/%d, %d bytes\n", fragment_index + 1, fragment_count, copy_length);
|
|
}
|
|
network_message.frame->size = copy_length;
|
|
memcpy(network_message.frame->bytes, packet->data + copy_offset, copy_length);
|
|
|
|
/* mac address must be allocated */
|
|
network_message.mac = calloc(1, PB_BYTES_ARRAY_T_ALLOCSIZE(PB_MAC_ADDR_SIZE));
|
|
network_message.mac->size = PB_MAC_ADDR_SIZE;
|
|
memcpy(network_message.mac->bytes, conn->mac_addr, PB_MAC_ADDR_SIZE);
|
|
|
|
/* Encode the protobuf message */
|
|
if (!pb_encode_ex(&stream, NetworkMessage_fields, &network_message,PB_ENCODE_DELIMITED)) {
|
|
net_switch_log("Encoding failed: %s\n", PB_GET_ERROR(&stream));
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
|
|
/* Send on the socket */
|
|
const ssize_t nc = ns_sock_send(conn, buffer, stream.bytes_written, 0);
|
|
if(!nc) {
|
|
net_switch_log("Error sending data on the socket\n");
|
|
errno=EBADF;
|
|
pb_release(NetworkMessage_fields, &network_message);
|
|
return -1;
|
|
}
|
|
#ifdef ENABLE_NET_SWITCH_PB_FILE_DEBUG
|
|
/* File writing for troubleshooting when needed */
|
|
FILE *f = fopen("/var/tmp/pbuf", "wb");
|
|
if (f) {
|
|
if (!pb_encode(&file_stream, NetworkMessage_fields, &network_message)) {
|
|
net_switch_log("File encoding failed: %s\n", PB_GET_ERROR(&file_stream));
|
|
}
|
|
fwrite(file_buffer, file_stream.bytes_written, 1, f);
|
|
fclose(f);
|
|
} else {
|
|
net_switch_log("file open failed\n");
|
|
}
|
|
#endif
|
|
|
|
/* Stats */
|
|
if(network_message.frame->size > conn->stats.max_tx_frame) {
|
|
conn->stats.max_tx_frame = network_message.frame->size;
|
|
}
|
|
if(nc > conn->stats.max_tx_packet) {
|
|
conn->stats.max_tx_packet = nc;
|
|
}
|
|
if(nc > MAX_FRAME_SEND_SIZE) {
|
|
conn->stats.total_fragments = fragment_count > 1 ? conn->stats.total_fragments + fragment_count : conn->stats.total_fragments;
|
|
}
|
|
conn->stats.total_tx_packets++;
|
|
memcpy(conn->stats.last_tx_ethertype, &packet->data[12], 2);
|
|
|
|
/* Increment the sequence number */
|
|
seq_increment(conn);
|
|
|
|
/* nanopb will free all the allocated entries for us */
|
|
pb_release(NetworkMessage_fields, &network_message);
|
|
|
|
}
|
|
|
|
return packet->len;
|
|
}
|
|
|
|
bool store_fragment(const NSCONN *conn, const NetworkMessage *network_message) {
|
|
|
|
if(conn == NULL || network_message == NULL) {
|
|
return false;
|
|
}
|
|
|
|
/* The fragment sequence indicates which fragment this is in the overall fragment
|
|
* collection. This is used to index the fragments while being stored for reassembly
|
|
* (zero indexed locally) */
|
|
const uint32_t fragment_index = network_message->fragment.sequence - 1;
|
|
const uint32_t fragment_size = network_message->frame->size;
|
|
|
|
/* Make sure the fragments aren't too small
|
|
* (see header notes about size requirements for MIN_FRAG_RECV_SIZE and FRAGMENT_BUFFER_LENGTH)
|
|
* NOTE: The last packet is exempt from this rule because it can have a smaller amount.
|
|
* This is primarily to ensure there's enough space to fit all the fragments. */
|
|
if(network_message->fragment.sequence != network_message->fragment.total) {
|
|
if (network_message->frame->size < MIN_FRAG_RECV_SIZE) {
|
|
net_switch_log("size: %d < %d\n", network_message->frame->size, MIN_FRAG_RECV_SIZE);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Make sure we can handle the amount of incoming fragments */
|
|
if (network_message->fragment.total > FRAGMENT_BUFFER_LENGTH) {
|
|
net_switch_log("buflen: %d > %d\n", network_message->fragment.total, FRAGMENT_BUFFER_LENGTH);
|
|
return false;
|
|
}
|
|
|
|
/* Allocate or reallocate as needed.
|
|
* size > 0 indicates this buffer has already been allocated. */
|
|
if(conn->fragment_buffer[fragment_index]->size > 0) {
|
|
conn->fragment_buffer[fragment_index]->data = realloc(conn->fragment_buffer[fragment_index]->data, sizeof(char) * fragment_size);
|
|
} else {
|
|
conn->fragment_buffer[fragment_index]->data = calloc(1, sizeof(char) * fragment_size);
|
|
}
|
|
|
|
if (conn->fragment_buffer[fragment_index]->data == NULL) {
|
|
net_switch_log("Failed to allocate / reallocate fragment buffer space\n");
|
|
return false;
|
|
}
|
|
|
|
/* Each fragment will belong to a particular ID. All members will have the same ID,
|
|
* which is generally set to the sequence number of the first fragment */
|
|
conn->fragment_buffer[fragment_index]->id = network_message->fragment.id;
|
|
/* The sequence here is set to the index of the packet in the total fragment collection
|
|
* (network_message->fragment.sequence) */
|
|
conn->fragment_buffer[fragment_index]->sequence = fragment_index;
|
|
/* Total number of fragments in this set */
|
|
conn->fragment_buffer[fragment_index]->total = network_message->fragment.total;
|
|
/* The sequence number from the packet that contained the fragment */
|
|
conn->fragment_buffer[fragment_index]->packet_sequence = network_message->sequence;
|
|
/* Copy the fragment data and size */
|
|
/* The size of fragment_buffer[fragment_index]->data is checked against MAX_FRAME_SEND_SIZE above */
|
|
memcpy(conn->fragment_buffer[fragment_index]->data, network_message->frame->bytes, fragment_size);
|
|
conn->fragment_buffer[fragment_index]->size = fragment_size;
|
|
/* 10 seconds for a TTL */
|
|
conn->fragment_buffer[fragment_index]->ttl = ns_get_current_millis() + 10000;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
reassemble_fragment(const NSCONN *conn, netpkt_t *pkt, const uint32_t packet_count)
|
|
{
|
|
uint32_t total = 0;
|
|
|
|
/* Make sure the reassembled packet doesn't exceed NET_MAX_FRAME */
|
|
// if (packet_count * MAX_FRAME_SEND_SIZE > NET_MAX_FRAME) {
|
|
// return false;
|
|
// }
|
|
|
|
/* Too many packets! */
|
|
if (packet_count > FRAGMENT_BUFFER_LENGTH) {
|
|
return false;
|
|
}
|
|
|
|
// TODO: Check fragment ID
|
|
// TODO: Check TTL
|
|
|
|
/* Get the fragment size from the first entry. All fragments in a particular
|
|
* set must be of the same size except the last fragment, which may be smaller.
|
|
* The fragment size will be used to determine the offset. */
|
|
const uint16_t fragment_size = conn->fragment_buffer[0]->size;
|
|
|
|
// net_switch_log("Reassembling %d fragments\n", packet_count);
|
|
|
|
for(int i = 0; i < packet_count; i++) {
|
|
/* Size of zero means we're trying to assemble from a bad fragment */
|
|
if(conn->fragment_buffer[i]->size == 0) {
|
|
net_switch_log("Fragment size 0 when trying to reassemble (id %i/index %i/seq %i/ total %i)\n",conn->fragment_buffer[i]->id, i, conn->fragment_buffer[i]->sequence, conn->fragment_buffer[i]->total);
|
|
return false;
|
|
}
|
|
if(conn->fragment_buffer[i]->data == NULL) {
|
|
net_switch_log("Missing fragment data when trying to reassemble\n");
|
|
return false;
|
|
}
|
|
|
|
memcpy(pkt->data + (fragment_size * i), conn->fragment_buffer[i]->data, conn->fragment_buffer[i]->size);
|
|
total += conn->fragment_buffer[i]->size;
|
|
|
|
/* Zero out the size to indicate the slot is unused */
|
|
conn->fragment_buffer[i]->size = 0;
|
|
free(conn->fragment_buffer[i]->data);
|
|
conn->fragment_buffer[i]->data = NULL;
|
|
}
|
|
|
|
/* Set the size, must cast due to netpkt_t (len is int) */
|
|
pkt->len = (int) total;
|
|
// net_switch_log("%d bytes reassembled and converted to data packet.\n", pkt->len);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ns_recv_pb(NSCONN *conn, ns_rx_packet_t *packet,size_t len,int flags) {
|
|
NetworkMessage network_message = NetworkMessage_init_zero;
|
|
ns_rx_packet_t *ns_packet = packet;
|
|
|
|
uint8_t buffer[NET_SWITCH_BUFFER_LENGTH];
|
|
|
|
/* TODO: Use the passed len? Most likely not needed */
|
|
const ssize_t nc = ns_sock_recv(conn, buffer, NET_SWITCH_BUFFER_LENGTH, 0);
|
|
if(!nc) {
|
|
net_switch_log("Error receiving data on the socket\n");
|
|
errno=EBADF;
|
|
return false;
|
|
}
|
|
pb_istream_t stream = pb_istream_from_buffer(buffer, sizeof(buffer));
|
|
|
|
if (!pb_decode_delimited(&stream, NetworkMessage_fields, &network_message)) {
|
|
/* Decode failed */
|
|
net_switch_log("PB decoding failed: %s\n", PB_GET_ERROR(&stream));
|
|
/* Allocated fields are automatically released upon failure */
|
|
return false;
|
|
}
|
|
|
|
/* Basic checks for validity */
|
|
if(network_message.mac == NULL || network_message.message_type == MessageType_MESSAGE_TYPE_UNSPECIFIED ||
|
|
network_message.client_id == 0) {
|
|
net_switch_log("Invalid packet received! Skipping..\n");
|
|
goto fail;
|
|
}
|
|
|
|
/* These fields should always be set. Start copying into our packet structure. */
|
|
ns_packet->client_id = network_message.client_id;
|
|
ns_packet->type = network_message.message_type;
|
|
memcpy(ns_packet->mac, network_message.mac->bytes, PB_MAC_ADDR_SIZE);
|
|
ns_packet->timestamp = network_message.timestamp;
|
|
ns_packet->version = network_message.version;
|
|
conn->remote_sequence = network_message.sequence;
|
|
conn->last_packet_stamp = network_message.timestamp;
|
|
|
|
/* Control messages take a different path */
|
|
if(network_message.message_type != MessageType_MESSAGE_TYPE_DATA &&
|
|
network_message.message_type != MessageType_MESSAGE_TYPE_FRAGMENT) {
|
|
process_control_packet(conn, ns_packet);
|
|
pb_release(NetworkMessage_fields, &network_message);
|
|
return true;
|
|
}
|
|
|
|
/* All packets should be DATA or FRAGMENT at this point and have a frame */
|
|
if(network_message.frame == NULL) {
|
|
net_switch_log("Invalid data packet received! Frame is null. Skipping..\n");
|
|
goto fail;
|
|
}
|
|
|
|
/* Fragment path first */
|
|
if(network_message.message_type == MessageType_MESSAGE_TYPE_FRAGMENT) {
|
|
|
|
/* Store fragment */
|
|
if(!store_fragment(conn, &network_message)) {
|
|
net_switch_log("Failed to store fragment\n");
|
|
goto fail;
|
|
}
|
|
|
|
/* Is this the last fragment? If not, return */
|
|
if(network_message.fragment.sequence != network_message.fragment.total) {
|
|
// FIXME: Really dumb, needs to be smarter
|
|
pb_release(NetworkMessage_fields, &network_message);
|
|
return true;
|
|
}
|
|
|
|
/* This is the last fragment. Attempt to reassemble */
|
|
if(!reassemble_fragment(conn, &ns_packet->pkt, network_message.fragment.total)) {
|
|
net_switch_log("Failed to reassemble fragment\n");
|
|
goto fail;
|
|
}
|
|
/* Change the type to DATA */
|
|
ns_packet->type = MessageType_MESSAGE_TYPE_DATA;
|
|
|
|
} else {
|
|
/* Standard DATA packet path. Copy frame from the message */
|
|
memcpy(ns_packet->pkt.data, network_message.frame->bytes, network_message.frame->size);
|
|
ns_packet->pkt.len = network_message.frame->size;
|
|
}
|
|
|
|
/* Stats */
|
|
if(network_message.frame->size > conn->stats.max_rx_frame) {
|
|
conn->stats.max_rx_frame = network_message.frame->size;
|
|
}
|
|
if(nc > conn->stats.max_rx_packet) {
|
|
conn->stats.max_rx_packet = nc;
|
|
}
|
|
memcpy(conn->stats.last_rx_ethertype, &packet->pkt.data[12], 2);
|
|
conn->stats.total_rx_packets++;
|
|
/* End Stats */
|
|
|
|
/* nanopb allocates the necessary fields while serializing.
|
|
They need to be manually released once you are done with the message */
|
|
pb_release(NetworkMessage_fields, &network_message);
|
|
return true;
|
|
fail:
|
|
pb_release(NetworkMessage_fields, &network_message);
|
|
return false;
|
|
}
|
|
|
|
bool process_control_packet(NSCONN *conn, const ns_rx_packet_t *packet) {
|
|
|
|
control_packet_info_t packet_info = get_control_packet_info(*packet, conn->mac_addr);
|
|
// net_switch_log("Last timestamp: %lld\n", ns_get_current_millis());
|
|
// net_switch_log("(%lld ms) [%03d] ", ns_get_current_millis() - packet_info.timestamp, conn->sequence);
|
|
|
|
/* I probably want to eventually differentiate between local and remote here, kind of basic now */
|
|
if(!packet_info.is_packet_from_me) { /* in case of local mode */
|
|
switch (packet_info.type) {
|
|
case MessageType_MESSAGE_TYPE_JOIN:
|
|
net_switch_log("Client ID 0x%08llx (MAC %s) has joined the chat\n", packet_info.client_id, packet_info.src_mac_h);
|
|
break;
|
|
case MessageType_MESSAGE_TYPE_LEAVE:
|
|
net_switch_log("Client ID 0x%08llx (MAC %s) has left us\n", packet_info.client_id, packet_info.src_mac_h);
|
|
break;
|
|
case MessageType_MESSAGE_TYPE_KEEPALIVE:
|
|
// net_switch_log("Client ID 0x%08llx (MAC %s) is still alive\n", packet_info.client_id, packet_info.src_mac_h);
|
|
break;
|
|
case MessageType_MESSAGE_TYPE_ACK:
|
|
// net_switch_log("Client ID 0x%08llx (MAC %s) has sent an ACK\n", packet_info.client_id, packet_info.src_mac_h);
|
|
break;
|
|
case MessageType_MESSAGE_TYPE_CONNECT_REPLY:
|
|
conn->client_state = CONNECTED;
|
|
net_switch_log("Client ID 0x%08llx (MAC %s) has sent a connection reply\n", packet_info.client_id, packet_info.src_mac_h);
|
|
net_switch_log("Client state is now CONNECTED\n");
|
|
break;
|
|
case MessageType_MESSAGE_TYPE_FRAGMENT:
|
|
net_switch_log("Client ID 0x%08llx (MAC %s) has sent a fragment\n", packet_info.client_id, packet_info.src_mac_h);
|
|
break;
|
|
default:
|
|
net_switch_log("Client ID 0x%08llx (MAC %s) has sent a message that we don't understand (type %d)\n", packet_info.client_id, packet_info.src_mac_h, packet_info.type);
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
|
|
}
|
|
|
|
bool
|
|
ns_send_control(NSCONN *conn, const MessageType type) {
|
|
|
|
NetworkMessage network_message = NetworkMessage_init_zero;
|
|
uint8_t buffer[NET_SWITCH_BUFFER_LENGTH];
|
|
|
|
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
|
|
network_message.message_type = type;
|
|
network_message.client_id = conn->client_id;
|
|
|
|
/* No frame data so we only need to allocate mac address */
|
|
network_message.mac = calloc(1, PB_BYTES_ARRAY_T_ALLOCSIZE(PB_MAC_ADDR_SIZE));
|
|
network_message.mac->size = PB_MAC_ADDR_SIZE;
|
|
memcpy(network_message.mac->bytes, conn->mac_addr, PB_MAC_ADDR_SIZE);
|
|
|
|
network_message.timestamp = ns_get_current_millis();
|
|
network_message.version = conn->version;
|
|
network_message.sequence = conn->sequence;
|
|
|
|
if (!pb_encode_ex(&stream, NetworkMessage_fields, &network_message, PB_ENCODE_DELIMITED)) {
|
|
net_switch_log("Encoding failed: %s\n", PB_GET_ERROR(&stream));
|
|
errno = EBADF;
|
|
return false;
|
|
}
|
|
|
|
const ssize_t nc = ns_sock_send(conn, buffer, stream.bytes_written, 0);
|
|
if(!nc) {
|
|
net_switch_log("Error sending control message on the socket\n");
|
|
errno=EBADF;
|
|
pb_release(NetworkMessage_fields, &network_message);
|
|
return -1;
|
|
}
|
|
/* Increment the sequence number */
|
|
seq_increment(conn);
|
|
|
|
/* Stats */
|
|
conn->stats.total_tx_packets++;
|
|
|
|
/* Must release allocated data */
|
|
pb_release(NetworkMessage_fields, &network_message);
|
|
|
|
return true;
|
|
}
|
|
|
|
uint32_t
|
|
ns_gen_client_id(void) {
|
|
uint32_t msb;
|
|
do {
|
|
msb = random_generate();
|
|
} while (msb < 0x10);
|
|
return ( random_generate() | (random_generate() << 8) | (random_generate() << 16) | (msb << 24));
|
|
}
|
|
|
|
const char *
|
|
ns_printable_message_type(const MessageType type)
|
|
{
|
|
switch (type) {
|
|
case MessageType_MESSAGE_TYPE_DATA:
|
|
return "Data";
|
|
case MessageType_MESSAGE_TYPE_JOIN:
|
|
return "Join";
|
|
case MessageType_MESSAGE_TYPE_LEAVE:
|
|
return "Leave";
|
|
case MessageType_MESSAGE_TYPE_KEEPALIVE:
|
|
return "Keepalive";
|
|
case MessageType_MESSAGE_TYPE_FRAGMENT:
|
|
return "Fragment";
|
|
case MessageType_MESSAGE_TYPE_ACK:
|
|
return "Ack";
|
|
case MessageType_MESSAGE_TYPE_UNSPECIFIED:
|
|
return "Unspecified (shouldn't get this)";
|
|
default:
|
|
return "Unknown message type - probably hasn't been added yet!";
|
|
}
|
|
}
|
|
|
|
int64_t
|
|
ns_get_current_millis(void) {
|
|
struct timeval time;
|
|
gettimeofday(&time, NULL);
|
|
/* Windows won't properly promote integers so this is necessary */
|
|
const int64_t seconds = (int64_t) time.tv_sec * 1000;
|
|
|
|
return seconds + (time.tv_usec / 1000);
|
|
}
|
|
|
|
int
|
|
ns_flags(const NSCONN *conn) {
|
|
return conn->flags;
|
|
}
|
|
|
|
int
|
|
ns_close(NSCONN *conn) {
|
|
if(conn->switch_type == SWITCH_TYPE_REMOTE) {
|
|
/* TBD */
|
|
}
|
|
/* No need to check the return here as we're closing out */
|
|
ns_send_control(conn, MessageType_MESSAGE_TYPE_LEAVE);
|
|
for (int i = 0; i < FRAGMENT_BUFFER_LENGTH; i++) {
|
|
if (conn->fragment_buffer[i]->size > 0) {
|
|
free(conn->fragment_buffer[i]->data);
|
|
conn->fragment_buffer[i]->data = NULL;
|
|
}
|
|
free(conn->fragment_buffer[i]);
|
|
}
|
|
close(conn->fddata);
|
|
close(conn->fdout);
|
|
return 0;
|
|
}
|
|
|
|
bool is_control_packet(const ns_rx_packet_t *packet) {
|
|
return packet->type != MessageType_MESSAGE_TYPE_DATA;
|
|
}
|
|
|
|
bool is_fragment_packet(const ns_rx_packet_t *packet) {
|
|
return packet->type == MessageType_MESSAGE_TYPE_FRAGMENT;
|
|
}
|
|
|
|
bool ns_connected(const NSCONN *conn) {
|
|
if(conn->switch_type == SWITCH_TYPE_LOCAL) {
|
|
return true;
|
|
}
|
|
|
|
if(conn->switch_type == SWITCH_TYPE_REMOTE) {
|
|
if(conn->client_state == CONNECTED) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
char* formatted_mac(uint8_t mac_addr[6])
|
|
{
|
|
char *mac_h = calloc(1, sizeof(char)* 32);
|
|
for(int i=0; i < 6; i++) {
|
|
char octet[4];
|
|
snprintf(octet, sizeof(octet), "%02X%s", mac_addr[i], i < 5 ? ":" : "");
|
|
strncat(mac_h, octet, sizeof(mac_h) - 1);
|
|
}
|
|
return mac_h;
|
|
}
|
|
|
|
control_packet_info_t get_control_packet_info(const ns_rx_packet_t packet, const uint8_t *my_mac)
|
|
{
|
|
control_packet_info_t packet_info;
|
|
|
|
packet_info.src_mac_h[0] = '\0';
|
|
packet_info.printable[0] = '\0';
|
|
packet_info.client_id = packet.client_id;
|
|
memcpy(packet_info.src_mac, &packet.mac, 6);
|
|
packet_info.type = packet.type;
|
|
packet_info.is_packet_from_me = (memcmp(my_mac, packet_info.src_mac, sizeof(uint8_t) * 6) == 0);
|
|
char *formatted_mac_h = formatted_mac(packet_info.src_mac);
|
|
strncpy(packet_info.src_mac_h, formatted_mac_h, MAX_PRINTABLE_MAC);
|
|
free(formatted_mac_h);
|
|
snprintf(packet_info.printable, sizeof(packet_info.printable), "%s", ns_printable_message_type(packet_info.type));
|
|
packet_info.timestamp = packet.timestamp;
|
|
|
|
return packet_info;
|
|
}
|
|
|
|
data_packet_info_t
|
|
get_data_packet_info(const netpkt_t *packet, const uint8_t *my_mac) {
|
|
data_packet_info_t packet_info;
|
|
|
|
packet_info.src_mac_h[0] = '\0';
|
|
packet_info.dest_mac_h[0] = '\0';
|
|
packet_info.my_mac_h[0] = '\0';
|
|
packet_info.printable[0] = '\0';
|
|
|
|
memcpy(packet_info.dest_mac,&packet->data[0], 6);
|
|
memcpy(packet_info.src_mac, &packet->data[6], 6);
|
|
|
|
/* Broadcast and multicast are treated the same at L2 and both will have the
|
|
* least significant bit of 1 in the first transmitted byte.
|
|
* The below test matches 0xFF for standard broadcast along with 0x01 and 0x33 for multicast */
|
|
packet_info.is_broadcast = ((packet->data[0] & 1) == 1);
|
|
packet_info.is_packet_for_me = (memcmp(my_mac, packet_info.dest_mac, sizeof(uint8_t) * 6) == 0);
|
|
packet_info.is_packet_from_me = (memcmp(my_mac, packet_info.src_mac, sizeof(uint8_t) * 6) == 0);
|
|
packet_info.is_data_packet = packet->len > 0;
|
|
packet_info.size = packet->len;
|
|
|
|
/* Since this function is applied to every packet, only enable the pretty formatting below
|
|
* if logging is specifically enabled. */
|
|
#ifdef ENABLE_NET_SWITCH_LOG
|
|
/* Pretty formatting for hardware addresses */
|
|
for(int i=0; i < 6; i++) {
|
|
char octet[4];
|
|
snprintf(octet, sizeof(octet), "%02X%s", packet_info.src_mac[i], i < 5 ? ":" : "");
|
|
strncat(packet_info.src_mac_h, octet, sizeof (packet_info.src_mac_h) - 1);
|
|
|
|
snprintf(octet, sizeof(octet), "%02X%s", packet_info.dest_mac[i], i < 5 ? ":" : "");
|
|
strncat(packet_info.dest_mac_h, octet, sizeof (packet_info.dest_mac_h) - 1);
|
|
|
|
snprintf(octet, sizeof(octet), "%02X%s", my_mac[i], i < 5 ? ":" : "");
|
|
strncat(packet_info.my_mac_h, octet, sizeof (packet_info.my_mac_h) - 1);
|
|
}
|
|
|
|
/* Printable output formatting */
|
|
if(packet_info.is_broadcast) {
|
|
if(packet_info.is_packet_from_me) {
|
|
snprintf(packet_info.printable, sizeof(packet_info.printable), "(broadcast)");
|
|
} else {
|
|
snprintf(packet_info.printable, sizeof(packet_info.printable), "%s (broadcast)", packet_info.src_mac_h);
|
|
}
|
|
} else {
|
|
snprintf(packet_info.printable, sizeof(packet_info.printable), "%s%s -> %s%s", packet_info.src_mac_h, packet_info.is_packet_from_me ? " (me)" : " ",
|
|
packet_info.dest_mac_h, packet_info.is_packet_for_me ? " (me)" : "");
|
|
}
|
|
#endif
|
|
return packet_info;
|
|
}
|
|
|
|
bool
|
|
fd_valid(const int fd)
|
|
{
|
|
#ifdef _WIN32
|
|
int error_code;
|
|
int error_code_size = sizeof(error_code);
|
|
getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *) &error_code, &error_code_size);
|
|
if (error_code == WSAENOTSOCK) {
|
|
return false;
|
|
}
|
|
return true;
|
|
#else
|
|
if (fcntl(fd, F_GETFD) == -1) {
|
|
return false;
|
|
}
|
|
/* All other values will be a valid fd */
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool
|
|
seq_increment(NSCONN *conn)
|
|
{
|
|
if(conn == NULL) {
|
|
return false;
|
|
}
|
|
conn->sequence++;
|
|
if (conn->sequence == 0) {
|
|
conn->sequence = 1;
|
|
}
|
|
return true;
|
|
}
|