This summer a group of us worked together to resurrect the original ARPAnet IMP software, and I’m now happy to say that the IMP lives again in simulation. It’s possible to run the original IMP software on a modified version of the H316 simh and to set up a virtual network of simulated IMPs talking to each other. IMP to IMP connections, which would have originally been carried over leased telephone lines, are tunneled over IP. As far as we can tell, everything works pretty much as it did in the early 1970s. IMPs are able to exchange routing information, console to console communications, network statistics, and they would carry host traffic if there were hosts on the network. The hooks are in there to allow simh to support the IMP side of the 1822 host interface, and the next step would be to recover the OS for an ARPAnet era host and then extend the corresponding simulator to talk to the IMP simulation.
667 lines
31 KiB
C
667 lines
31 KiB
C
/* 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
|
|
|
|
|
|
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.
|
|
|
|
One last comment - there's a nice sim_sock module which provides platform
|
|
independent TCP functions. Unfortunately there is no UDP equivalent, and this
|
|
module doesn't use sim_sock. Sorry. Even more unfortunate is that the
|
|
current implementation is WIN32/WINSOCK specific. Sorry again. There's no
|
|
reason why it couldn't be ported to other platforms, but somebody will have
|
|
to write the missing code.
|
|
*/
|
|
#ifdef VM_IMPTIP
|
|
#include "sim_defs.h" // simh machine independent definitions
|
|
#ifdef _WIN32 // WINSOCK definitions
|
|
#include <winsock2.h> // at least Windows puts it all in one file!
|
|
#elif defined(__linux__) // Linux definitions
|
|
#include <sys/socket.h> // struct socketaddr_in, et al
|
|
#include <netinet/in.h> // INADDR_NONE, et al
|
|
#include <netdb.h> // gethostbyname()
|
|
#include <fcntl.h> // fcntl() (what else??)
|
|
#include <unistd.h> // getpid(), more?
|
|
#endif
|
|
#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)
|
|
|
|
// Compatibility hacks for WINSOCK vs Linux ...
|
|
#ifdef __linux__
|
|
#define WSAGetLastError() errno
|
|
#define closesocket close
|
|
#define SOCKET int32
|
|
#define SOCKADDR struct sockaddr
|
|
#define WSAEWOULDBLOCK EWOULDBLOCK
|
|
#define INVALID_SOCKET ((SOCKET) (-1))
|
|
#define SOCKET_ERROR (-1)
|
|
#endif
|
|
|
|
// 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
|
|
uint32 ipremote; // IP address of the remote system
|
|
uint16 rxport; // OUR receiving port number
|
|
uint16 txport; // HIS receiving port number (on ipremote)
|
|
struct sockaddr_in rxaddr; // OUR receiving address (goes with rxsock!)
|
|
struct sockaddr_in txaddr; // HIS transmitting address (pairs with txsock!)
|
|
SOCKET rxsock; // socket for receiving incoming packets
|
|
SOCKET txsock; // socket for sending outgoing packets
|
|
uint32 rxsequence; // next message sequence number for receive
|
|
uint32 txsequence; // next message sequence number for transmit
|
|
};
|
|
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 ...
|
|
t_bool udp_wsa_started = FALSE; // TRUE if WSAStartup() has been called
|
|
UDP_LINK udp_links[MAXLINKS] = {0}; // data for every active connection
|
|
|
|
|
|
t_stat udp_startup (DEVICE *dptr)
|
|
{
|
|
// WINSOCK requires that WSAStartup be called exactly once before any other
|
|
// network calls are made. That's a bit inconvenient, but this routine deals
|
|
// with it by using a static variable to call WSAStartup the first time thru
|
|
// and then never again.
|
|
#ifdef _WIN32
|
|
WORD wVersionRequested = MAKEWORD(2,2);
|
|
WSADATA wsaData; int32 ret;
|
|
if (!udp_wsa_started) {
|
|
ret = WSAStartup (wVersionRequested, &wsaData);
|
|
if (ret != 0) {
|
|
fprintf(stderr,"UDP - WINSOCK startup error %d\n", ret);
|
|
return SCPE_IERR;
|
|
} else
|
|
sim_debug(IMP_DBG_UDP, dptr, "WSAStartup() called\n");
|
|
udp_wsa_started = TRUE;
|
|
}
|
|
#endif
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat udp_shutdown (DEVICE *dptr)
|
|
{
|
|
// This routine calls WSACleanup() after the last socket has been closed.
|
|
// It's essentially the opposite of udp_startup() ...
|
|
#ifdef _WIN32
|
|
if (udp_wsa_started) {
|
|
WSACleanup(); udp_wsa_started = FALSE;
|
|
sim_debug(IMP_DBG_UDP, dptr, "WSACleanup() called\n");
|
|
}
|
|
#endif
|
|
return SCPE_OK;
|
|
}
|
|
|
|
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));
|
|
// Just in case these values aren't zero!
|
|
udp_links[i].rxsock = udp_links[i].txsock = INVALID_SOCKET;
|
|
return i;
|
|
}
|
|
}
|
|
return NOLINK;
|
|
}
|
|
|
|
char *udp_format_remote (int32 link)
|
|
{
|
|
// Format the remote address and port in the format "w.x.y.z:pppp" . It's
|
|
// a bit ugly (OK, it's a lot ugly!) but it's just for error messages...
|
|
static char buf[64];
|
|
sprintf(buf, "%d.%d.%d.%d:%d",
|
|
(udp_links[link].ipremote >> 24) & 0xFF,
|
|
(udp_links[link].ipremote >> 16) & 0xFF,
|
|
(udp_links[link].ipremote >> 8) & 0xFF,
|
|
udp_links[link].ipremote & 0xFF,
|
|
udp_links[link].txport);
|
|
return buf;
|
|
}
|
|
|
|
/* get_ipaddr IP address:port
|
|
|
|
Inputs:
|
|
cptr = pointer to input string
|
|
Outputs:
|
|
ipa = pointer to IP address (may be NULL), 0 = none
|
|
ipp = pointer to IP port (may be NULL), 0 = none
|
|
result = status
|
|
*/
|
|
|
|
static t_stat get_ipaddr (char *cptr, uint32 *ipa, uint32 *ipp)
|
|
{
|
|
char gbuf[CBUFSIZE];
|
|
char *addrp, *portp, *octetp;
|
|
uint32 i, addr, port, octet;
|
|
t_stat r;
|
|
|
|
if ((cptr == NULL) || (*cptr == 0))
|
|
return SCPE_ARG;
|
|
strncpy (gbuf, cptr, CBUFSIZE);
|
|
addrp = gbuf; /* default addr */
|
|
if ((portp = strchr (gbuf, ':'))) /* x:y? split */
|
|
*portp++ = 0;
|
|
else if (strchr (gbuf, '.')) /* x.y...? */
|
|
portp = NULL;
|
|
else {
|
|
portp = gbuf; /* port only */
|
|
addrp = NULL; /* no addr */
|
|
}
|
|
if (portp) { /* port string? */
|
|
if (ipp == NULL) /* not wanted? */
|
|
return SCPE_ARG;
|
|
port = (int32) get_uint (portp, 10, 65535, &r);
|
|
if ((r != SCPE_OK) || (port == 0))
|
|
return SCPE_ARG;
|
|
}
|
|
else port = 0;
|
|
if (addrp) { /* addr string? */
|
|
if (ipa == NULL) /* not wanted? */
|
|
return SCPE_ARG;
|
|
for (i = addr = 0; i < 4; i++) { /* four octets */
|
|
octetp = strchr (addrp, '.'); /* find octet end */
|
|
if (octetp != NULL) /* split string */
|
|
*octetp++ = 0;
|
|
else if (i < 3) /* except last */
|
|
return SCPE_ARG;
|
|
octet = (int32) get_uint (addrp, 10, 255, &r);
|
|
if (r != SCPE_OK)
|
|
return SCPE_ARG;
|
|
addr = (addr << 8) | octet;
|
|
addrp = octetp;
|
|
}
|
|
if (((addr & 0377) == 0) || ((addr & 0377) == 255))
|
|
return SCPE_ARG;
|
|
}
|
|
else addr = 0;
|
|
if (ipp) /* return req values */
|
|
*ipp = port;
|
|
if (ipa)
|
|
*ipa = addr;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
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, *colon; int32 port; struct hostent *he;
|
|
if (*premote == '\0') return SCPE_2FARG;
|
|
// Look for the local port number. If it's not there, set rxport to zero for now.
|
|
port = strtoul(premote, &end, 10); udp_links[link].rxport = 0;
|
|
if ((*end == ':') && (port > 0)) {
|
|
udp_links[link].rxport = port; premote = end+1;
|
|
}
|
|
|
|
// Look for "name:port" and extract the remote port...
|
|
if ((colon = strchr(premote, ':')) == NULL) return SCPE_ARG;
|
|
*colon++ = '\0'; port = strtoul(colon, &end, 10);
|
|
if ((*end != '\0') || (port == 0)) return SCPE_ARG;
|
|
udp_links[link].txport = port;
|
|
if (udp_links[link].rxport == 0) udp_links[link].rxport = port;
|
|
|
|
// Now try to parse the host as a dotted IP address ...
|
|
if (get_ipaddr(premote, &udp_links[link].ipremote, NULL) == SCPE_OK) return SCPE_OK;
|
|
|
|
// Special kludge - allow just ":port" to mean "localhost:port" ...
|
|
if(*premote == '\0') {
|
|
if (udp_links[link].rxport == udp_links[link].txport)
|
|
fprintf(stderr,"WARNING - use different transmit and receive ports!\n");
|
|
premote = "localhost";
|
|
}
|
|
|
|
// Not a dotted IP - try to lookup a host name ...
|
|
if ((he = gethostbyname(premote)) == NULL) return SCPE_OPENERR;
|
|
udp_links[link].ipremote = * (unsigned long *) he->h_addr_list[0];
|
|
if (udp_links[link].ipremote == INADDR_NONE) {
|
|
fprintf(stderr,"WARNING - unable to resolve \"%s\"\n", premote);
|
|
return SCPE_OPENERR;
|
|
}
|
|
udp_links[link].ipremote = ntohl(udp_links[link].ipremote);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat udp_socket_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_rx_socket (int32 link)
|
|
{
|
|
// This routine will create the receiver socket for the virtual modem.
|
|
// Sockets are always UDP and, in the case of the receiver, bound to the port
|
|
// specified. Receiving sockets are also always set to NON BLOCKING mode.
|
|
int32 iret; uint32 flags = 1;
|
|
|
|
// Creating the socket works on both Windows and Linux ...
|
|
udp_links[link].rxsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (udp_links[link].rxsock == INVALID_SOCKET)
|
|
return udp_socket_error(link, "RX socket()");
|
|
udp_links[link].rxaddr.sin_family = AF_INET;
|
|
udp_links[link].rxaddr.sin_port = htons(udp_links[link].rxport);
|
|
udp_links[link].rxaddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
iret = bind(udp_links[link].rxsock, (SOCKADDR *) &udp_links[link].rxaddr, sizeof(struct sockaddr_in));
|
|
if (iret != 0)
|
|
return udp_socket_error(link, "bind()");
|
|
|
|
// But making it non-blocking is a problem ...
|
|
#ifdef _WIN32
|
|
iret = ioctlsocket(udp_links[link].rxsock, FIONBIO, (u_long *) &flags);
|
|
if (iret != 0)
|
|
return udp_socket_error(link, "ioctlsocket()");
|
|
#elif defined(__linux__)
|
|
flags = fcntl(udp_links[link].rxsock, F_GETFL, 0);
|
|
if (flags == -1) return udp_socket_error(link, "fcntl(F_GETFL)");
|
|
iret = fcntl(udp_links[link].rxsock, F_SETFL, flags | O_NONBLOCK);
|
|
if (iret == -1) return udp_socket_error(link, "fcntl(F_SETFL)");
|
|
iret = fcntl(udp_links[link].rxsock, F_SETOWN, getpid());
|
|
if (iret == -1) return udp_socket_error(link, "fcntl(F_SETOWN)");
|
|
#endif
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat udp_create_tx_socket (int32 link)
|
|
{
|
|
// This routine will create the transmitter socket for the virtual modem.
|
|
// In the case of the transmitter, we don't bind the socket at this time -
|
|
// WINSOCK will automatically bind it for us to a free port on the first IO.
|
|
// Also note that transmitting sockets are blocking; we don't have code (yet!)
|
|
// to allow them to be nonblocking.
|
|
udp_links[link].txsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (udp_links[link].txsock == INVALID_SOCKET)
|
|
return udp_socket_error(link, "TX socket()");
|
|
|
|
// Initialize the txaddr structure too - note that this isn't used now; it's
|
|
// the sockaddr we will use when we later do a sendto() the remote host!
|
|
udp_links[link].txaddr.sin_family = AF_INET;
|
|
udp_links[link].txaddr.sin_port = htons(udp_links[link].txport);
|
|
udp_links[link].txaddr.sin_addr.s_addr = htonl(udp_links[link].ipremote);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
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;
|
|
int32 link = udp_find_free_link();
|
|
if (link < 0) return SCPE_MEM;
|
|
|
|
// Make sure WINSOCK is initialized ...
|
|
if ((ret = udp_startup(dptr)) != SCPE_OK) return ret;
|
|
|
|
// Parse the remote name and set up the ipaddr and port ...
|
|
if ((ret = udp_parse_remote(link, premote)) != SCPE_OK) return ret;
|
|
|
|
// Create the sockets for the transmitter and receiver ...
|
|
if ((ret = udp_create_rx_socket(link)) != SCPE_OK) return ret;
|
|
if ((ret = udp_create_tx_socket(link)) != SCPE_OK) return ret;
|
|
|
|
// All done - mark the TCP_LINK data as "used" and return the index.
|
|
udp_links[link].used = TRUE; *pln = link;
|
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - listening on port %d and sending to %s\n", link, udp_links[link].rxport, udp_format_remote(link));
|
|
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.
|
|
int32 iret, i;
|
|
if ((link < 0) || (link >= MAXLINKS)) return SCPE_IERR;
|
|
if (!udp_links[link].used) return SCPE_IERR;
|
|
|
|
// Close the sockets associated with this connection - that's easy ...
|
|
iret = closesocket(udp_links[link].rxsock);
|
|
if (iret != 0) udp_socket_error(link, "closesocket()");
|
|
iret = closesocket(udp_links[link].txsock);
|
|
if (iret != 0) udp_socket_error(link, "closesocket()");
|
|
udp_links[link].used = FALSE;
|
|
sim_debug(IMP_DBG_UDP, dptr, "link %d - closed\n", link);
|
|
|
|
// If we just closed the last link, then call udp_shutdown() ...
|
|
for (i = 0; i < MAXLINKS; ++i) {
|
|
if (udp_links[i].used) return SCPE_OK;
|
|
}
|
|
return udp_shutdown(dptr);
|
|
}
|
|
|
|
t_stat udp_send_to (DEVICE *dptr, int32 link, uint16 *pdata, uint16 count, SOCKADDR *pdest)
|
|
{
|
|
// 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; int32 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;
|
|
|
|
// 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 = sendto(udp_links[link].txsock, (const char *) &pkt, pktlen, 0, pdest, sizeof (struct sockaddr_in));
|
|
if (iret == SOCKET_ERROR) return udp_socket_error(link, "sendto()");
|
|
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_send (DEVICE *dptr, int32 link, uint16 *pdata, uint16 count)
|
|
{
|
|
// Send an IMP packet to the remote simh. This is the usual case - the only
|
|
// reason there's any other options at all is so we can emulate loopback.
|
|
return udp_send_to (dptr, link, pdata, count, (SOCKADDR *) &(udp_links[link].txaddr));
|
|
}
|
|
|
|
t_stat udp_send_self (DEVICE *dptr, int32 link, uint16 *pdata, uint16 count)
|
|
{
|
|
// Send an IMP packet to our own receiving socket. This might seem silly,
|
|
// but it's used to emulate the line loopback function...
|
|
struct sockaddr_in self;
|
|
self.sin_family = AF_INET;
|
|
self.sin_port = htons(udp_links[link].rxport);
|
|
self.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
return udp_send_to (dptr, link, pdata, count, (SOCKADDR *) &self);
|
|
}
|
|
|
|
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!
|
|
int32 pktsiz;
|
|
struct sockaddr_in sender;
|
|
#if defined (macintosh) || defined (__linux) || defined (__linux__) || \
|
|
defined (__APPLE__) || defined (__OpenBSD__) || \
|
|
defined(__NetBSD__) || defined(__FreeBSD__) || \
|
|
(defined(__hpux) && defined(_XOPEN_SOURCE_EXTENDED))
|
|
socklen_t sndsiz = (socklen_t)sizeof(sender);
|
|
#elif defined (_WIN32) || defined (__EMX__) || \
|
|
(defined (__ALPHA) && defined (__unix__)) || \
|
|
defined (__hpux)
|
|
int sndsiz = (int)sizeof(sender);
|
|
#else
|
|
size_t sndsiz = sizeof(sender);
|
|
#endif
|
|
|
|
pktsiz = recvfrom(udp_links[link].rxsock, (char *) ppkt, sizeof(UDP_PACKET),
|
|
0, (SOCKADDR *) &sender, &sndsiz);
|
|
if (pktsiz >= 0) return pktsiz;
|
|
if (WSAGetLastError() == WSAEWOULDBLOCK) return 0;
|
|
udp_socket_error(link, "recvfrom()");
|
|
return NOLINK;
|
|
}
|
|
|
|
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;
|
|
|
|
while ((pktlen = udp_receive_packet(link, &pkt)) > 0) {
|
|
// First do some header checks for a valid UDP packet ...
|
|
if (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.
|
|
pktseq = ntohl(pkt.sequence);
|
|
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;
|
|
}
|
|
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
|