usim

Artifact [9206f487ef]
Login

Artifact 9206f487ef8f352419cc5cdc86dd8354a272b5660c9817b596ccf03cb8fbebcf:


/* uch11.c --- chaosnet interface
 */

#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/un.h>

#include <netinet/in.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

#include <err.h>
#include <pthread.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "uch11.h"
#include "chaosd.h"
#include "hosttab.h"
#include "misc.h"
#include "ucfg.h"
#include "ucode.h"
#include "usim.h"
#include "utrace.h"

enum
{
	CHAOS_CSR_TIMER_INTERRUPT_ENABLE = (01 << 00),	/* CHBUSY */
	CHAOS_CSR_LOOP_BACK = (01 << 01),	/* CHLPBK */
	CHAOS_CSR_RECEIVE_ALL = (01 << 02),	/* CHSPY */
	CHAOS_CSR_RECEIVER_CLEAR = (01 << 03),
	CHAOS_CSR_RECEIVE_ENABLE = (01 << 04),	/* CHREN */
	CHAOS_CSR_TRANSMIT_ENABLE = (01 << 05),	/* CHRIEN */
	CHAOS_CSR_INTERRUPT_ENABLES = (02 << 04),
	CHAOS_CSR_TRANSMIT_ABORT = (01 << 06),	/* CHABRT */
	CHAOS_CSR_TRANSMIT_DONE = (01 << 07),	/* CHTDN */
	CHAOS_CSR_TRANSMITTER_CLEAR = (01 << 010),	/* CHTCLR */
	CHAOS_CSR_LOST_COUNT = (04 << 011),	/* CHLC */
	CHAOS_CSR_RESET = (01 << 015),	/* CHRST */
	CHAOS_CSR_CRC_ERROR = (01 << 016),	/* CHCRC */
	CHAOS_CSR_RECEIVE_DONE = (01 << 017),	/* CHRDN */
} CHAOS_HARDWARE_VALUES;

int uch11_backend = UCH11_BACKEND_LOCAL;
int uch11_myaddr = 0177040;	/* LOCAL-CADR */
int uch11_serveraddr = 0177001;	/* LOCAL-BRIDGE */

static int uch11_csr;
static int uch11_bit_count;
static int uch11_lost_count;

static unsigned short uch11_xmit_buffer[4096];
static int uch11_xmit_buffer_size;
static int uch11_xmit_buffer_ptr;

static unsigned short uch11_rcv_buffer[4096];
static unsigned short uch11_rcv_buffer_toss[4096];
static int uch11_rcv_buffer_ptr;
static int uch11_rcv_buffer_size;
static bool uch11_rcv_buffer_empty;

static int reconnect_delay;
static bool reconnect_chaos;

static int (*uch11_send)(char *, int);

static void uch11_force_reconect(void);

static u_short udp_bridge_chaddr;
static int chudpopen(void);
static int chaos_send_to_udp(char *buffer, int size);
static int chaos_poll_udp(void);

extern void settreeroot(const char *, const char *prefix);

/*
 * RFC1071: Compute Internet Checksum for COUNT bytes beginning at
 * location ADDR.
 */
static unsigned short
uch11_checksum(const unsigned char *addr, int count)
{
	long sum;

	sum = 0;
	while (count > 1) {
		sum += *(addr) << 8 | *(addr + 1);
		addr += 2;
		count -= 2;
	}
	/*
	 * Add left-over byte, if any.
	 */
	if (count > 0)
		sum += *(unsigned char *) addr;
	/*
	 * Fold 32-bit sum to 16 bits.
	 */
	while (sum >> 16)
		sum = (sum & 0xffff) + (sum >> 16);
	return (~sum) & 0xffff;
}

/*
 * Called when we are we have something in uch11_rcv_buffer.
 */
static void
uch11_rx_pkt(void)
{
	uch11_rcv_buffer_ptr = 0;
	uch11_bit_count = (uch11_rcv_buffer_size * 2 * 8) - 1;
	INFO(TRACE_CHAOS, "chaos: receiving %d words\n", uch11_rcv_buffer_size);
	if (uch11_rcv_buffer_size > 0) {
		DEBUG(TRACE_CHAOS, "chaos: set csr receive done, generate interrupt\n");
		uch11_csr |= CHAOS_CSR_RECEIVE_DONE;
		if (uch11_csr & CHAOS_CSR_RECEIVE_ENABLE)
			assert_unibus_interrupt(0270);
	} else {
		DEBUG(TRACE_CHAOS, "chaos: recieve buffer empty\n");
	}
}

static void
uch11_xmit_done_intr(void)
{
	uch11_csr |= CHAOS_CSR_TRANSMIT_DONE;
	if (uch11_csr & CHAOS_CSR_TRANSMIT_ENABLE)
		assert_unibus_interrupt(0270);
}

void
uch11_xmit_pkt(void)
{
	INFO(TRACE_CHAOS, "chaos: transmitting %d bytes, data len %d\n", uch11_xmit_buffer_ptr * 2, (uch11_xmit_buffer_ptr > 0 ? uch11_xmit_buffer[1] & 0x3f : -1));
	uch11_xmit_buffer_size = uch11_xmit_buffer_ptr;
	/*
	 * Dest. is already in the buffer.
	 */
	uch11_xmit_buffer[uch11_xmit_buffer_size++] = (unsigned short) uch11_myaddr;	/* Source. */
	uch11_xmit_buffer[uch11_xmit_buffer_size] = uch11_checksum((unsigned char *) uch11_xmit_buffer, uch11_xmit_buffer_size * 2);	/* Checksum. */
	uch11_xmit_buffer_size++;
	(*uch11_send) ((char *) uch11_xmit_buffer, uch11_xmit_buffer_size * 2);
	uch11_xmit_buffer_ptr = 0;
	uch11_xmit_done_intr();
}

int
uch11_get_bit_count(void)
{
	if (uch11_rcv_buffer_size > 0)
		return uch11_bit_count;
	DEBUG(TRACE_CHAOS, "chaos: returned empty bit count\n");
	return 07777;
}

int
uch11_get_rcv_buffer(void)
{
	if (uch11_rcv_buffer_ptr < uch11_rcv_buffer_size) {
		int ret;

		ret = uch11_rcv_buffer[uch11_rcv_buffer_ptr++];
		if (uch11_rcv_buffer_ptr == uch11_rcv_buffer_size) {
			DEBUG(TRACE_CHAOS, "chaos: marked recieve buffer as empty\n");
			uch11_rcv_buffer_empty = true;
		}
		return ret;
	}
	/*
	 * Read last word, clear receive done.
	 */
	DEBUG(TRACE_CHAOS, "chaos: cleared csr receive done\n");
	uch11_csr &= ~CHAOS_CSR_RECEIVE_DONE;
	uch11_rcv_buffer_size = 0;
	return 0;
}

void
uch11_put_xmit_buffer(int v)
{
	if (uch11_xmit_buffer_ptr < (int) sizeof(uch11_xmit_buffer) / 2)
		uch11_xmit_buffer[uch11_xmit_buffer_ptr++] = (unsigned short) v;
	uch11_csr &= ~CHAOS_CSR_TRANSMIT_DONE;
}

