Networking with SIMH

From Computer History Wiki
Jump to: navigation, search

The aim of this quick tutorial is to show how to setup SIMH's networking with VAXen and PDP-11s.


libpcap

By default SIMH supports networking via libpcap. The restriction of this method is that the host machine is unable to communicate with the emulator. Otherwise it's relatively straight forward. First you need to make sure your binary has networking built in.

Types of host ethernet adapters

There are three types of host ethernet adapters:

  • XQ for QBUS computers
  • XU for UNIBUS computers
  • XS for computers with neither QBUS nor UNIBUS (e.g. a lot of VAXstations)

Showing host ethernet adapters

By issuing the command 'show xq eth' you will get either a list of adapters, or an error that this version does not support networking.

On the Windows platform it would look something like this:

With networking:

sim> show xq eth
ETH devices:
  0  \Device\NPF_GenericDialupAdapter                   (Adapter for generic dialup and VPN capture)
  1  \Device\NPF_{9E485825-641F-45C3-BC3B-E9DF4DDE520D} (Local Area Connection 4)
  2  \Device\NPF_{279EDB55-7802-44D3-8FA1-15B14452B309} (Local Area Connection)
sim>

Without networking:

sim> show xq eth
ETH devices:
  network support not available in simulator
sim>

Setting the MAC Address

To set the MAC Address to something other then the default (which is important if you ever plan on having more than one SIMH device talking to another, say in HECnet) you need to change the MAC address. Remember this needs to be done before you attach the device. The command is simple as all you have to do is type in something to the effect of:

sim> set xq mac=00:00:01:00:00:01

Remember the first half is the vendor code, and the second half is the unique identifier. Do not use all 0's or all FF's as it will create major issues on your network.

Attaching the device

Once the host ethernet adapter to bind to has been identified, the MAC address has been configured, you can now attach the emulator to the host network card. You can either specifiy the whole name as listed from the 'xq show dev' command, or use the corresponding number. Thusly:

att xq eth2

And

att xq \Device\NPF_{279EDB55-7802-44D3-8FA1-15B14452B309}

Are identical. You can now boot your emulator and it will have network accesss.

HECnet

The following code snippet in sim_ether.c will allow SIMH to talk on a HECnet network. For now the destination bridge & port are compiled in. The following code however is weird in that the destination isn't initialized.. it needs.. fixing..

The format is as follows:

att xq 192.168.0.1:7771

only dot notation for hosts!

struct sockaddr_in si_me, si_other;
int s, i, slen;
char host[55];
char port[16];

#define BUFLEN 1518

#ifdef _WIN32
int inet_aton (const char * str, struct in_addr * add)
{
    add->s_addr = inet_addr( str );
    if( add->s_addr == INADDR_NONE )
        return 0;
    return 1;
}
#endif


t_stat eth_open(ETH_DEV* dev, char* name, DEVICE* dptr, uint32 dbit)
{
int32 fl;
char parsename[255];
char *pch;
#if defined (_WIN32)
WORD wVersionRequested;
WSADATA wsaData;
#endif

memset(&host,0x0,sizeof(host));
memset(&port,0x0,sizeof(port));
memset(&parsename,0x0,sizeof(parsename));
sprintf(parsename,"%s",name);
pch=strchr(parsename,':');
*pch=' ';
sscanf(parsename,"%s %s",&host,&port);
printf("HECnet connecting to %s:%s\n",host,port);

slen=sizeof(si_other);

#if defined (_WIN32)
wVersionRequested = MAKEWORD (1, 1);
WSAStartup (wVersionRequested, &wsaData);
#endif

if ((s=socket(PF_INET, SOCK_DGRAM, 0))==-1)
        printf("socket error\n");
memset((char *) &si_me, 0, sizeof(si_me));
si_me.sin_family = PF_INET;
si_me.sin_port = htons(atoi(port));
si_me.sin_addr.s_addr = INADDR_ANY;
if (bind(s, &si_me, sizeof(si_me))==-1)
        {
        printf("error binding socket\n");
        exit(-1);
        }

#if defined (_WIN32)
unsigned long non_block=1;
ioctlsocket(s,FIONBIO,&non_block);
#else
fl = fcntl (s, F_GETFL,0);
fcntl (s, F_SETFL, fl | O_NONBLOCK);
#endif
return SCPE_OK;
}

t_stat eth_close (ETH_DEV* dev)
{
close(s);
return SCPE_OK;
}

t_stat eth_write (ETH_DEV* dev, ETH_PACK* packet, ETH_PCALLBACK routine)
{
char buf[BUFLEN];

si_other.sin_family=PF_INET;
si_other.sin_port=htons(atoi(port));
inet_aton(host,&si_other.sin_addr);

slen=sizeof(si_other);

        if (sendto(s, packet->msg, packet->len, 0, &si_other, slen)==-1)
                {  //error sending packet!
                if(routine)
                        (routine)(1);
                }
        else{
                if(routine)
                (routine)(0);}
return SCPE_OK;
}


