/* h316_udp.c: IMP/TIP Modem and Host Interface socket routines using UDP | |
Copyright (c) 2013 Robert Armstrong, bob@jfcl.com | |
Permission is hereby granted, free of charge, to any person obtaining a | |
copy of this software and associated documentation files (the "Software"), | |
to deal in the Software without restriction, including without limitation | |
the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
and/or sell copies of the Software, and to permit persons to whom the | |
Software is furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in | |
all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
ROBERT ARMSTRONG BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER | |
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
Except as contained in this notice, the name of Robert Armstrong shall not be | |
used in advertising or otherwise to promote the sale, use or other dealings | |
in this Software without prior written authorization from Robert Armstrong. | |
REVISION HISTORY | |
udp socket routines | |
26-Jun-13 RLA Rewritten from TCP version | |
26-Nov-13 MP Rewritten to use TMXR layer packet semantics thus | |
allowing portability to all simh hosts. | |
2-Dec-13 RLA Improve error recovery if the other simh is restarted | |
OVERVIEW | |
This module emulates low level communications between two virtual modems | |
using UDP packets over the modern network connections. It's used by both | |
the IMP modem interface and the host interface modules to implement IMP to | |
IMP and IMP to HOST connections. | |
TCP vs UDP | |
Why UDP and not TCP? TCP has a couple of advantages after all - it's | |
stream oriented, which is intrinsically like a modem, and it handles all | |
the network "funny stuff" for us. TCP has a couple of problems too - first, | |
it's inherently asymmetrical. There's a "server" end which opens a master | |
socket and passively listens for connections, and a "client" end which | |
actively attempts to connect. That's annoying, but it can be worked around. | |
The big problem with TCP is that even though it treats the data like a stream | |
it's internally buffering it, and you really have absolutely no control over | |
when TCP will decide to send its buffer. Google "nagle algorithm" to get an | |
idea. Yes, you can set TCP_NODELAY to disable Nagle, but the data's still | |
buffered and it doesn't fix the problem. What's the issue with buffering? | |
It introduces completely unpredictable delays into the message traffic. A | |
transmitting IMP could send two or three (or ten or twenty!) messages before | |
TCP actually decides to try to deliver them to the destination. | |
And it turns out that IMPs are extraordinarily sensitive to line speed. The | |
IMP firmware actually goes to the trouble of measuring the effective line | |
speed by using the RTC to figure out how long it takes to send a message. | |
One thing that screws up the IMP to no end is variation in the effective | |
line speed. I guess they had a lot of trouble with AT&T Long Lines back in | |
the Old Days, and the IMP has quite a bit of code to monitor line quality. | |
Even fairly minor variations in speed will cause it to mark the line as | |
"down" and sent a trouble report back to BBN. And no, I'm not making this up! | |
UDP gives us a few advantages. First, it's inherently packet oriented so | |
we can simply grab the entire packet from the transmitting IMP's memory, wrap | |
a little extra information around it, and ship it off in one datagram. The | |
receiving IMP gets the whole packet at once and it can simply BLT it into | |
the receiving IMP's memory. No fuss, no muss, no need convert the packet | |
into a stream, add word counts, wait for complete packets, etc. And UDP is | |
symmetrical - both ends listen and send in the same way. There's no need for | |
master sockets, passive (server) and active (client) ends, or any of that. | |
Also UDP has no buffering - the packet goes out on the wire when we send it. | |
The data doesn't wait around in some buffer for TCP to decide when it wants | |
to let it go. The latency and delay for UDP is much more predictable and | |
consistent, at least for local networks. If you're actually sending the | |
packets out on the big, wide, Internet then all bets are off on that. | |
UDP has a few problems that we have to worry about. First, it's not | |
guaranteed delivery so just because one IMP sends a packet doesn't mean that | |
the other end will ever see it. Surprisingly that's not a problem for us. | |
Phone lines have noise and dropouts, and real modems lose packets too. The | |
IMP code is completely happy and able to deal with that, and generally we | |
don't worry about dropped packets at all. | |
There are other issues with UDP - it doesn't guarantee packet order, so the | |
sending IMP might transmit packets 1, 2 and 3 and the receiving IMP will get | |
1, 3 then 2. THAT would never happen with a real modem and we have to shield | |
the IMP code from any such eventuality. Also, with UDP packets can be | |
duplicated so the receiving IMP might see 1, 2, 2, 3 (or even 1, 3, 2, 1!). | |
Again, a modem would never do that and we have to prevent it from happening. | |
Both cases are easily dealt with by adding a sequence number to the header | |
we wrap around the IMP's packet. Out of sequence or duplicate packets can | |
be detected and are simply dropped. If necessary, the IMP will deal with | |
retransmitting them in its own time. | |
One more thing about UDP - there is no way to tell whether a connection is | |
established or not and for that matter there is no "connection" at all | |
(that's why it's a "connectionless" protocol, after all!). We simply send | |
packets out and there's no way to know whether anybody is hearing them. The | |
real IMP modem hardware had no carrier detect or other dataset control | |
functions, so it was identical in that respect. An IMP sent messages out the | |
modem and, unless it received a message back, it had no way to know whether | |
the IMP on the other end was hearing them. | |
INTERFACE | |
This module provides a simplified UDP socket interface. These functions are | |
implemented - | |
udp_create define a connection to the remote IMP | |
udp_release release a connection | |
udp_send send an IMP message to the other end | |
udp_receive receive (w/o blocking!) a message if available | |
Note that each connection is assigned a unique "handle", a small integer, | |
which is used as an index into our internal connection data table. There | |
is a limit on the maximum number of connections available, as set my the | |
MAXLINKS parameter. Also, notice that all links are intrinsically full | |
duplex and bidirectional - data can be sent and received in both directions | |
independently. Real modems and host cards were exactly the same. | |
*/ | |
#ifdef VM_IMPTIP | |
#include "sim_defs.h" // simh machine independent definitions | |
#include "sim_tmxr.h" // The MUX layer exposes packet send and receive semantics | |
#include "h316_defs.h" // H316 emulator definitions | |
#include "h316_imp.h" // ARPAnet IMP/TIP definitions | |
// Local constants ... | |
#define MAXLINKS 10 // maximum number of simultaneous connections | |
// This constant determines the longest possible IMP data payload that can be | |
// sent. Most IMP messages are trivially small - 68 words or so - but, when one | |
// IMP asks for a reload the neighbor IMP sends the entire memory image in a | |
// single message! That message is about 14K words long. | |
// The next thing you should worry about is whether the underlying IP network | |
// can actually send a UDP packet of this size. It turns out that there's no | |
// simple answer to that - it'll be fragmented for sure, but as long as all | |
// the fragments arrive intact then the destination should reassemble them. | |
#define MAXDATA 16384 // longest possible IMP packet (in H316 words) | |
// UDP connection data structure ... | |
// One of these blocks is allocated for every simulated modem link. | |
struct _UDP_LINK { | |
t_bool used; // TRUE if this UDP_LINK is in use | |
char rhostport[64]; // Remote host:port | |
char lport[64]; // Local port | |
uint32 rxsequence; // next message sequence number for receive | |
uint32 txsequence; // next message sequence number for transmit | |
DEVICE *dptr; // Device associated with link | |
}; | |
typedef struct _UDP_LINK UDP_LINK; | |
// This magic number is stored at the beginning of every UDP message and is | |
// checked on receive. It's hardly foolproof, but its a simple attempt to | |
// guard against other applications dumping unsolicited UDP messages into our | |
// receiver socket... | |
#define MAGIC ((uint32) (((((('H' << 8) | '3') << 8) | '1') << 8) | '6')) | |
// UDP wrapper data structure ... | |
// This is the UDP packet which is actually transmitted or received. It | |
// contains the actual IMP packet, plus whatever additional information we | |
// need to keep track of things. NOTE THAT ALL DATA IN THIS PACKET, INCLUDING | |
// THE H316 MEMORY WORDS, ARE SENT AND RECEIVED WITH NETWORK BYTE ORDER! | |
struct _UDP_PACKET { | |
uint32 magic; // UDP "magic number" (see above) | |
uint32 sequence; // UDP packet sequence number | |
uint16 count; // number of H316 words to follow | |
uint16 data[MAXDATA]; // and the actual H316 data words/IMP packet | |
}; | |
typedef struct _UDP_PACKET UDP_PACKET; | |
#define UDP_HEADER_LEN (2*sizeof(uint32) + sizeof(uint16)) | |
// Locals ... | |
UDP_LINK udp_links[MAXLINKS] = { {0} }; // data for every active connection | |
TMLN udp_lines[MAXLINKS] = { {0} }; // line descriptors | |
TMXR udp_tmxr = { MAXLINKS, NULL, 0, udp_lines};// datagram mux | |
int32 udp_find_free_link (void) | |
{ | |
// Find a free UDP_LINK block, initialize it and return its index. If none | |
// are free, then return -1 ... | |
int32 i; | |
for (i = 0; i < MAXLINKS; ++i) { | |
if (udp_links[i].used == 0) { | |
memset(&udp_links[i], 0, sizeof(UDP_LINK)); | |
return i; | |
} | |
} | |
return NOLINK; | |
} | |
t_stat udp_parse_remote (int32 link, char *premote) | |
{ | |
// This routine will parse a remote address string in any of these forms - | |
// | |
// llll:w.x.y.z:rrrr | |
// llll:name.domain.com:rrrr | |
// llll::rrrr | |
// w.x.y.z:rrrr | |
// name.domain.com:rrrr | |
// | |
// In all examples, "llll" is the local port number that we use for listening, | |
// and "rrrr" is the remote port number that we use for transmitting. The | |
// local port is optional and may be omitted, in which case it defaults to the | |
// same as the remote port. This works fine if the other IMP is actually on | |
// a different host, but don't try that with localhost - you'll be talking to | |
// yourself!! In both cases, "w.x.y.z" is a dotted IP for the remote machine | |
// and "name.domain.com" is its name (which will be looked up to get the IP). | |
// If the host name/IP is omitted then it defaults to "localhost". | |
char *end; int32 lport, rport; t_stat ret; | |
char host[64], port[16]; | |
if (*premote == '\0') return SCPE_2FARG; | |
memset (udp_links[link].lport, 0, sizeof(udp_links[link].lport)); | |
memset (udp_links[link].rhostport, 0, sizeof(udp_links[link].rhostport)); | |
// Handle the llll::rrrr case first | |
if (2 == sscanf (premote, "%d::%d", &lport, &rport)) { | |
if ((lport < 1) || (lport >65535) || (rport < 1) || (rport >65535)) return SCPE_ARG; | |
sprintf (udp_links[link].lport, "%d", lport); | |
sprintf (udp_links[link].rhostport, "localhost:%d", rport); | |
return SCPE_OK; | |
} | |
// Look for the local port number and save it away. | |
lport = strtoul(premote, &end, 10); | |
if ((*end == ':') && (lport > 0)) { | |
sprintf (udp_links[link].lport, "%d", lport); | |
premote = end+1; | |
} | |
if (sim_parse_addr (premote, host, sizeof(host), "localhost", port, sizeof(port), NULL, NULL)) | |
return SCPE_ARG; | |
sprintf (udp_links[link].rhostport, "%s:%s", host, port); | |
if (udp_links[link].lport[0] == '\0') | |
strcpy (udp_links[link].lport, port); | |
if ((strcmp (udp_links[link].lport, port) == 0) && | |
(strcmp ("localhost", host) == 0)) | |
fprintf(stderr,"WARNING - use different transmit and receive ports!\n"); | |
return SCPE_OK; | |
} | |
t_stat udp_error (int32 link, const char *msg) | |
{ | |
// This routine is called whenever a SOCKET_ERROR is returned for any I/O. | |
fprintf(stderr,"UDP%d - %s failed with error %d\n", link, msg, WSAGetLastError()); | |
return SCPE_IOERR; | |
} | |
t_stat udp_create (DEVICE *dptr, char *premote, int32 *pln) | |
{ | |
// Create a logical UDP link to the specified remote system. The "remote" | |
// string specifies both the remote host name or IP and a port number. The | |
// port number is both the port we send datagrams to, and also the port we | |
// listen on for incoming datagrams. UDP doesn't have any real concept of a | |
// "connection" of course, and this routine simply creates the necessary | |
// sockets in this host. We have no way of knowing whether the remote host is | |
// listening or even if it exists. | |
// | |
// We return SCPE_OK if we're successful and an error code if we aren't. If | |
// we are successful, then the ln parameter is assigned the link number, | |
// which is a handle used to identify this connection to all future udp_xyz() | |
// calls. | |
t_stat ret; | |
char linkinfo[128]; | |
int32 link = udp_find_free_link(); | |
if (link < 0) return SCPE_MEM; | |
// Parse the remote name and set up the ipaddr and port ... | |
if ((ret = udp_parse_remote(link, premote)) != SCPE_OK) return ret; | |
// Create the socket connection to the destination ... | |
sprintf(linkinfo, "Buffer=%d,Line=%d,%s,UDP,Connect=%s", (int)(sizeof(UDP_PACKET)+sizeof(int32)), link, udp_links[link].lport, udp_links[link].rhostport); | |
ret = tmxr_open_master (&udp_tmxr, linkinfo); | |
if (ret != SCPE_OK) return ret; | |
// All done - mark the TCP_LINK data as "used" and return the index. | |
udp_links[link].used = TRUE; *pln = link; | |
udp_lines[link].dptr = udp_links[link].dptr = dptr; // save device | |
udp_tmxr.uptr = dptr->units; | |
udp_tmxr.last_poll_time = 1; // h316'a use of TMXR doesn't poll periodically for connects | |
tmxr_poll_conn (&udp_tmxr); // force connection initialization now | |
udp_tmxr.last_poll_time = 1; // h316'a use of TMXR doesn't poll periodically for connects | |
sim_debug(IMP_DBG_UDP, dptr, "link %d - listening on port %s and sending to %s\n", link, udp_links[link].lport, udp_links[link].rhostport); | |
return SCPE_OK; | |
} | |
t_stat udp_release (DEVICE *dptr, int32 link) | |
{ | |
// Close a link that was created by udp_create() and release any resources | |
// allocated to it. We always return SCPE_OK unless the link specified is | |
// already unused. | |
if ((link < 0) || (link >= MAXLINKS)) return SCPE_IERR; | |
if (!udp_links[link].used) return SCPE_IERR; | |
if (dptr != udp_links[link].dptr) return SCPE_IERR; | |
tmxr_detach_ln (&udp_lines[link]); | |
udp_links[link].used = FALSE; | |
sim_debug(IMP_DBG_UDP, dptr, "link %d - closed\n", link); | |
return SCPE_OK; | |
} | |
t_stat udp_send (DEVICE *dptr, int32 link, uint16 *pdata, uint16 count) | |
{ | |
// This routine does all the work of sending an IMP data packet. pdata | |
// is a pointer (usually into H316 simulated memory) to the IMP packet data, | |
// count is the length of the data (in H316 words, not bytes!), and pdest is | |
// the destination socket. There are two things worthy of note here - first, | |
// notice that the H316 words are sent in network order, so the remote simh | |
// doesn't necessarily need to have the same endian-ness as this machine. | |
// Second, notice that transmitting sockets are NOT set to non blocking so | |
// this routine might wait, but we assume the wait will never be too long. | |
UDP_PACKET pkt; int pktlen; uint16 i; t_stat iret; | |
if ((link < 0) || (link >= MAXLINKS)) return SCPE_IERR; | |
if (!udp_links[link].used) return SCPE_IERR; | |
if ((pdata == NULL) || (count == 0) || (count > MAXDATA)) return SCPE_IERR; | |
if (dptr != udp_links[link].dptr) return SCPE_IERR; | |
// Build the UDP packet, filling in our own header information and copying | |
// the H316 words from memory. REMEMBER THAT EVERYTHING IS IN NETWORK ORDER! | |
pkt.magic = htonl(MAGIC); | |
pkt.sequence = htonl(udp_links[link].txsequence++); | |
pkt.count = htons(count); | |
for (i = 0; i < count; ++i) pkt.data[i] = htons(*pdata++); | |
pktlen = UDP_HEADER_LEN + count*sizeof(uint16); | |
// Send it and we're outta here ... | |
iret = tmxr_put_packet_ln (&udp_lines[link], (const uint8 *)&pkt, (size_t)pktlen); | |
if (iret != SCPE_OK) return udp_error(link, "tmxr_put_packet_ln()"); | |
sim_debug(IMP_DBG_UDP, dptr, "link %d - packet sent (sequence=%d, length=%d)\n", link, ntohl(pkt.sequence), ntohs(pkt.count)); | |
return SCPE_OK; | |
} | |
t_stat udp_set_link_loopback (DEVICE *dptr, int32 link, t_bool enable_loopback) | |
{ | |
// Enable or disable the local (interface) loopback on this link... | |
if ((link < 0) || (link >= MAXLINKS)) return SCPE_IERR; | |
if (!udp_links[link].used) return SCPE_IERR; | |
if (dptr != udp_links[link].dptr) return SCPE_IERR; | |
return tmxr_set_line_loopback (&udp_lines[link], enable_loopback); | |
} | |
int32 udp_receive_packet (int32 link, UDP_PACKET *ppkt) | |
{ | |
// This routine will do the hard part of receiving a UDP packet. If it's | |
// successful the packet length, in bytes, is returned. The receiver socket | |
// is non-blocking, so if no packet is available then zero will be returned | |
// instead. Lastly, if a fatal socket I/O error occurs, -1 is returned. | |
// | |
// Note that this routine only receives the packet - it doesn't handle any | |
// of the checking for valid packets, unexpected packets, duplicate or out of | |
// sequence packets. That's strictly the caller's problem! | |
size_t pktsiz; | |
const uint8 *pbuf; | |
t_stat ret; | |
udp_lines[link].rcve = TRUE; // Enable receiver | |
tmxr_poll_rx (&udp_tmxr); | |
ret = tmxr_get_packet_ln (&udp_lines[link], &pbuf, &pktsiz); | |
udp_lines[link].rcve = FALSE; // Disable receiver | |
if (ret != SCPE_OK) { | |
udp_error(link, "tmxr_get_packet_ln()"); | |
return NOLINK; | |
} | |
if (pbuf == NULL) return 0; | |
// Got a packet, so copy it to the packet buffer | |
memcpy (ppkt, pbuf, pktsiz); | |
return pktsiz; | |
} | |
int32 udp_receive (DEVICE *dptr, int32 link, uint16 *pdata, uint16 maxbuf) | |
{ | |
// Receive an IMP packet from the virtual modem. pdata is a pointer usually | |
// directly into H316 simulated memory) to where the IMP packet data should | |
// be stored, and maxbuf is the maximum length of that buffer in H316 words | |
// (not bytes!). If a message is successfully received then this routine | |
// returns the length, again in H316 words, of the IMP packet. The caller | |
// can detect buffer overflows by comparing this result to maxbuf. If no | |
// packets are waiting right now then zero is returned, and -1 is returned | |
// in the event of any fatal socket I/O error. | |
// | |
// This routine also handles checking for unsolicited messages and duplicate | |
// or out of sequence messages. All of these are unceremoniously discarded. | |
// | |
// One final note - it's explicitly allowed for pdata to be null and/or | |
// maxbuf to be zero. In either case the received package is discarded, but | |
// the actual length of the discarded package is still returned. | |
UDP_PACKET pkt; int32 pktlen, explen, implen, i; uint32 magic, pktseq; | |
if ((link < 0) || (link >= MAXLINKS)) return SCPE_IERR; | |
if (!udp_links[link].used) return SCPE_IERR; | |
if (dptr != udp_links[link].dptr) return SCPE_IERR; | |
while ((pktlen = udp_receive_packet(link, &pkt)) > 0) { | |
// First do some header checks for a valid UDP packet ... | |
if (((size_t)pktlen) < UDP_HEADER_LEN) { | |
sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet w/o header (length=%d)\n", link, pktlen); | |
continue; | |
} | |
magic = ntohl(pkt.magic); | |
if (magic != MAGIC) { | |
sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet w/bad magic number (magic=%08x)\n", link, magic); | |
continue; | |
} | |
implen = ntohs(pkt.count); | |
explen = UDP_HEADER_LEN + implen*sizeof(uint16); | |
if (explen != pktlen) { | |
sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet length wrong (expected=%d received=%d)\n", link, explen, pktlen); | |
continue; | |
} | |
// Now the hard part = check the sequence number. The rxsequence value is | |
// the number of the next packet we expect to receive - that's the number | |
// this packet should have. If this packet's sequence is less than that, | |
// then this packet is out of order or a duplicate and we discard it. If | |
// this packet is greater than that, then we must have missed one or two | |
// packets. In that case we MUST update rxsequence to match this one; | |
// otherwise the two ends could never resynchronize after a lost packet. | |
// | |
// And there's one final complication to worry about - if the simh on the | |
// other end is restarted for some reason, then his sequence numbers will | |
// reset to zero. In that case we'll never recover synchronization without | |
// special efforts. The hack is to check for a packet sequence number of | |
// zero and, if we find it, force synchronization. This improves the | |
// situation, but I freely admit that it's possible to think of a number of | |
// cases where this also fails. The only absolute solution is to implement | |
// a more complicated system with non-IMP control messages exchanged between | |
// the modem emulation on both ends. That'd be nice, but I'll leave it as | |
// an exercise for later. | |
pktseq = ntohl(pkt.sequence); | |
if ((pktseq == 0) && (udp_links[link].rxsequence != 0)) { | |
sim_debug(IMP_DBG_UDP, dptr, "link %d - remote modem restarted\n", link); | |
} else if (pktseq < udp_links[link].rxsequence) { | |
sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet out of sequence 1 (expected=%d received=%d\n", link, udp_links[link].rxsequence, pktseq); | |
continue; // discard this packet! | |
} | |
else if (pktseq != udp_links[link].rxsequence) { | |
sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet out of sequence 2 (expected=%d received=%d\n", link, udp_links[link].rxsequence, pktseq); | |
} | |
udp_links[link].rxsequence = pktseq+1; | |
// It's a valid packet - if there's no buffer then just discard it. | |
if ((pdata == NULL) || (maxbuf == 0)) { | |
sim_debug(IMP_DBG_UDP, dptr, "link %d - received packet discarded (no buffer available)\n", link); | |
return implen; | |
} | |
// Copy the data to the H316 memory and we're done! | |
sim_debug(IMP_DBG_UDP, dptr, "link %d - packet received (sequence=%d, length=%d)\n", link, pktseq, pktlen); | |
for (i = 0; i < (implen < maxbuf ? implen : maxbuf); ++i) | |
*pdata++ = ntohs(pkt.data[i]); | |
return implen; | |
} | |
// Here if pktlen <= 0 ... | |
return pktlen; | |
} | |
#endif // ifdef VM_IMPTIP |