int
uch11_get_csr(void)
{
	static int old_csr = 0;

	if (uch11_csr != old_csr) {
		DEBUG(TRACE_CHAOS, "chaos: read csr %o\n", uch11_csr);
		old_csr = uch11_csr;
	}
	return uch11_csr | ((uch11_lost_count << 9) & 017);
}

void
uch11_set_csr(int v)
{
	int mask;
	int old_csr;

	old_csr = uch11_csr;
	v &= 0xffff;
	/*
	 * Writing these don't stick.
	 */
	/* *INDENT-OFF* */
	mask =
		CHAOS_CSR_TRANSMIT_DONE |
		CHAOS_CSR_LOST_COUNT |
		CHAOS_CSR_CRC_ERROR |
		CHAOS_CSR_RECEIVE_DONE |
		CHAOS_CSR_RECEIVER_CLEAR;
	/* *INDENT-ON* */
	uch11_csr = (uch11_csr & mask) | (v & ~mask);
	if (uch11_csr & CHAOS_CSR_RESET) {
		uch11_rcv_buffer_ptr = 0;
		uch11_rcv_buffer_size = 0;
		uch11_xmit_buffer_ptr = 0;
		uch11_lost_count = 0;
		uch11_bit_count = 0;
		uch11_csr &= ~(CHAOS_CSR_RESET | CHAOS_CSR_RECEIVE_DONE);
		uch11_csr |= CHAOS_CSR_TRANSMIT_DONE;
		reconnect_delay = 200;	/* Do it right away. */
		uch11_force_reconect();
	}
	if (v & CHAOS_CSR_RECEIVER_CLEAR) {
		uch11_rcv_buffer_ptr = 0;
		uch11_rcv_buffer_size = 0;
		uch11_lost_count = 0;
		uch11_bit_count = 0;
		uch11_csr &= ~CHAOS_CSR_RECEIVE_DONE;
	}
	if (v & (CHAOS_CSR_TRANSMITTER_CLEAR | CHAOS_CSR_TRANSMIT_DONE)) {
		uch11_xmit_buffer_ptr = 0;
		uch11_csr &= ~CHAOS_CSR_TRANSMIT_ABORT;
		uch11_csr |= CHAOS_CSR_TRANSMIT_DONE;
	}
	if (uch11_csr & CHAOS_CSR_RECEIVE_ENABLE) {
		if ((old_csr & CHAOS_CSR_RECEIVE_ENABLE) == 0)
			DEBUG(TRACE_CHAOS, "chaos: CSR receive enable\n");
		if (uch11_rcv_buffer_empty) {
			uch11_rcv_buffer_ptr = 0;
			uch11_rcv_buffer_size = 0;
		}
		/*
		 * If buffer is full, generate status and interrupt again.
		 */
		if (uch11_rcv_buffer_size > 0) {
			DEBUG(TRACE_CHAOS, "chaos: rx-enabled and buffer is full\n");
			uch11_rx_pkt();
		}
	} else if (old_csr & CHAOS_CSR_RECEIVE_ENABLE)
		DEBUG(TRACE_CHAOS, "chaos: CSR receive DISable\n");
	if (uch11_csr & CHAOS_CSR_TRANSMIT_ENABLE) {
		if ((old_csr & CHAOS_CSR_TRANSMIT_ENABLE) == 0)
			DEBUG(TRACE_CHAOS, "chaos: CSR transmit enable\n");
		uch11_csr |= CHAOS_CSR_TRANSMIT_DONE;
	} else if (old_csr & CHAOS_CSR_TRANSMIT_ENABLE)
		DEBUG(TRACE_CHAOS, "chaos: CSR transmit DISable\n");
	DEBUG(TRACE_CHAOS, "chaos: set csr bits 0%o, old 0%o, new 0%o\n", v, old_csr, uch11_csr);
}

/* Connect to a chaos daemon via a Unix socket.
 */

#define UNIX_SOCKET_PATH	"/var/tmp/"
#define UNIX_SOCKET_CLIENT_NAME	"chaosd_"
#define UNIX_SOCKET_SERVER_NAME	"chaosd_server"
#define UNIX_SOCKET_PERM	S_IRWXU

static int chaosd_fd;

static int
chaos_send_to_chaosd(char *buffer, int size)
{
	int wcount, dest_addr;

	/*
	 * Local loopback.
	 */
	if (uch11_csr & CHAOS_CSR_LOOP_BACK) {
		DEBUG(TRACE_CHAOS, "chaos: loopback %d bytes\n", size);
		memcpy(uch11_rcv_buffer, buffer, size);
		uch11_rcv_buffer_size = (size + 1) / 2;
		uch11_rcv_buffer_empty = false;
		uch11_rx_pkt();
		return 0;
	}
	wcount = (size + 1) / 2;
	dest_addr = ((unsigned short *) buffer)[wcount - 3];
	DEBUG(TRACE_CHAOS, "chaos: sending packet to chaosd (dest_addr=%o, uch11_myaddr=%o, size %d, wcount %d)\n", dest_addr, uch11_myaddr, size, wcount);
	/*
	 * Recieve packets addressed to us, but don't receive broadcasts we send
	 */
	if (dest_addr == uch11_myaddr) {
		memcpy(uch11_rcv_buffer, buffer, size);
		uch11_rcv_buffer_size = (size + 1) / 2;
		uch11_rcv_buffer_empty = false;
		uch11_rx_pkt();
	}
	if (chaosd_fd == -1)
		return 0;
	{
		struct iovec iov[2];
		unsigned char lenbytes[4];
		int ret;

		lenbytes[0] = size >> 8;
		lenbytes[1] = size;
		lenbytes[2] = 1;
		lenbytes[3] = 0;
		iov[0].iov_base = lenbytes;
		iov[0].iov_len = 4;
		iov[1].iov_base = buffer;
		iov[1].iov_len = size;
		ret = writev(chaosd_fd, iov, 2);
		if (ret < 0) {
			perror("chaos write");
			return -1;
		}
	}
	return 0;
}