t_stat eth_read (ETH_DEV* dev, ETH_PACK* packet, ETH_PCALLBACK routine)
{
char buf[2500];
int rc;

if(!dev) return SCPE_UNATT;
if(!packet) return SCPE_ARG;

dev->read_packet = packet;
packet->len = 0;
dev->read_callback = routine;

slen=sizeof(si_other);

memset(&buf,0x0,sizeof(buf));
rc=recvfrom(s, buf, BUFLEN, 0, &si_other, &slen);
if (rc==-1)
        {} //error?
else {
        dev->read_packet->len=rc;
       memcpy(dev->read_packet->msg,buf,rc);
       eth_add_crc32(dev->read_packet);
       if(dev->read_callback)
        (dev->read_callback)(0);
}

return SCPE_OK;
}

t_stat eth_filter (ETH_DEV* dev, int addr_count,
                   ETH_MAC* addresses,
                   ETH_BOOL all_multicast,
                   ETH_BOOL promiscuous)
{
return SCPE_OK;
}

int eth_devices (int max, ETH_LIST* dev)
{
return SCPE_OK;
}

SLiRP

First, I extracted, configure and built Qemu 0.9.0.. Next I copied the slirp directory into SIMH along with the various config headerfiles. I built this on an i386, so I took the headers from i386-softmmu ... I'm not sure if it matters for Big Endian machines.

Build the slirp directory with gcc and DO NOT USE ANY OPTIMIZATIONS... I think it's just a bunch of unallocated variables, but it will either not work correctly, or crash. You can build the rest of SIMH with -O2.

Place this code in the 'non implemented' section of sim_ether.c and build/link and it'll be good. Just attach the ethernet to anything and it'll work.

By default this listens on port 42323 on the host, and will send that TCP connection to port 23 to the SIMH VM, as long as that VM is configured as follows:

IP 10.0.2.15
MASK 255.255.255.0
GATEWAY 10.0.2.2

PRIMARY DNS 10.0.2.3

The 'cool' thing is that this configuration will work ANYWHERE, as SLiRP can be thought of as user mode NAT...


#include <slirp.h>
#include <ctype.h>
#include "sim_ether.h"
#include "sim_sock.h"

unsigned char tbuff[2000];
int tbufflen;
int slirp_inited=0;
extern fd_set *global_readfds, *global_writefds, *global_xfds;

t_stat eth_open(ETH_DEV* dev, char* name, DEVICE* dptr, uint32 dbit)
 {
       struct in_addr guest_addr;
       slirp_init();
       tbufflen=0;
       slirp_inited=1;
      memset(&guest_addr,0,sizeof(guest_addr));
       inet_aton("10.0.2.15",&guest_addr);
//printf("%d",guest_addr.s_addr);
       slirp_redir(0,42323,guest_addr,23);
       return SCPE_OK;
       }

t_stat eth_close (ETH_DEV* dev)
 {//slirp_exit(1);
       return SCPE_OK;}

t_stat eth_write (ETH_DEV* dev, ETH_PACK* packet, ETH_PCALLBACK routine)
 {
       slirp_input(packet->msg,packet->len);
       if(routine)
       (routine)(0);
       return SCPE_OK;
 }



t_stat eth_read (ETH_DEV* dev, ETH_PACK* packet, ETH_PCALLBACK routine)
 {
       int ret,nfds;
       fd_set rfds, wfds, xfds;
       struct timeval tv;

if(!dev) return SCPE_UNATT;
if(!packet) return SCPE_ARG;

dev->read_packet = packet;
packet->len = 0;
dev->read_callback = routine;

if(tbufflen>0)
       {
       dev->read_packet->len=tbufflen;
       memcpy(dev->read_packet->msg,tbuff,tbufflen);
       eth_add_crc32(dev->read_packet);
       if(dev->read_callback)
        (dev->read_callback)(0);
       tbufflen=0;
       }


nfds=-1;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&xfds);
if(slirp_inited)
       {
       slirp_select_fill(&nfds,&rfds,&wfds,&xfds);
       tv.tv_sec=0;
       tv.tv_usec=0;
       ret = select(nfds + 1, &rfds, &wfds, &xfds, &tv);
       if(ret>0)
               {}
       if(ret<0) {
           FD_ZERO(&rfds);
           FD_ZERO(&wfds);
           FD_ZERO(&xfds);
           }
       slirp_select_poll(&rfds, &wfds, &xfds);
       }


return SCPE_OK;
}


