Difference between revisions of "Networking with SIMH"
m (Fix some minor typos and spelling.) |
(Three types of adapters and some links added) |
||
Line 1: | Line 1: | ||
− | The aim of this quick tutorial is to show how to setup [[SIMH]]'s networking. | + | The aim of this quick tutorial is to show how to setup [[SIMH]]'s networking with [[VAX]]en and [[PDP-11]]s. |
Line 5: | Line 5: | ||
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. | 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 [[VAXstation]]s) | ||
=== Showing host ethernet adapters === | === Showing host ethernet adapters === |
Revision as of 08:40, 9 July 2022
The aim of this quick tutorial is to show how to setup SIMH's networking with VAXen and PDP-11s.
Contents
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 ////////////////////////////////////////////////////////////////////////