static int
chaos_poll_chaosd(void)
{
	ssize_t ret;
	struct pollfd pfd[1];
	int nfds, timeout;
	unsigned char lenbytes[4];
	ssize_t len;
	int dest_addr;

	if (reconnect_chaos == true) {
		uch11_reconnect();
	}
	if (chaosd_fd == -1) {
		return 0;
	}
	timeout = 0;
	nfds = 1;
	pfd[0].fd = chaosd_fd;
	pfd[0].events = POLLIN;
	pfd[0].revents = 0;
	ret = poll(pfd, nfds, timeout);
	if (ret == -1) {
		ERR(TRACE_CHAOS, "chaos: polling chaosd: nothing there (RDN=%o)\n", uch11_csr & CHAOS_CSR_RECEIVE_DONE);
		reconnect_chaos = true;
		return -1;
	} else if (ret == 0) {
		ERR(TRACE_CHAOS, "chaos: timeout\n");
		return -1;
	}
	/*
	 * Is RX buffer full?
	 */
	if (!uch11_rcv_buffer_empty && (uch11_csr & CHAOS_CSR_RECEIVE_DONE)) {
		/*
		 * Toss packets arriving when buffer is already in
		 * use, they will be resent.
		 */
		ERR(TRACE_CHAOS, "chaos: polling chaosd: unread data, drop (RDN=%o, lost %d)\n", uch11_csr & CHAOS_CSR_RECEIVE_DONE, uch11_lost_count);
		uch11_lost_count++;
		ret = read(chaosd_fd, lenbytes, 4);
		if (ret != 4)
			perror("read");
		len = (lenbytes[0] << 8) | lenbytes[1];
		DEBUG(TRACE_CHAOS, "chaos: tossing packet of %d bytes\n", len);
		if (len > (ssize_t) sizeof(uch11_rcv_buffer_toss)) {
			ERR(TRACE_CHAOS, "chaos: packet won't fit (len=%d)", len);
			uch11_force_reconect();
			return -1;
		}
		/*
		 * Toss it...
		 */
		ret = read(chaosd_fd, (char *) uch11_rcv_buffer_toss, len);
		if (ret != len)
			perror("read");
		return -1;
	}
	/*
	 * Read header from chaosd.
	 */
	ret = read(chaosd_fd, lenbytes, 4);
	if (ret <= 0) {
		perror("chaos: header read error");
		uch11_force_reconect();
		return -1;
	}
	len = (lenbytes[0] << 8) | lenbytes[1];
	if (len > (ssize_t) sizeof(uch11_rcv_buffer)) {
		ERR(TRACE_CHAOS, "chaos: packet too big: pkt size %d, buffer size %lu\n", len, sizeof(uch11_rcv_buffer));
		/*
		 * When we get out of synch break socket conn.
		 */
		uch11_force_reconect();
		return -1;
	}
	ret = read(chaosd_fd, (char *) uch11_rcv_buffer, len);
	if (ret == -1) {
		perror("chaos: read");
		uch11_force_reconect();
		return -1;
	} else if (ret == 0) {
		ERR(TRACE_CHAOS, "chaos: read zero bytes\n");
		return -1;
	}
	DEBUG(TRACE_CHAOS, "chaos: polling; got chaosd packet %d\n", ret);
	uch11_rcv_buffer_size = (ret + 1) / 2;
	uch11_rcv_buffer_empty = false;
	dest_addr = uch11_rcv_buffer[uch11_rcv_buffer_size - 3];
	/*
	 * If not to us, ignore.
	 */
	if (dest_addr != uch11_myaddr) {
		DEBUG(TRACE_CHAOS, "chaos: ignoring packet not to us (%o): %o\n", uch11_myaddr, dest_addr);
		uch11_rcv_buffer_size = 0;
		uch11_rcv_buffer_empty = true;
		/*
		 * Should uch11_rx_pkt be called here?
		 */
		return 0;
	}
	DEBUG(TRACE_CHAOS, "chaos: recieving packet: from %o, my %o\n", dest_addr, uch11_myaddr);
	uch11_rx_pkt();
	return 0;
}

/* Local Chaos -- this should go into chlib.c
 */

static pthread_mutex_t recvqueue;
static pthread_mutex_t recvqueue;
struct queue_head queuehead = TAILQ_HEAD_INITIALIZER(queuehead);

static void
chaos_queue(struct packet *packet)
{
	struct packet_queue *node;

	node = malloc(sizeof(struct packet_queue));
	node->packet = packet;
	pthread_mutex_lock(&recvqueue);
	TAILQ_INSERT_TAIL(&queuehead, node, next);
	pthread_mutex_unlock(&recvqueue);
}

int
chaos_connection_queue(struct connection *conn, struct packet *packet)
{
	struct packet_queue *node;
	unsigned short nextpacket;

	INFO(TRACE_CHAOS, "chaos: sending packet (0%o) to 0%o from 0%o (length: %d)\n", packet->pk_op, CH_ADDR_SHORT(packet->pk_daddr), CH_ADDR_SHORT(packet->pk_saddr), PH_LEN(packet->pk_phead));
	if (trace_level == LOG_DEBUG && (trace_facilities & TRACE_CHAOS)) {
		dumpmem(packet->pk_cdata, PH_LEN(packet->pk_phead));
	}
	/*
	 * Any packet for remote gets queued.
	 */
	if (CH_ADDR_SHORT(packet->pk_daddr) == CH_ADDR_SHORT(conn->cn_faddr)) {
		for (;;) {
			if (chtfull(conn) && packet->pk_op != STSOP) {
				struct timespec ts;

				DEBUG(TRACE_CHAOS, "chaos: waiting for remote ack packet=%d (cn_tlast = %d cn_tacked = %d twsize = %d)\n", packet->pk_pkn, conn->cn_tlast, conn->cn_tacked, conn->cn_twsize);
				pthread_mutex_lock(&conn->twsem);
				clock_gettime(CLOCK_REALTIME, &ts);
				ts.tv_sec += 5;
				if (pthread_cond_timedwait(&conn->twcond, &conn->twsem, &ts)) {
					if (conn->lastpacket) {
						struct packet *retransmit;

						DEBUG(TRACE_CHAOS, "chaos: re-transmit last packet\n");
						retransmit = conn->lastpacket;
						conn->lastpacket = 0;
						chaos_queue(retransmit);
					}
				}
				pthread_mutex_unlock(&conn->twsem);
			} else
				break;
		}
		if (packet->pk_op != STSOP && CH_INDEX_SHORT(packet->pk_didx) == CH_INDEX_SHORT(conn->cn_fidx) && cmp_gt(packet->pk_pkn, conn->cn_tlast))
			conn->cn_tlast = packet->pk_pkn;
		conn->lastpacket = packet;
		chaos_queue(packet);
		return 0;
	}
	node = malloc(sizeof(struct packet_queue));
	node->packet = packet;
	nextpacket = conn->cn_rlast + 1;
	if (cmp_gt(packet->pk_pkn, nextpacket)) {
		DEBUG(TRACE_CHAOS, "chaos: queuing out-of-order packet: nextpacket=%d packet=%d\n", nextpacket, packet->pk_pkn);
		pthread_mutex_lock(&conn->queuelock);
		TAILQ_INSERT_TAIL(&conn->orderhead, node, next);
		pthread_mutex_unlock(&conn->queuelock);
		return 0;
	}
	{
		pthread_mutex_lock(&conn->queuelock);
		TAILQ_INSERT_TAIL(&conn->queuehead, node, next);
		pthread_mutex_unlock(&conn->queuelock);
	}
	pthread_mutex_lock(&conn->queuesem);
	pthread_cond_signal(&conn->queuecond);
	pthread_mutex_unlock(&conn->queuesem);
	return 0;
}