t_stat eth_filter (ETH_DEV* dev, int addr_count, ETH_MAC* addresses,
                  ETH_BOOL all_multicast, ETH_BOOL promiscuous)
 {return SCPE_NOFNC;}

int eth_devices (int max, ETH_LIST* dev)
 {return -1;}

void slirp_output (const unsigned char *pkt, int pkt_len)
{
tbufflen=pkt_len;
memcpy(tbuff,pkt,pkt_len);
}

int slirp_can_output(void)
{
return 1;
}

Writing your own

According to my old notes, the following code in the sim_ether.c file, replacing the either NON WORKING, or LIB_PCAP sections will produce an exe that will write packets to a directory, and constantly scan another directory for files to 'input' as packets.


#include <fcntl.h>               /*constants defined*/
#include <sys\types.h>
#include <sys\stat.h>            /*constants defined*/
#include <share.h>               /*constants defined*/ 
#include <io.h>

 
//Packet couters for reading/writing
//int Packet;
int PacketR;	//Internal counter to what we have read last.
char Path[255];
char Readpath[255];

/************Helper functions...***************************/

//sopenreadcounter  Opens the readcounter file
//return 1 with the file handle, 0 if the file doesnt exist
//-1 if there was some other error related to the file
int sopenreadcounter(char *path,int *filehndl)
{
//char *filename;
#ifdef DEBUG
printf("sopenreadcounter opening %s\n",path);
#endif
*filehndl=sopen(path,O_RDWR | O_BINARY, SH_DENYRW, S_IREAD | S_IWRITE);

//Check to see if it barfed
if (*filehndl==-1)
	{
#ifdef DEBUG
	printf("sopenreadcounter sopen returned -1 errno %d\n\n",errno);
#endif
	//file doesnt exist!
	if (errno==ENOENT)
		{
#ifdef DEBUG
			printf("sopenreadercount file doesnt exist(ENOENT)!\n");
#endif
		return 0;
		}
	//file is there, but we missed the lock.
	if (errno==EACCES)
		{
			int j=0;
		while(*filehndl==-1)
			{
			*filehndl=sopen(path,O_RDWR|O_BINARY, SH_DENYRW, S_IREAD | S_IWRITE);
#ifdef DEBUG
			printf("\rEACCES on %s fh %d errno %d\t\t%d",path,*filehndl,errno,j);
			//printf("\rEACCES fh %d errno %d\t\t%d",*filehndl,errno,j);
#endif
			Sleep(5);  //jiffy wait..
			j++;
			}
		return 1; //we got the filehandle
		}
	//Some other error.. we're fucked for now let's panic!
	printf("\ndrop out with error sopenreadcounter\n");
	return -1;
	}
return 1;  //it worked on first shot!
}
/***********************************************************/



t_stat eth_open (ETH_DEV* dev, char* name)
  {
	FILE *packetcount;
	char packetcountpath[255];
	PacketR=0;

   memset(Path,0x0,sizeof(Path));
   sprintf(Path,"%s",name);
   sprintf(Readpath,"%s\\%02X%02X%02X%02X%02X%02X",Path,dev->mac_address[0],dev->mac_address[1],dev->mac_address[2],dev->mac_address[3],dev->mac_address[4],dev->mac_address[5]);
   printf("\nPath:\t%s\nReadpath:\t%s\n",Path,Readpath);
   mkdir(Readpath);

   //Since we are new, we are going to reset the counter to 0.  We
   //wouldnt want the old packets anyways.  yes I knkow this will
   //liter the filesystem... clear those temp dirs!
   sprintf(packetcountpath,"%s\\packetcount",Readpath);
   packetcount=fopen(packetcountpath,"wb");
   fprintf(packetcount,"0");
   fclose(packetcount);
   return SCPE_OK;
  }
 
t_stat eth_close (ETH_DEV* dev)
  {
        //rmdir(Readpath);
 return SCPE_NOFNC;
 }
 
 
/*************************************************************/
 
t_stat eth_write (ETH_DEV* dev, ETH_PACK* packet, ETH_PCALLBACK routine)
      {
      int status = 1;   /* default to failure */
      
  /* make sure device exists */
  if (!dev) return SCPE_UNATT;
 
  /* make sure packet exists */
  if (!packet) return SCPE_ARG;
 
  /* make sure packet is acceptable length */
  if ((packet->len >= ETH_MIN_PACKET) && (packet->len <= ETH_MAX_PACKET)) {
      char buffer[5000];
      char fname[255];
	  int	err=0;
      int   outz=0;
      int	FHpacketcount=0;	//filehandle for the packet file
      char  packetcountpath[255];
	  int	Packet=0; //the packet #
      
 
      //verify path exists first.. by making it.... 
      memset(packetcountpath,0x0,sizeof(packetcountpath));
      sprintf(packetcountpath,"%s\\%02X%02X%02X%02X%02X%02X",Path,packet->msg[0],packet->msg[1],packet->msg[2],packet->msg[3],packet->msg[4],packet->msg[5]);
      mkdir(packetcountpath);

	  sprintf(packetcountpath,"%s\\%02X%02X%02X%02X%02X%02X\\packetcount",Path,packet->msg[0],packet->msg[1],packet->msg[2],packet->msg[3],packet->msg[4],packet->msg[5]);
	  err=sopenreadcounter(&packetcountpath,&FHpacketcount);
	  
	  if(err==0) //The file didn't exist!  For now we'll just assume this works...
	  {
		FHpacketcount=sopen(packetcountpath,O_RDWR | O_BINARY | O_CREAT, SH_DENYRW, S_IREAD | S_IWRITE);
		Packet=0;
	  }
	  if(err==-1)//something bombed!
	  {
#ifdef DEBUG
		  printf("something bombed openening the packet counter");
#endif
		  exit(-1);
	  }
	  if(err==1)
	  {
		  char buffer[20];
		  memset(buffer,0x0,sizeof(buffer));
		  read(FHpacketcount,buffer,sizeof(buffer));
		  Packet=atoi(buffer);
		  lseek(FHpacketcount,0,0);
	  }
      
   memset(buffer,0x0,sizeof(buffer));
   memset(fname,0x0,sizeof(fname));

   sprintf(fname,"%s\\%02X%02X%02X%02X%02X%02X\\packet_%d.cap",Path,packet->msg[0],packet->msg[1],packet->msg[2],packet->msg[3],packet->msg[4],packet->msg[5],Packet);

   //printf("writing frame %s\n",fname);

   outz=sopen(fname,O_RDWR | O_BINARY | O_CREAT, SH_DENYRW, S_IREAD | S_IWRITE);
   eth_add_crc32(packet->msg);
   write(outz,packet->msg,packet->len);
   close(outz);
   status=0;
   Packet++;
   {
	   char buffer[20];
	   memset(buffer,0x0,sizeof(buffer));
	   sprintf(buffer,"%d",Packet);
#ifdef DEBUG
	   printf("Writing packetcount[%d] %s\n",Packet,packetcountpath);
#endif
	   write(FHpacketcount,buffer,sizeof(buffer));
	   close(FHpacketcount);
   }
    /* detect sending of decnet loopback packet */
    if ((status == 0) && DECNET_SELF_FRAME(dev->decnet_addr, packet->msg)) 
      dev->decnet_self_sent += dev->reflections;
 
  } /* if packet->len */
 
  /* call optional write callback function */
  if (routine)
    (routine)(status);
  return SCPE_OK;
}
 
/*************************************************************/
t_stat eth_read (ETH_DEV* dev, ETH_PACK* packet, ETH_PCALLBACK routine)
  {
  int status;
 
  /* make sure device exists */
  if (!dev) return SCPE_UNATT;
 
  /* make sure packet exists */
  if (!packet) return SCPE_ARG;
 
  /* set read packet */
  dev->read_packet = packet;
  packet->len = 0;
 
  /* set optional callback routine */
  dev->read_callback = routine;
 
  /* dispatch read request to either receive a filtered packet or timeout */
  do 
  {
	  char fname[255];
	  FILE *inz;
      FILE *packetcount;
      int j=0;

	  sprintf(fname,"%s\\packet_%d.cap",Readpath,PacketR); 
#ifdef DEBUG
      //printf("eth_read from %s\n",fname);
#endif

  inz=sopen(fname,O_RDWR | O_BINARY, SH_DENYRW);
 
 if(inz==-1)
 {
  return SCPE_OK;
 }
 else
 {
  unsigned char packetbuffer[2000];
  memset(packetbuffer,0x0,sizeof(packetbuffer));
  j=read(inz,packetbuffer,sizeof(packetbuffer));
  close(inz); 
  unlink(fname);
#ifdef DEBUG
  printf("read frame %s\n",fname);
#endif
  
  dev->read_packet->len=j;
  memcpy(dev->read_packet->msg,packetbuffer,j);
  eth_add_crc32(dev->read_packet);
  if (dev->read_callback)
   (dev->read_callback)(0);
  PacketR++;
 }
  
  return SCPE_OK;
 } while ((status) && (0 == packet->len));
}
 
 
 
t_stat eth_filter (ETH_DEV* dev, int addr_count, ETH_MAC* addresses,
                   ETH_BOOL all_multicast, ETH_BOOL promiscuous)
  {return SCPE_NOFNC;}
int eth_devices (int max, ETH_LIST* dev)
  {return 0;}

#endif
////////////////////////////////////////////////////////////////////////