struct packet *
chaos_connection_dequeue(struct connection *conn)
{
	struct packet *packet;
	struct packet_queue *node;
#if 0
	unsigned short nextpacket;
#endif

	packet = NOPKT;
	node = 0;
#if 0
	nextpacket = conn->cn_rlast + 1;
#endif
	if (conn->cn_state == CSCLOSED)
		return NOPKT;
	for (;;) {
		pthread_mutex_lock(&conn->queuelock);
		if (TAILQ_EMPTY(&conn->orderhead) == false) {
#if 0
			struct packet_queue *prev;

			for (node = conn->orderhead; node; prev = node, node = node->next)
				if (node->packet->pk_pkn == nextpacket) {
					if (prev == 0)
						conn->orderhead = node->next;
					else
						prev->next = node->next;
					if (conn->ordertail == node)
						conn->ordertail = prev;
					packet = node->packet;
					break;
				}
#endif
#if 0
			TAILQ_FOREACH(node, &conn->orderhead, next) {
				if (node->packet->pk_pkn == nextpacket) {
					DEBUG(TRACE_CHAOS, "chaos: dequeued out-of-order packet %d\n", nextpacket);
					/*
					 * ---!! magic
					 */
					break;
				}
			}
#else
			DEBUG(TRACE_CHAOS, "chaos: ordered queue empty\n");
#endif
		}
		if (node == 0 && TAILQ_EMPTY(&conn->queuehead) == false) {
			node = TAILQ_FIRST(&conn->queuehead);
			packet = node->packet;
			TAILQ_REMOVE(&conn->queuehead, node, next);
		}
		pthread_mutex_unlock(&conn->queuelock);
		if (node) {
			free(node);
			node = 0;
		}
		if (packet) {
			if (cmp_gt(packet->pk_pkn, conn->cn_rlast))
				conn->cn_rlast = packet->pk_pkn;
			conn->cn_tacked = packet->pk_ackn;
			if (3 * (short) (conn->cn_rlast - conn->cn_racked) > conn->cn_rwsize) {
				struct packet *status;

				status = chaos_allocate_packet(conn, STSOP, 2 * sizeof(unsigned short));
				conn->cn_racked = conn->cn_rlast;
				*(unsigned short *) &status->pk_cdata[0] = conn->cn_rlast;
				*(unsigned short *) &status->pk_cdata[2] = conn->cn_rwsize;
				chaos_queue(status);
			}
			return packet;
		}
		pthread_mutex_lock(&conn->queuesem);
		pthread_cond_wait(&conn->queuecond, &conn->queuesem);
		pthread_mutex_unlock(&conn->queuesem);
		if (conn->cn_state == CSCLOSED)
			return NOPKT;
	}
}

static int
chaos_queue_time_pkt(unsigned short saddr, unsigned short sidx)
{
	time_t t;
	struct timeval time;
	struct packet *answer;

	INFO(TRACE_CHAOS, "chaos: RFC (TIME): answering...\n");
	gettimeofday(&time, NULL);
	t = time.tv_sec;
	t += 60UL * 60 * 24 * ((1970 - 1900) * 365L + 1970 / 4 - 1900 / 4);
	answer = pkalloc(sizeof(long), 0);
	answer->pk_op = ANSOP;
	SET_PH_LEN(answer->pk_phead, sizeof(long));
	SET_CH_ADDR(answer->pk_daddr, saddr);
	SET_CH_INDEX(answer->pk_didx, sidx);
	SET_CH_ADDR(answer->pk_saddr, chaos_addr(ucfg.chaos_servername, 0));
	SET_CH_INDEX(answer->pk_sidx, 0);
	answer->pk_pkn = 0;
	answer->pk_ackn = 0;
	*(long *) &answer->pk_cdata[0] = t;
	chaos_queue(answer);
	return 0;
}

static int
chaos_queue_uptime_pkt(unsigned short saddr, unsigned short sidx)
{
	struct packet *answer;

	INFO(TRACE_CHAOS, "chaos: RFC (UPTIME): answering...\n");
	answer = pkalloc(sizeof(long), 0);
	answer->pk_op = ANSOP;
	SET_PH_LEN(answer->pk_phead, sizeof(long));
	SET_CH_ADDR(answer->pk_daddr, saddr);
	SET_CH_INDEX(answer->pk_didx, sidx);
	SET_CH_ADDR(answer->pk_saddr, chaos_addr(ucfg.chaos_servername, 0));
	SET_CH_INDEX(answer->pk_sidx, 0);
	answer->pk_pkn = 0;
	answer->pk_ackn = 0;
	*(long *) &answer->pk_cdata[0] = 0;
	chaos_queue(answer);
	return 0;
}

static int
chaos_queue_status_pkt(unsigned short saddr, unsigned short sidx)
{
	struct packet *answer;
	struct status *status;

	INFO(TRACE_CHAOS, "chaos: RFC (STATUS): answering...\n");
	answer = pkalloc(CHSTATNAME + 2 * 2, 0);	/* Only room for name and subnet+nwords */
	answer->pk_op = ANSOP;
	SET_PH_LEN(answer->pk_phead, CHSTATNAME + 2 * 2);
	SET_CH_ADDR(answer->pk_daddr, saddr);
	SET_CH_INDEX(answer->pk_didx, sidx);
	SET_CH_ADDR(answer->pk_saddr, chaos_addr(ucfg.chaos_servername, 0));
	SET_CH_INDEX(answer->pk_sidx, 0);
	answer->pk_pkn = 0;
	answer->pk_ackn = 0;
	status = (struct status *) &answer->pk_cdata[0];
	memset(status, 0, sizeof(struct status));
	strncpy(status->sb_name, ucfg.chaos_servername, CHSTATNAME);
	status->sb_name[CHSTATNAME - 1] = '\0';
	/* Only put in the subnet (with the 400 marker) */
	status->sb_data->sb_ident = (chaos_addr(ucfg.chaos_servername, 0) >> 8) | 0400;
	/* and say no data is following */
	status->sb_data->sb_nshorts = 0 * sizeof(int) / sizeof(short);
	chaos_queue(answer);
	return 0;
}

static int
chaos_queue_file_pkt(struct packet *packet)
{
	void processdata(struct connection *conn);
	struct packet *answer;
	struct connection *conn;

	packet->pk_cdata[PH_LEN(packet->pk_phead)] = '\0';
	INFO(TRACE_CHAOS, "chaos: RFC (%s): answering...\n", &packet->pk_cdata);
	conn = allconn();
	conn->cn_faddr = packet->pk_saddr;
	conn->cn_fidx = packet->pk_sidx;
	answer = chaos_allocate_packet(conn, OPNOP, 2 * sizeof(unsigned short));
	answer->pk_ackn = packet->pk_pkn;
	conn->cn_racked = packet->pk_pkn;
	*(unsigned short *) &answer->pk_cdata[0] = packet->pk_pkn;	/* Last packed received. */
	*(unsigned short *) &answer->pk_cdata[2] = conn->cn_rwsize;
	chaos_connection_queue(conn, answer);
	conn->cn_state = CSLISTEN;
	processdata(conn);
	return 0;
}

static int
chaos_queue_mini_pkt(struct packet *packet)
{
	void processmini(struct connection *conn);
	struct packet *answer;
	struct connection *conn;

	packet->pk_cdata[PH_LEN(packet->pk_phead)] = '\0';
	INFO(TRACE_CHAOS, "chaos: RFC (%s): answering...\n", &packet->pk_cdata);
	conn = allconn();
	conn->cn_faddr = packet->pk_saddr;
	conn->cn_fidx = packet->pk_sidx;
	answer = chaos_allocate_packet(conn, OPNOP, 2 * sizeof(unsigned short));
	answer->pk_ackn = packet->pk_pkn;
	conn->cn_racked = packet->pk_pkn;
	*(unsigned short *) &answer->pk_cdata[0] = packet->pk_pkn;	/* Last packed received. */
	*(unsigned short *) &answer->pk_cdata[2] = conn->cn_rwsize;
	chaos_connection_queue(conn, answer);
	conn->cn_state = CSLISTEN;
	conn->cn_rlast = 1;
	processmini(conn);
	return 0;
}

static int
chaos_send_to_local(char *buffer, int size)
{				/* ---!!! rcvpkt ? */
	struct packet *packet;
	struct connection *conn;

	packet = (struct packet *) buffer;
	/*
	 * Local loopback.
	 */
	if (uch11_csr & CHAOS_CSR_LOOP_BACK) {
		DEBUG(TRACE_CHAOS, "chaos: loopback %d bytes\n", size);
		memcpy(uch11_rcv_buffer, buffer, size);
		uch11_rcv_buffer_size = (size + 1) / 2;
		uch11_rcv_buffer_empty = false;
		uch11_rx_pkt();
		return 0;
	}
	DEBUG(TRACE_CHAOS, "chaos: transmitting packet (dest_addr = %o, uch11_myaddr=%o, size %d, wcount %d, op: %o)\n", CH_ADDR_SHORT(packet->pk_daddr), uch11_myaddr, size, (size + 1) / 2, packet->pk_op);
	/*
	 * Receive packets addressed to ourselves.
	 */
	if (CH_ADDR_SHORT(packet->pk_daddr) == uch11_myaddr) {
		memcpy(uch11_rcv_buffer, buffer, size);
		uch11_rcv_buffer_size = (size + 1) / 2;
		uch11_rcv_buffer_empty = false;
		uch11_rx_pkt();
	}
	/*
	 * Ignore packets anything that isn't addressed to us.
	 */
	if (CH_ADDR_SHORT(packet->pk_daddr) != chaos_addr(ucfg.chaos_servername, 0)) {
		DEBUG(TRACE_CHAOS, "chaos: ignoring packet not to us (%o): %o\n", chaos_addr(ucfg.chaos_servername, 0), CH_ADDR_SHORT(packet->pk_daddr));
		return 0;
	}
	conn = chaos_find_connection(CH_INDEX_SHORT(packet->pk_didx));
	/*
	 * ---!!! Large parts of this is in rcvrfc.
	 */
	if (packet->pk_op == RFCOP) {
		DEBUG(TRACE_CHAOS, "chaos: got RFC packet\n");
		if (conn && conn->cn_state == CSLISTEN) {
			DEBUG(TRACE_CHAOS, "chaos: duplicate RFC\n");
			return 0;
		}
		if (conn) {
			struct packet *pkt;

			pkt = pkalloc((size_t) size, 0);
			memcpy(pkt, packet, size);
			chaos_connection_queue(conn, pkt);
			return 0;
		}
		if (memcmp(&packet->pk_cdata, "STATUS", 6) == 0)
			return chaos_queue_status_pkt(CH_ADDR_SHORT(packet->pk_saddr), CH_INDEX_SHORT(packet->pk_sidx));
		else if (memcmp(&packet->pk_cdata, "TIME", 4) == 0)
			return chaos_queue_time_pkt(CH_ADDR_SHORT(packet->pk_saddr), CH_INDEX_SHORT(packet->pk_sidx));
		else if (memcmp(&packet->pk_cdata, "UPTIME", 6) == 0)
			return chaos_queue_uptime_pkt(CH_ADDR_SHORT(packet->pk_saddr), CH_INDEX_SHORT(packet->pk_sidx));
		else if (memcmp(&packet->pk_cdata, "FILE", 4) == 0)
			return chaos_queue_file_pkt(packet);
		else if (memcmp(&packet->pk_cdata, "MINI", 4) == 0)
			return chaos_queue_mini_pkt(packet);
		else {
			char buffer[512];

			strncpy(buffer, (char *) packet->pk_cdata, PH_LEN(packet->pk_phead));
			buffer[PH_LEN(packet->pk_phead)] = '\0';
			WARNING(TRACE_CHAOS, "chaos: RFC (%s): protocol not implemented\n", buffer);
			return 0;
		}
	}
	if (packet->pk_op == SNSOP && conn) {
		struct packet *sts;

		sts = pkalloc(2 * sizeof(unsigned short) + 3 * sizeof(unsigned short), 1);
		sts->pk_op = STSOP;
		SET_PH_LEN(sts->pk_phead, 2 * sizeof(unsigned short));
		sts->pk_daddr = packet->pk_saddr;
		sts->pk_didx = packet->pk_sidx;
		conn->cn_faddr = packet->pk_saddr;
		conn->cn_fidx = packet->pk_sidx;
		sts->pk_saddr = conn->cn_laddr;
		sts->pk_sidx = conn->cn_lidx;
		sts->pk_pkn = packet->pk_pkn;
		if (cmp_gt(packet->pk_ackn, conn->cn_tacked))
			conn->cn_tacked = packet->pk_ackn;
		sts->pk_ackn = conn->cn_racked;
		*(unsigned short *) &sts->pk_cdata[0] = conn->cn_racked;
		*(unsigned short *) &sts->pk_cdata[2] = conn->cn_rwsize;
		chaos_connection_queue(conn, sts);
		return 0;
	}
	if (packet->pk_op == STSOP) {
		if (conn) {
			if (cmp_gt(packet->pk_ackn, conn->cn_tacked))
				conn->cn_tacked = packet->pk_ackn;
			if (conn->lastpacket && conn->cn_tacked == conn->lastpacket->pk_pkn) {
				free(conn->lastpacket);
				conn->lastpacket = 0;
			}
			conn->cn_state = CSOPEN;
			conn->cn_twsize = *(unsigned short *) &packet->pk_cdata[2];
			DEBUG(TRACE_CHAOS, "chaos: STSOP: twsize = %d\n", conn->cn_twsize);
			pthread_mutex_lock(&conn->twsem);
			pthread_cond_signal(&conn->twcond);
			pthread_mutex_unlock(&conn->twsem);
		}
		return 0;
	}
	if (conn && packet->pk_op == CLSOP) {	/* clsconn ? */
		DEBUG(TRACE_CHAOS, "chaos: CLSOP: got close \n");
		conn->cn_state = CSCLOSED;
		pthread_mutex_lock(&conn->queuesem);
		pthread_cond_signal(&conn->queuecond);
		pthread_mutex_unlock(&conn->queuesem);
		usleep(100000);	/* wait for queue to wake up */
		rlsconn(conn);
		return 0;
	}
	if (conn && (cmp_gt(conn->cn_rlast, packet->pk_pkn) || (conn->cn_rlast == packet->pk_pkn))) {
		struct packet *status;

		DEBUG(TRACE_CHAOS, "chaos: duplicate data packet\n");
		status = chaos_allocate_packet(conn, STSOP, 2 * sizeof(unsigned short));
		status->pk_pkn = packet->pk_pkn;
		conn->cn_racked = conn->cn_rlast;
		*(unsigned short *) &status->pk_cdata[0] = conn->cn_rlast;
		*(unsigned short *) &status->pk_cdata[2] = conn->cn_rwsize;
		chaos_connection_queue(conn, status);
		return 0;
	}
	if (conn) {
		struct packet *pkt;

		pkt = pkalloc((size_t) size, 0);
		memcpy(pkt, packet, size);
		chaos_connection_queue(conn, pkt);
	}
	return 0;
}

static int
chaos_poll_local(void)
{
	struct packet *packet;
	struct packet_queue *node;
	int size;

	/*
	 * Is RX buffer full?
	 */
	if (!uch11_rcv_buffer_empty && (uch11_csr & CHAOS_CSR_RECEIVE_DONE)) {
		DEBUG(TRACE_CHAOS, "chaos: polling, but unread data exists\n");
		return 0;
	}
	if (!uch11_rcv_buffer_empty) {
		DEBUG(TRACE_CHAOS, "chaos: polling, but buffer not empty\n");
		return 0;
	}
	if (TAILQ_EMPTY(&queuehead) == true) {
		return 0;
	}
	if (!(uch11_csr & CHAOS_CSR_RECEIVE_ENABLE)) {
		DEBUG(TRACE_CHAOS, "chaos: polling but rx not enabled\n");
		return 0;
	}
	pthread_mutex_lock(&recvqueue);
	node = TAILQ_FIRST(&queuehead);
	packet = node->packet;
	TAILQ_REMOVE(&queuehead, node, next);
	free(node);
	pthread_mutex_unlock(&recvqueue);
	size = ((PH_LEN(packet->pk_phead) & 0x0fff) + CHHEADSIZE + 1) / 2;
	/*
	 * Ignore any packets not to us.
	 */
	if (CH_ADDR_SHORT(packet->pk_daddr) != uch11_myaddr)
		return 0;
	memcpy(uch11_rcv_buffer, packet, (size_t) size * sizeof(unsigned short));
	/*
	 * Hardware header.
	 */
	uch11_rcv_buffer[size] = CH_ADDR_SHORT(packet->pk_daddr);
	uch11_rcv_buffer[size + 1] = CH_ADDR_SHORT(packet->pk_saddr);
	uch11_rcv_buffer[size + 2] = 0;
	size += 3;
	uch11_rcv_buffer_size = size;
	uch11_rcv_buffer_empty = false;
	DEBUG(TRACE_CHAOS, "chaos: polling: got chaos packet of %d bytes\n", uch11_rcv_buffer_size * 2);
#if 0
	dumpbuffer(uch11_rcv_buffer, size * 2);
#endif
	uch11_rx_pkt();
	return 0;
}

void
uch11_poll(void)
{
	if (uch11_backend == UCH11_BACKEND_LOCAL)
		chaos_poll_local();
	else if (uch11_backend == UCH11_BACKEND_DAEMON)
		chaos_poll_chaosd();
	else if (uch11_backend == UCH11_BACKEND_UDP)
		chaos_poll_udp();
}

static void
uch11_force_reconect(void)
{
	if (uch11_backend == UCH11_BACKEND_DAEMON) {
		WARNING(TRACE_CHAOS, "chaos: forcing reconnect to chaosd\n");
		close(chaosd_fd);
		chaosd_fd = -1;
		reconnect_chaos = true;
	}
}

void
uch11_reconnect(void)
{
	static int reconnect_time;

	if (++reconnect_delay < 200)
		return;
	reconnect_delay = 0;
	if (reconnect_time && time(NULL) < (reconnect_time + 5))	/* Try every 5 seconds. */
		return;
	reconnect_time = time(NULL);
	NOTICE(TRACE_CHAOS, "chaos: reconnecting to chaosd\n");
	if (uch11_init() == 0) {
		INFO(TRACE_CHAOS, "chaos: chaosd reconnected\n");
		reconnect_chaos = false;
		reconnect_delay = 0;
	}
}

int
uch11_init(void)
{
	char *root_directory;
	char *hosts_file;

	hosts_file = realpath(ucfg.chaos_hosts, NULL);
	if (hosts_file == NULL) {
		hosts_file = "hosts.text";
		WARNING(TRACE_USIM, "chaos: no host table; using defaults\n");
	}
	NOTICE(TRACE_USIM, "chaos: using hosts table from \"%s\"\n", hosts_file);
	readhosts(ucfg.chaos_myname, hosts_file);
	uch11_myaddr = chaos_addr(ucfg.chaos_myname, 0);
	uch11_serveraddr = chaos_addr(ucfg.chaos_servername, 0);
	NOTICE(TRACE_USIM, "chaos: I am %s (0%o)\n", ucfg.chaos_myname, uch11_myaddr);
	if (uch11_backend == UCH11_BACKEND_LOCAL) {
		NOTICE(TRACE_USIM, "chaos: backend is \"local\", connecting to %s (0%o)\n", ucfg.chaos_servername, uch11_serveraddr);
		TAILQ_INIT(&queuehead);
		uch11_send = &chaos_send_to_local;
		pthread_mutex_init(&recvqueue, NULL);
	} else if (uch11_backend == UCH11_BACKEND_DAEMON) {
		NOTICE(TRACE_USIM, "chaos: backend is \"chaosd\"\n");
		uch11_send = &chaos_send_to_chaosd;
		chaosd_fd = chdopen();
		if (chaosd_fd == -1) {
			close(chaosd_fd);
			return -1;
		}
	} else if (uch11_backend == UCH11_BACKEND_UDP) {
		if (hybrid_udp_and_local)
			NOTICE(TRACE_USIM, "chaos: backend is \"udp\" with server %s (%#o)\n", ucfg.chaos_servername, uch11_serveraddr);
		else
			NOTICE(TRACE_USIM, "chaos: backend is \"udp\"\n");
		uch11_send = &chaos_send_to_udp;
		chaosd_fd = chudpopen();
		if (chaosd_fd == -1) {
			close(chaosd_fd);
			return -1;
		}
	}
	uch11_rcv_buffer_empty = true;
	char *whichconf, *whichdir;
	if (ucfg.usim_sys_directory != NULL) {
		// Backwards compat
		whichconf = "sys_directory";
		whichdir = "/tree";
		root_directory = realpath(ucfg.usim_sys_directory, NULL);
		if (root_directory != NULL)
			settreeroot(root_directory, whichdir);
	} else {
		whichconf = "fs_root_directory";
		whichdir = "/";
		root_directory = realpath(ucfg.usim_fs_root_directory, NULL);
		if (root_directory != NULL)
			settreeroot(root_directory, NULL);
	}
	if (root_directory == NULL)
		err(1, "could not resolve %s", whichconf);
	NOTICE(TRACE_USIM, "chaos: mapping %s to %s\n", whichdir, root_directory);
	return 0;
}

/* Chaos over UDP */

int hybrid_udp_and_local = 0;

static int
chudpopen(void)
{
	int sock, lport, res, udp_dport, braddr;
	struct sockaddr_in sin;
	struct addrinfo *he, hi;
	struct in_addr udp_dest;

	if (hybrid_udp_and_local) {
		if (uch11_serveraddr == 0) {
			ERR(TRACE_CHAOS, "You configured udp_local_hybrid but there is no server address! Disabling hybrid.\n");
			hybrid_udp_and_local = 0;
		} else {
			/* Do the local init too */
			TAILQ_INIT(&queuehead);
			pthread_mutex_init(&recvqueue, NULL);
		}
	}

	/* Parse the local port given */
	if ((ucfg.chaos_bridgeport_local == NULL) || (ucfg.chaos_bridgeport_local[0] == '\0') || ((lport = atoi(ucfg.chaos_bridgeport_local)) <= 0) || (lport >= (1 << 16))) {
		/* Or default to 42042? */
		err(1, "bad bridgeport_local");
	}
	/* Parse the bridge port given */
	if ((ucfg.chaos_bridgeport == NULL) || (ucfg.chaos_bridgeport[0] == '\0') || ((udp_dport = atoi(ucfg.chaos_bridgeport)) <= 0) || (udp_dport >= (1 << 16))) {
		/* Or default to 42042? */
		err(1, "bad bridgeport");
	}
	/* Parse the bridge address given */
	if ((ucfg.chaos_bridgeip != NULL) && (ucfg.chaos_bridgeip[0] != '\0')) {
		/* @@@@ allow IPv6 */
		/* Check if it is an explicit IPv4 address */
		if (inet_aton(ucfg.chaos_bridgeip, &udp_dest) == 0) {
			/* Else try to parse a host name */
			memset(&hi, 0, sizeof(hi));
			hi.ai_family = AF_INET;
			hi.ai_flags = AI_ADDRCONFIG;
			if ((res = getaddrinfo(ucfg.chaos_bridgeip, NULL, &hi, &he)) == 0) {
				struct sockaddr_in *s = (struct sockaddr_in *) he->ai_addr;
				memcpy(&udp_dest.s_addr, (u_char *) & s->sin_addr, 4);
			} else {
				err(1, "bad bridgeip");
			}
		}
	} else {
		err(1, "no bridge?");
	}
	/* Parse bridge Chaos address */
	if ((ucfg.chaos_bridgechaos == NULL) || (ucfg.chaos_bridgechaos[0] == '\0') | (sscanf(ucfg.chaos_bridgechaos, "%o", &braddr) != 1) ||
	    /* Check it's a valid address */
	    (braddr == 0) || (braddr >= (1 << 16)) || ((braddr & 0xff) == 0) || (((braddr >> 8) & 0xff) == 0)) {
		err(1, "bad bridgechaos");
	} else {
		udp_bridge_chaddr = (braddr & 0xffff);
	}

	NOTICE(TRACE_USIM, "chaos: chudp init, bridge is %#o at %s:%d, local port %d, hybrid %s\n", udp_bridge_chaddr, inet_ntoa(udp_dest), udp_dport, lport, hybrid_udp_and_local ? "true" : "false");

	/* Now create a socket, and bind it to our local port */
	if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		perror("socket failed");
		exit(1);
	}
	sin.sin_family = AF_INET;
	sin.sin_port = htons(lport);
	sin.sin_addr.s_addr = INADDR_ANY;
	if (bind(sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
		perror("udp bind failed");
		exit(1);
	}
	/* and then connect it to the remote address - since we're only using one and the same */
	sin.sin_port = htons(udp_dport);
	memcpy(&sin.sin_addr.s_addr, &udp_dest.s_addr, 4);
	if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
		perror("udp connect failed");
		exit(1);
	}
	return sock;
}

/* from chudp.h and cbridge-chaos.h */

// Max pkt size (12 bits) plus header
// The limit of 488 bytes is from MIT AIM 628, although more would fit any modern pkt (and 12 bits would give 4096 as max).
// This is due to original Chaos hardware pkts limited to 4032 bits, of which 16 bytes are header.
#define CH_PK_MAX_DATALEN 488

/* Protocol version */
#define CHUDP_VERSION 1
/* Protocol function codes */
#define CHUDP_PKT 1		/* Chaosnet packet */

struct chudp_header
{
	char chudp_version;
	char chudp_function;
	char chudp_arg1;
	char chudp_arg2;
};

struct chaos_hw_trailer
{
	unsigned short ch_hw_destaddr:16;
	unsigned short ch_hw_srcaddr:16;
	unsigned short ch_hw_checksum:16;
};

/* Max: CHUDP header, Chaos header, Chaos data, trailer */
#define CHUDP_MAXLEN (sizeof(struct chudp_header)+sizeof(struct pkt_header)+CH_PK_MAX_DATALEN+sizeof(struct chaos_hw_trailer))

static u_char trans_chudpbuf[CHUDP_MAXLEN];

/* So sorry about this. */
void
ntohs_buf(u_short *ibuf, u_short *obuf, int len)
{
	int i;
	for (i = 0; i < len; i += 2)
		*obuf++ = ntohs(*ibuf++);
}

static int
chaos_send_to_udp(char *buffer, int size)
{
	int wcount, dest_addr;

	/*
	 * Local loopback.
	 */
	if (uch11_csr & CHAOS_CSR_LOOP_BACK) {
		DEBUG(TRACE_CHAOS, "chaos: loopback %d bytes\n", size);
		memcpy(uch11_rcv_buffer, buffer, size);
		uch11_rcv_buffer_size = (size + 1) / 2;
		uch11_rcv_buffer_empty = false;
		uch11_rx_pkt();
		return 0;
	}
	if (hybrid_udp_and_local) {
		/* Check if it is for our "local server" */
		struct packet *packet;
		packet = (struct packet *) buffer;
		if (CH_ADDR_SHORT(packet->pk_daddr) == chaos_addr(ucfg.chaos_servername, 0)) {
			return chaos_send_to_local(buffer, size);
		}
	}
	wcount = (size + 1) / 2;
	dest_addr = ((unsigned short *) buffer)[wcount - 3];
	DEBUG(TRACE_CHAOS, "chaos: sending packet to udp (dest_addr=%o, uch11_myaddr=%o, size %d, wcount %d)\n", dest_addr, uch11_myaddr, size, wcount);
	if (size > (int) CHUDP_MAXLEN) {
		ERR(TRACE_CHAOS, "chaos: packet too long: %d", size);
		return -1;
	}
	/*
	 * Receive packets addressed to us, or broadcasts.
	 */
	if ((dest_addr == uch11_myaddr) || (dest_addr == 0)) {
		memcpy(uch11_rcv_buffer, buffer, size);
		uch11_rcv_buffer_size = (size + 1) / 2;
		uch11_rcv_buffer_empty = false;
		uch11_rx_pkt();
		if (dest_addr != 0)	/* Broadcasts should be sent also to other */
			return 0;
	}
	if (chaosd_fd == -1) {
		ERR(TRACE_CHAOS, "chaos: transmit but chaosd_fd not open!\n");
		return 0;
	}
	{
		struct chudp_header *hp = (struct chudp_header *) &trans_chudpbuf;
		u_char *op = trans_chudpbuf + sizeof(struct chudp_header);
		int nb;

		memset(trans_chudpbuf, 0, sizeof(trans_chudpbuf));
		/* Set up CHUDP header */
		hp->chudp_version = CHUDP_VERSION;
		hp->chudp_function = CHUDP_PKT;

		memcpy(op, buffer, size);

		/* Update the hw trailer dest (and checksum) since what is there is probably the ultimate dest,
		 * but it should be just the next hop */
		struct pkt_header *ph = (struct pkt_header *) ((char *) op);
		u_short pklen = LENFC_LEN(ph->ph_lenfc);
		u_short offs = sizeof(struct pkt_header) + pklen;
		if (offs % 2)
			offs++;
		struct chaos_hw_trailer *tp = (struct chaos_hw_trailer *) (op + offs);
		u_short hwdest = tp->ch_hw_destaddr;
		u_short cksum = tp->ch_hw_checksum;
		if (hwdest != udp_bridge_chaddr) {
			INFO(TRACE_CHAOS, "chaos: hw trailer dest is %#o should be %#o\n", hwdest, udp_bridge_chaddr);
			tp->ch_hw_destaddr = udp_bridge_chaddr;
		}
		tp->ch_hw_checksum = htons(uch11_checksum(op, size - 2));

		/* Now swap it */
		ntohs_buf((u_short *) op, (u_short *) op, size);

		INFO(TRACE_CHAOS, "chaos: sending %d bytes (pkt size %d)\n", size + sizeof(struct chudp_header), size);
		if ((nb = send(chaosd_fd, (char *) hp, size + sizeof(struct chudp_header), 0)) < 0) {
			perror("chaos: send to udp");
			ERR(TRACE_CHAOS, "chaos: send to udp failed");
			return -1;
		} else if (nb != size + sizeof(struct chudp_header)) {
			ERR(TRACE_CHAOS, "chaos: could not send the full pkt: %d sent, expected %d", nb, size + sizeof(struct chudp_header));
			return -1;
		}
	}
	return 0;
}

static int
chaos_poll_udp(void)
{
	/* basically copy chaos_poll_chaosd but skip CHUDP header and swap */
	ssize_t ret;
	struct pollfd pfd[1];
	int nfds, timeout;
	unsigned char lenbytes[4];
	ssize_t len;
	int dest_addr;

	if (hybrid_udp_and_local) {
		/* Prioritize local traffic */
		chaos_poll_local();
	}

	if (chaosd_fd == -1) {
		return 0;
	}
	timeout = 0;
	nfds = 1;
	pfd[0].fd = chaosd_fd;
	pfd[0].events = POLLIN;
	pfd[0].revents = 0;
	ret = poll(pfd, nfds, timeout);
	if (ret == -1) {
		ERR(TRACE_CHAOS, "chaos: polling udp: nothing there (RDN=%o)\n", uch11_csr & CHAOS_CSR_RECEIVE_DONE);
		return -1;
	} else if (ret == 0) {
		// this happens all the time so don't
		// ERR(TRACE_CHAOS, "chaos: udp poll timeout\n");
		return -1;
	}
	/*
	 * Is RX buffer full?
	 */
	if (!uch11_rcv_buffer_empty && (uch11_csr & CHAOS_CSR_RECEIVE_DONE)) {
		/*
		 * Toss packets arriving when buffer is already in
		 * use, they will be resent.
		 */
		ERR(TRACE_CHAOS, "chaos: polling udp: unread data, drop (RDN=%o, lost %d)\n", uch11_csr & CHAOS_CSR_RECEIVE_DONE, uch11_lost_count);
		uch11_lost_count++;
		/* Toss it by reading it */
		ret = recv(chaosd_fd, (char *) uch11_rcv_buffer_toss, sizeof(uch11_rcv_buffer_toss), 0);
		DEBUG(TRACE_CHAOS, "chaos: tossing udp packet of %d bytes\n", ret);
		return -1;
	}
	ret = recv(chaosd_fd, (char *) uch11_rcv_buffer, sizeof(uch11_rcv_buffer), 0);
	if (ret == -1) {
		perror("chaos: udp read");
		return -1;
	} else if (ret == 0) {
		ERR(TRACE_CHAOS, "chaos: udp read zero bytes\n");
		return -1;
	} else if (ret > (int) CHUDP_MAXLEN) {
		ERR(TRACE_CHAOS, "chaos: udp read too many bytes: %d\n", ret);
		return -1;
	}
	INFO(TRACE_CHAOS, "chaos: polling; got udp packet len %d\n", ret);
	struct chudp_header *hp = (struct chudp_header *) uch11_rcv_buffer;
	if (hp->chudp_version != CHUDP_VERSION) {
		ERR(TRACE_CHAOS, "chaos: chudp version is wrong: %d\n", hp->chudp_version);
		return -1;
	}
	if (hp->chudp_function != CHUDP_PKT) {
		ERR(TRACE_CHAOS, "chaos: chudp function is wrong: %d\n", hp->chudp_function);
		return -1;
	}
	/* zap chudp header */
	memmove((char *) uch11_rcv_buffer, ((char *) uch11_rcv_buffer) + sizeof(struct chudp_header), ret - sizeof(struct chudp_header));
	ret -= sizeof(struct chudp_header);

	/* and swap it */
	ntohs_buf(uch11_rcv_buffer, uch11_rcv_buffer, ret);

	uch11_rcv_buffer_size = (ret + 1) / 2;
	uch11_rcv_buffer_empty = false;
	dest_addr = uch11_rcv_buffer[uch11_rcv_buffer_size - 3];
	/*
	 * If not to us (or broadcast), ignore.
	 */
	if ((dest_addr != uch11_myaddr) && (dest_addr != 0)) {
		INFO(TRACE_CHAOS, "chaos: ignoring packet not to us (%o): %o\n", uch11_myaddr, dest_addr);
		uch11_rcv_buffer_size = 0;
		uch11_rcv_buffer_empty = true;
		/*
		 * Should uch11_rx_pkt be called here?
		 */
		return 0;
	}
	INFO(TRACE_CHAOS, "chaos: udp receiving packet: from %o, my %o\n", dest_addr, uch11_myaddr);
	uch11_rx_pkt();
	return 0;
}