├── httpd.com ├── net2.h ├── httpd.h ├── httpd2.c ├── httpd4.c ├── README.md ├── httpd.c ├── net3.c ├── httpd3.c ├── net.h ├── net.c └── net2.c /httpd.com: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jes/cpmhttpd/HEAD/httpd.com -------------------------------------------------------------------------------- /net2.h: -------------------------------------------------------------------------------- 1 | /* Networking code for RC2014 2 | * 3 | * James Stanley 2019 4 | */ 5 | 6 | #define SLIP_MTU 296 7 | #define SLIP_MAX 600 8 | 9 | typedef unsigned char uint8_t; 10 | typedef unsigned int uint16_t; 11 | typedef unsigned long uint32_t; 12 | 13 | typedef struct socket_s socket_t; 14 | 15 | typedef void (*CloseFunc)(socket_t *); 16 | typedef void (*OpenFunc)(socket_t *); 17 | typedef void (*RecvFunc)(socket_t *, unsigned char *, int); 18 | typedef void (*SendFunc)(socket_t *, int); 19 | 20 | struct socket_s { 21 | OpenFunc open; 22 | RecvFunc recv; 23 | SendFunc send; 24 | CloseFunc close; 25 | uint8_t state; 26 | uint8_t writable; 27 | unsigned char remoteaddr[4]; 28 | uint16_t localport; 29 | uint16_t remoteport; 30 | uint32_t localseq; 31 | uint32_t remoteseq; 32 | uint32_t remoteack; 33 | int lru; 34 | int sendlen; 35 | unsigned char sendbuf[SLIP_MTU+4]; 36 | }; 37 | 38 | extern unsigned char local_ipaddr[4]; 39 | 40 | void net_tick(); 41 | void net_init(); 42 | char *str2ip(unsigned char *ip); 43 | int listen(uint16_t port, OpenFunc open); 44 | void unlisten(uint16_t port); 45 | int connect(unsigned char *remote, uint16_t port, OpenFunc open); 46 | int send(socket_t *s, unsigned char *buf, int len); 47 |  -------------------------------------------------------------------------------- /httpd.h: -------------------------------------------------------------------------------- 1 | /* Basic httpd for CP/M 2 | * 3 | * jes 2019 4 | */ 5 | 6 | #define READREQ 0 7 | #define WRHEADER 1 8 | #define WRFILE 2 9 | 10 | typedef struct { 11 | char *filename; 12 | char *mode; 13 | int seek; 14 | } jesfile_t; 15 | 16 | typedef struct { 17 | int state; 18 | int recvlen; 19 | jesfile_t *fp; 20 | char recvbuf[SLIP_MTU+1]; 21 | char *tosend; 22 | char *reqfile; 23 | int is_head; 24 | socket_t *s; 25 | } client_t; 26 | 27 | #define MAX_CLIENT 32 28 | extern client_t client[MAX_CLIENT]; 29 | 30 | extern unsigned char filebuf[256]; 31 | 32 | extern char *texttypes[]; 33 | extern char *mimetypes[]; 34 | 35 | int jestolower(int c); 36 | int strcasecmp(char *a, char *b); 37 | char *filemode(char *ext); 38 | char *contenttype(char *ext); 39 | void jesmemset(char *p, int c, int len); 40 | int filesize(FILE *fp, char *mode); 41 | 42 | void parse_request(client_t *c); 43 | void handle_request(client_t *c); 44 | 45 | void logrequest(client_t *c, int status); 46 | client_t *lookup_client(socket_t *s); 47 | void internal_response(client_t *c, int code, char *status); 48 | jesfile_t *jesfopen(char *filename, char *mode); 49 | void jesfclose(jesfile_t *fp); 50 | size_t jesfread(void *buf, size_t size, size_t nmemb, jesfile_t *fp); -------------------------------------------------------------------------------- /httpd2.c: -------------------------------------------------------------------------------- 1 | /* Basic httpd for CP/M 2 | * 3 | * jes 2019 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include "d:net2.h" 10 | #include "d:httpd.h" 11 | 12 | char *texttypes[] = {"htm", "txt", 0}; 13 | char *mimetypes[] = {"htm", "text/html", "txt", "text/plain", 14 | "png", "image/png", 0}; 15 | 16 | /* hitech c tolower is just a macro for (c + 'a' - 'A'); 17 | * it doesn't check that c is a letter 18 | */ 19 | int jestolower(int c) { 20 | if (c >= 'A' && c <= 'Z') 21 | return c + 'a' - 'A'; 22 | return c; 23 | } 24 | 25 | /* this doesn't exist in the hitech c lib */ 26 | int strcasecmp(char *a, char *b) { 27 | while (*a && *b && jestolower(*a) == jestolower(*b)) { 28 | a++; b++; 29 | } 30 | return *a - *b; 31 | } 32 | 33 | char *filemode(char *ext) { 34 | int i; 35 | for (i = 0; texttypes[i]; i++) { 36 | if (strcasecmp(ext, texttypes[i]) == 0) 37 | return "r"; 38 | } 39 | return "rb"; 40 | } 41 | 42 | char *contenttype(char *ext) { 43 | int i; 44 | for (i = 0; mimetypes[i]; i += 2) { 45 | if (strcasecmp(ext, mimetypes[i]) == 0) 46 | return mimetypes[i+1]; 47 | } 48 | return "application/octet-stream"; 49 | } 50 | 51 | /* despite a correct prototype in stdlib.h, the hitech c lib has 52 | * the character and length arguments reversed in the memset() 53 | * implementation 54 | */ 55 | void jesmemset(char *p, int c, int len) { 56 | while (len--) 57 | *(p++) = c; 58 | } 59 | 60 | /* you can't simply fseek() and ftell() on text files in CP/M to 61 | * find their length because \r\n becomes only 1 byte 62 | */ 63 | int filesize(FILE *fp, char *mode) { 64 | int l = 0; 65 | int ch; 66 | 67 | rewind(fp); 68 | 69 | if (mode[1] == 'b') { 70 | fseek(fp, 0, 2); /* SEEK_END */ 71 | l = ftell(fp); 72 | } else { 73 | while ((ch = fgetc(fp)) != EOF) 74 | l++; 75 | } 76 | 77 | rewind(fp); 78 | 79 | return l; 80 | } -------------------------------------------------------------------------------- /httpd4.c: -------------------------------------------------------------------------------- 1 | /* Basic httpd for CP/M 2 | * 3 | * jes 2019 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "d:net2.h" 11 | #include "d:httpd.h" 12 | 13 | void logrequest(client_t *c, int status) { 14 | int i; 15 | printf("%-15s ", str2ip(c->s->remoteaddr)); 16 | printf("%s ", c->is_head ? "HEAD" : " GET"); 17 | printf("%s ", c->reqfile); 18 | for (i = c->reqfile ? strlen(c->reqfile) : 6; i < 13; i++) 19 | putchar(' '); 20 | printf("%d\n", status); 21 | } 22 | 23 | client_t *lookup_client(socket_t *s) { 24 | int i; 25 | 26 | for (i = 0; i < MAX_CLIENT; i++) { 27 | if (client[i].s == s) 28 | return client+i; 29 | } 30 | return NULL; 31 | } 32 | 33 | void internal_response(client_t *c, int code, char *status) { 34 | char *p; 35 | 36 | logrequest(c, code); 37 | 38 | c->state = WRHEADER; 39 | 40 | c->tosend = malloc(256); 41 | p = c->tosend; 42 | p += sprintf(p, "HTTP/1.0 %d %s\r\n", code, status); 43 | p += sprintf(p, "Content-Type: text/html\r\n"); 44 | p += sprintf(p, "Content-Length: %d\r\n", 4 + strlen(status) + 1); 45 | p += sprintf(p, "\r\n"); 46 | p += sprintf(p, "%d %s\n", code, status); 47 | } 48 | 49 | jesfile_t *jesfopen(char *filename, char *mode) { 50 | jesfile_t *fp; 51 | FILE *test; 52 | 53 | test = fopen(filename, mode); 54 | if (test) 55 | fclose(test); 56 | else 57 | return NULL; 58 | 59 | fp = malloc(sizeof(jesfile_t)); 60 | fp->filename = malloc(strlen(filename)+1); 61 | strcpy(fp->filename, filename); 62 | fp->mode = mode; 63 | fp->seek = 0; 64 | 65 | return fp; 66 | } 67 | 68 | void jesfclose(jesfile_t *fp) { 69 | free(fp->filename); 70 | free(fp); 71 | } 72 | 73 | size_t jesfread(void *buf, size_t size, size_t nmemb, jesfile_t *fp) { 74 | FILE *f; 75 | size_t r; 76 | 77 | f = fopen(fp->filename, fp->mode); 78 | if (!f) 79 | return 0; 80 | 81 | fseek(f, fp->seek, 0); 82 | r = fread(buf, size, nmemb, f); 83 | fp->seek = ftell(f); 84 | fclose(f); 85 | 86 | return r; 87 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cpmhttpd 2 | 3 | This is a basic networking stack and web server for CP/M. 4 | 5 | It is occasionally available running on my RC2014 at http://moonship.jes.xxx:8118/ 6 | 7 | It is not very good. It basically does the absolute minimum necessary to trick people into 8 | thinking that it is a working web server. 9 | 10 | It speaks to the rest of the world via a SLIP connection. 11 | 12 | Its IP address is hardcoded to 192.168.1.51. 13 | 14 | It has no facility to retransmit dropped packets. 15 | 16 | It supports a maximum of 32 simultaneous clients. 17 | 18 | Incoming packet validation is... missing. It is likely that this program has an arbitrary remote code execution feature. 19 | Use it at your own risk etc. 20 | 21 | I wrote it all in CP/M using ZDE. It is meant to be compiled with the Hi-Tech C compiler, which crashes 22 | if your source files are too large, which is why both the "net" and "httpd" parts are split 23 | into many helpfully-named files. I doubt it will compile correctly with any other compiler 24 | because some of the "net" code uses Hi-Tech's memset() function which has the arguments the wrong 25 | way round. 26 | 27 | ## Compiling 28 | 29 | If you don't want to compile it, you can use the included `httpd.com` executable. Just copy it to your CP/M machine 30 | and proceed to the "Usage" section. 31 | 32 | If you do want to compile it, first get the source files onto your CP/M system any way you know how. If you don't put them in drive D, modify all the source 33 | files to change the `#include` lines to reference the correct drive letter. 34 | 35 | Then, install the Hi-Tech C compiler as per https://techtinkering.com/2008/10/22/installing-the-hi-tech-z80-c-compiler-for-cpm/ 36 | 37 | Then navigate to the disk that the C compiler is in, and, for example: 38 | 39 | ``` 40 | C>C -v D:HTTPD.C D:HTTPD2.C D:HTTPD3.C D:HTTP4.C D:NET.C D:NET2.C D:NET3.C 41 | ``` 42 | 43 | Then wait approximately 7 minutes while it compiles. 44 | 45 | ## Usage 46 | 47 | You'll need to plug the "reader/punch" (serial port B on the RC2014) into a more-capable machine that supports SLIP networking. 48 | I use a Linux machine. 49 | 50 | On the Linux machine, in one terminal: 51 | 52 | ``` 53 | $ sudo slattach -p slip -s 115200 /dev/ttyUSB0 54 | ``` 55 | 56 | `slattach` stays running. In another terminal: 57 | 58 | ``` 59 | $ sudo ifconfig sl0 192.168.1.50 pointopoint 192.168.1.51 up 60 | ``` 61 | 62 | This tells the Linux machine that its IP address on the SLIP link is 192.168.1.50, and the other end is 192.168.1.51. 63 | 64 | Then, on the CP/M machine, navigate to the disk that your web content is in, and, for example: 65 | 66 | ``` 67 | H>C:HTTPD 68 | ``` 69 | 70 | Then, on the Linux machine, navigate your web browser to http://192.168.1.51/ and wait while it thinks about loading. 71 | 72 | Once you've verified that it works, you're on your own to sort out your routing configuration to make the CP/M 73 | web server accessible from the wider Internet. 74 | 75 | Please email me if you want: james@incoherency.co.uk 76 | -------------------------------------------------------------------------------- /httpd.c: -------------------------------------------------------------------------------- 1 | /* Basic httpd for CP/M 2 | * 3 | * jes 2019 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include "d:net2.h" 10 | #include "d:httpd.h" 11 | 12 | client_t client[MAX_CLIENT]; 13 | 14 | unsigned char filebuf[256]; 15 | 16 | void accept_client(socket_t *s) { 17 | int i; 18 | 19 | for (i = 0; i < MAX_CLIENT; i++) { 20 | if (!client[i].s) { 21 | jesmemset(client+i, 0, sizeof(client_t)); 22 | client[i].state = READREQ; 23 | client[i].s = s; 24 | return; 25 | } 26 | } 27 | 28 | fprintf(stderr, "*** can't accept a new client\n"); 29 | /* TODO: disconnect the new client, or the last-used client 30 | from the table */ 31 | } 32 | 33 | void httprecv(socket_t *s, unsigned char *buf, int len) { 34 | client_t *c = lookup_client(s); 35 | 36 | if (!c || c->state != READREQ) 37 | return; 38 | 39 | if (c->recvlen + len >= SLIP_MTU) { 40 | internal_response(c, 400, "Bad Request"); 41 | return; 42 | } 43 | memcpy(c->recvbuf+c->recvlen, buf, len); 44 | c->recvlen += len; 45 | 46 | parse_request(c); 47 | } 48 | 49 | void httpsend(socket_t *s, int len) { 50 | client_t *c = lookup_client(s); 51 | 52 | if (!c) 53 | return; 54 | 55 | if (c->state == WRHEADER) { 56 | if (len < strlen(c->tosend)) { 57 | fprintf(stderr, "*** want to send %d bytes but can only send %d\n", strlen(c->tosend), len); 58 | /* TODO: just send what we can and reduce tosend */ 59 | return; 60 | } 61 | 62 | send(s, (unsigned char *)c->tosend, strlen(c->tosend)); 63 | free(c->tosend); 64 | c->tosend = NULL; 65 | if (c->fp) { 66 | c->state = WRFILE; 67 | } else { 68 | /* TODO: close socket */ 69 | c->s = NULL; 70 | } 71 | } else if (c->state == WRFILE) { 72 | if (len > 256) 73 | len = 256; 74 | len = jesfread(filebuf, 1, len, c->fp); 75 | if (len > 0) { 76 | send(s, filebuf, len); 77 | } else { 78 | /* TODO: close socket */ 79 | c->s = NULL; 80 | jesfclose(c->fp); 81 | } 82 | } 83 | } 84 | 85 | void httpclose(socket_t *s) { 86 | client_t *c = lookup_client(s); 87 | if (c) { 88 | c->s = NULL; 89 | if (c->tosend) 90 | free(c->tosend); 91 | c->tosend = NULL; 92 | if (c->fp) 93 | jesfclose(c->fp); 94 | c->fp = NULL; 95 | } 96 | } 97 | 98 | void httpopen(socket_t *s) { 99 | accept_client(s); 100 | 101 | s->recv = httprecv; 102 | s->send = httpsend; 103 | s->close = httpclose; 104 | } 105 | 106 | void main() { 107 | net_init(); 108 | 109 | listen(80, httpopen); 110 | 111 | printf("httpd listening on %s:80\n", str2ip(local_ipaddr)); 112 | 113 | while (1) 114 | net_tick(); 115 | } -------------------------------------------------------------------------------- /net3.c: -------------------------------------------------------------------------------- 1 | /* Networking code for RC2014: more higher-level parts 2 | * 3 | * James Stanley 2019 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "d:net.h" 11 | 12 | void net_init() { 13 | _slip_buf = malloc(SLIP_MAX); 14 | } 15 | 16 | char *str2ip(unsigned char *remoteaddr) { 17 | static char buf[16]; 18 | char *p = buf; 19 | int i; 20 | for (i = 0; i < 4; i++) { 21 | p += sprintf(p, "%d", remoteaddr[i]); 22 | if (i != 3) 23 | *(p++) = '.'; 24 | } 25 | return buf; 26 | } 27 | 28 | /* return -1 on error, 0 on success */ 29 | int listen(uint16_t port, OpenFunc open) { 30 | int i; 31 | 32 | for (i = 0; i < MAX_LISTEN; i++) { 33 | if (listentable[i].port == 0) { 34 | listentable[i].port = port; 35 | listentable[i].open = open; 36 | return 0; 37 | } 38 | } 39 | 40 | return -1; 41 | } 42 | 43 | void unlisten(uint16_t port) { 44 | int i; 45 | 46 | for (i = 0; i < MAX_LISTEN; i++) { 47 | if (listentable[i].port == port) 48 | listentable[i].port = 0; 49 | } 50 | } 51 | 52 | uint16_t allocate_port() { 53 | uint16_t port; 54 | int i; 55 | int ok = 0; 56 | 57 | while (!ok) { 58 | port = rand(); 59 | ok = 1; 60 | for (i = 0; ok && i < MAX_SOCKET; i++) { 61 | if (socktable[i].state != CLOSED && socktable[i].localport == port) 62 | ok = 0; 63 | } 64 | for (i = 0; ok && i < MAX_LISTEN; i++) { 65 | if (listentable[i].port == port) 66 | ok = 0; 67 | } 68 | } 69 | 70 | return port; 71 | } 72 | 73 | int connect(unsigned char *remote, uint16_t port, OpenFunc open) { 74 | socket_t *s; 75 | uint16_t localport; 76 | 77 | localport = allocate_port(); 78 | s = newconn_socket(localport, port, remote); 79 | 80 | if (!s) 81 | return -1; 82 | 83 | s->localseq = ((uint32_t)rand())<<16 + rand(); 84 | /* TODO: send the SYN packet */ 85 | s->localseq++; 86 | s->state = SYN_SENT; 87 | 88 | return 0; 89 | } 90 | 91 | int send(socket_t *s, unsigned char *buf, int len) { 92 | unsigned char *buf2 = s->sendbuf; 93 | 94 | if (40+len > SLIP_MTU) 95 | return -1; 96 | 97 | if (s->state != ESTABLISHED) 98 | return -1; 99 | 100 | /* IP header */ 101 | init_ip_header(buf2); 102 | iph_set_len(buf2, 40+len); 103 | iph_set_dst(buf2, s->remoteaddr); 104 | set_iph_crc(buf2); 105 | 106 | /* TCP header */ 107 | init_tcp_header(buf2+20); 108 | tcph_set_sport(buf2+20, s->localport); 109 | tcph_set_dport(buf2+20, s->remoteport); 110 | tcph_set_seq(buf2+20, s->localseq); 111 | tcph_set_ack(buf2+20, s->remoteseq); 112 | tcph_set_flags(buf2+20, TCP_PSH|TCP_ACK); 113 | memcpy(buf2+40, buf, len); 114 | buf2[40+len] = 0;/* ensure crc is right for odd len */ 115 | set_tcph_crc(buf2, 20+len); 116 | 117 | write_slip_frame(buf2, 40+len); 118 | 119 | s->localseq += len; 120 | s->sendlen = 40+len; 121 | } 122 |  -------------------------------------------------------------------------------- /httpd3.c: -------------------------------------------------------------------------------- 1 | /* Basic httpd for CP/M 2 | * 3 | * jes 2019 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "d:net2.h" 11 | #include "d:httpd.h" 12 | 13 | void parse_request(client_t *c) { 14 | char *reqmethod; 15 | int i; 16 | int got_full_line = 0; 17 | char *p; 18 | 19 | /* check if we've got the first line of the request */ 20 | for (i = 1; i < c->recvlen; i++) { 21 | if (c->recvbuf[i-1] == '\n') { 22 | got_full_line = 1; 23 | break; 24 | } 25 | } 26 | if (!got_full_line) 27 | return; 28 | 29 | /* now parse out the request method and filename */ 30 | reqmethod = c->recvbuf; 31 | if (strncmp(reqmethod, "GET ", 4) == 0) { 32 | c->is_head = 0; 33 | c->reqfile = reqmethod+4; 34 | } else if (strncmp(reqmethod, "HEAD ", 5) == 0) { 35 | c->is_head = 1; 36 | c->reqfile = reqmethod+5; 37 | } else { 38 | internal_response(c, 400, "Bad Request"); 39 | return; 40 | } 41 | 42 | /* check that filename begins with "/" and step past it */ 43 | if (*(c->reqfile) != '/') { 44 | internal_response(c, 400, "Bad Request"); 45 | return; 46 | } 47 | 48 | /* overwrite the space character with a NUL byte */ 49 | for (p = c->reqfile; *p != '\n'; p++) { 50 | if (*p == ':') { 51 | /* don't allow colons in filename */ 52 | internal_response(c, 403, "Forbidden"); 53 | return; 54 | } 55 | if (*p == ' ') { 56 | *p = 0; 57 | break; 58 | } 59 | } 60 | 61 | handle_request(c); 62 | } 63 | 64 | void handle_request(client_t *c) { 65 | char *realfile; 66 | char *fileext; 67 | char *mode; 68 | char *type; 69 | char *p; 70 | int contentlength; 71 | FILE *fp; 72 | 73 | /* skip "/" */ 74 | realfile = c->reqfile+1; 75 | 76 | /* index page is INDEX.HTM */ 77 | if (*realfile == 0) 78 | realfile = "INDEX.HTM"; 79 | 80 | /* work out the content-type based on the extension */ 81 | for (fileext = realfile; *fileext && *fileext != '.'; fileext++); 82 | if (*fileext == '.') 83 | fileext++; 84 | mode = filemode(fileext); 85 | type = contenttype(fileext); 86 | 87 | /* see if the file exists */ 88 | fp = fopen(realfile, mode); 89 | if (!fp) { 90 | internal_response(c, 404, "Not Found"); 91 | return; 92 | } 93 | 94 | /* find out the length of the file */ 95 | contentlength = filesize(fp, mode); 96 | 97 | logrequest(c, 200); 98 | 99 | /* hitech c lib only supports 8 simultaneous open FILE*'s 100 | * (of which 3 are stdin/out/err) 101 | * so we need to close the real fp and use a jesfile_t* instead 102 | */ 103 | fclose(fp); 104 | if (!c->is_head) 105 | c->fp = jesfopen(realfile, mode); 106 | 107 | c->state = WRHEADER; 108 | 109 | /* TODO: allocate the right amount, instead of guessing */ 110 | c->tosend = malloc(256); 111 | p = c->tosend; 112 | p += sprintf(p, "HTTP/1.0 200 OK\r\n"); 113 | p += sprintf(p, "Content-Type: %s\r\n", type); 114 | p += sprintf(p, "Content-Length: %d\r\n", contentlength); 115 | p += sprintf(p, "\r\n"); 116 | } 117 |  -------------------------------------------------------------------------------- /net.h: -------------------------------------------------------------------------------- 1 | 2 | /* Networking code for RC2014 3 | * 4 | * James Stanley 2019 5 | */ 6 | 7 | #define SLIP_MTU 296 8 | #define SLIP_MAX 600 9 | 10 | typedef unsigned char uint8_t; 11 | typedef unsigned int uint16_t; 12 | typedef unsigned long uint32_t; 13 | 14 | /* IP header accessors */ 15 | #define iph_ver(b) (((b)[0] & 0xf0) >> 4) 16 | #define iph_ihl(b) ((b)[0] & 0x0f) 17 | #define iph_tos(b) fu8(b,1) 18 | #define iph_len(b) fu16(b,2) 19 | #define iph_id(b) fu16(b,4) 20 | #define iph_flags(b) (((b)[6] & 0xe0) >> 5) 21 | #define iph_fragoff(b) (((int)(((b)[6] & 0x1f))*256) + (int)((b)[7])) 22 | #define iph_ttl(b) fu8(b,8) 23 | #define iph_protocol(b) fu8(b,9) 24 | #define iph_crc(b) fu16(b,10) 25 | #define iph_src(b) ((unsigned char *)((b)+12)) 26 | #define iph_dst(b) ((unsigned char *)((b)+16)) 27 | #define iph_set_len(b,n) su16(b,2,n) 28 | #define iph_set_src(b,a) memcpy((b)+12, (a), 4) 29 | #define iph_set_dst(b,a) memcpy((b)+16, (a), 4) 30 | 31 | #define PROT_ICMP 1 32 | #define PROT_TCP 6 33 | #define PROT_UDP 17 34 | 35 | /* ICMP header accessors */ 36 | #define icmp_type(b) fu8(b,0) 37 | #define icmp_code(b) fu8(b,1) 38 | #define icmp_crc(b) fu16(b,2) 39 | 40 | /* TCP flag bits */ 41 | #define TCP_URG 0x20 42 | #define TCP_ACK 0x10 43 | #define TCP_PSH 0x08 44 | #define TCP_RST 0x04 45 | #define TCP_SYN 0x02 46 | #define TCP_FIN 0x01 47 | 48 | /* TCP header accessors */ 49 | #define tcph_sport(b) fu16(b,0) 50 | #define tcph_dport(b) fu16(b,2) 51 | #define tcph_seq(b) fu32(b,4) 52 | #define tcph_ack(b) fu32(b,8) 53 | #define tcph_dataoff(b) (fu8(b,12)>>4) 54 | #define tcph_flags(b) (fu8(b,13)&0x3f) 55 | #define tcph_window(b) fu16(b,14) 56 | #define tcph_crc(b) fu16(b,16) 57 | #define tcph_urgent(b) fu16(b,18) 58 | /* TODO: options */ 59 | #define tcph_set_sport(b,n) su16(b,0,n) 60 | #define tcph_set_dport(b,n) su16(b,2,n) 61 | #define tcph_set_seq(b,n) su32(b,4,n) 62 | #define tcph_set_ack(b,n) su32(b,8,n) 63 | #define tcph_set_flags(b,n) su8(b,13,n&0x3f) 64 | #define tcph_set_crc(b,n) su16(b,16,n) 65 | 66 | /* TCP states */ 67 | #define CLOSED 0 68 | #define LISTEN 1 69 | #define SYN_RCVD 2 70 | #define SYN_SENT 3 71 | #define ESTABLISHED 4 72 | #define CLOSE_WAIT 5 73 | #define LAST_ACK 6 74 | #define FIN_WAIT_1 7 75 | #define FIN_WAIT_2 8 76 | #define CLOSING 9 77 | #define TIME_WAIT 10 78 | 79 | #define MAX_SOCKET 32 80 | #define MAX_LISTEN 16 81 | 82 | typedef struct socket_s socket_t; 83 | 84 | typedef void (*CloseFunc)(socket_t *); 85 | typedef void (*OpenFunc)(socket_t *); 86 | typedef void (*RecvFunc)(socket_t *, unsigned char *, int); 87 | typedef void (*SendFunc)(socket_t *, int); 88 | 89 | struct socket_s { 90 | OpenFunc open; 91 | RecvFunc recv; 92 | SendFunc send; 93 | CloseFunc close; 94 | uint8_t state; 95 | uint8_t writable; 96 | unsigned char remoteaddr[4]; 97 | uint16_t localport; 98 | uint16_t remoteport; 99 | uint32_t localseq; 100 | uint32_t remoteseq; 101 | uint32_t remoteack; 102 | int lru; 103 | int sendlen; 104 | unsigned char sendbuf[SLIP_MTU+4]; 105 | }; 106 | 107 | typedef struct { 108 | uint16_t port; 109 | OpenFunc open; 110 | } listen_t; 111 | 112 | extern unsigned char *_slip_buf; 113 | extern unsigned char local_ipaddr[4]; 114 | 115 | /* read a SLIP frame from the reader and return a pointer to 116 | * a static buffer 117 | */ 118 | unsigned char *read_slip_frame(); 119 | void write_slip_frame(unsigned char *buf, int len); 120 | 121 | uint8_t fu8(unsigned char *b, int off); 122 | uint16_t fu16(unsigned char *b, int off); 123 | uint32_t fu32(unsigned char *b, int off); 124 | void su8(unsigned char *b, int off, uint8_t val); 125 | void su16(unsigned char *b, int off, uint16_t val); 126 | void su32(unsigned char *b, int off, uint32_t val); 127 | 128 | extern unsigned char _blank_iph[20]; 129 | extern unsigned char _blank_tcph[20]; 130 | 131 | void init_ip_header(unsigned char *buf); 132 | void init_tcp_header(unsigned char *buf); 133 | int valid_ip_header(unsigned char *buf); 134 | int valid_tcp_header(unsigned char *buf, int len); 135 | int dst_is_us(unsigned char *buf); 136 | int src_is(unsigned char *buf, unsigned char *src); 137 | int is_icmp_echoreq(unsigned char *buf); 138 | int is_icmp_echoreply(unsigned char *buf); 139 | unsigned char *ip_payload(unsigned char *buf); 140 | unsigned char *icmp_echo_payload(unsigned char *buf); 141 | uint16_t crc(unsigned char *buf, int len, uint16_t start); 142 | void set_iph_crc(unsigned char *buf); 143 | void set_icmp_crc(unsigned char *buf, int len); 144 | void set_tcph_crc(unsigned char *buf, int len); 145 | 146 | extern socket_t socktable[MAX_SOCKET]; 147 | extern listen_t listentable[MAX_LISTEN]; 148 | 149 | void process_packet(unsigned char *buf, int len); 150 | void process_icmp(unsigned char *buf, int len); 151 | void process_tcp(unsigned char *buf, int len); 152 | void tcp_close(socket_t *s); 153 | void tcp_reply(unsigned char *buf, int len, int flags, socket_t *s); 154 | socket_t *lookup_socket(unsigned char *buf, int len); 155 | socket_t *newconn_socket(uint16_t localport, uint16_t remoteport, unsigned char *remoteaddr); 156 | void free_socket(socket_t *s); 157 |  -------------------------------------------------------------------------------- /net.c: -------------------------------------------------------------------------------- 1 | /* Networking code for RC2014 (lower-level parts) 2 | * 3 | * James Stanley 2019 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "d:net.h" 11 | 12 | unsigned char *_slip_buf; 13 | unsigned char local_ipaddr[4] = {192,168,1,51}; 14 | 15 | /* read a SLIP frame from the reader and return a pointer to 16 | * a static buffer 17 | */ 18 | unsigned char *read_slip_frame() { 19 | unsigned char *buf = _slip_buf; 20 | int bufp = 0; 21 | int c; 22 | 23 | memset(buf, SLIP_MAX, 0); 24 | 25 | while (1) { 26 | c = bdoshl(CPMRRDR); 27 | /* printf("%02x ", c); */ 28 | if (c == 0xc0) { 29 | if (bufp != 0) 30 | return buf; 31 | } else if (c == 0xdb) { /* escaped byte */ 32 | c = bdoshl(CPMRRDR); 33 | if (c == 0xdc) 34 | buf[bufp++] = 0xc0; 35 | else if (c == 0xdd) 36 | buf[bufp++] = 0xdb; 37 | } else { 38 | buf[bufp++] = c; 39 | } 40 | if (bufp == SLIP_MAX) { 41 | fprintf(stderr, "NET: Panic: >%d bytes", SLIP_MAX); 42 | exit(1); 43 | } 44 | } 45 | 46 | return buf; 47 | } 48 | 49 | void write_slip_frame(unsigned char *buf, int len) { 50 | int i = 0; 51 | 52 | /* printf("\n\n"); */ 53 | 54 | bdos(CPMWPUN, 0xc0); 55 | 56 | while (i < len) { 57 | /* printf("%02x ", buf[i]); */ 58 | if (buf[i] == 0xc0) { 59 | bdos(CPMWPUN, 0xdb); 60 | bdos(CPMWPUN, 0xdc); 61 | } else if (buf[i] == 0xdb) { 62 | bdos(CPMWPUN, 0xdb); 63 | bdos(CPMWPUN, 0xdd); 64 | } else { 65 | bdos(CPMWPUN, buf[i]); 66 | } 67 | i++; 68 | } 69 | 70 | /* printf("C0 "); */ 71 | bdos(CPMWPUN, 0xc0); 72 | } 73 | 74 | uint8_t fu8(unsigned char *b, int off) { 75 | return b[off]; 76 | } 77 | uint16_t fu16(unsigned char *b, int off) { 78 | return b[off]*256 + b[off+1]; 79 | } 80 | uint32_t fu32(unsigned char *b, int off) { 81 | return (uint32_t)fu16(b, off)*65536 + fu16(b,off+2); 82 | } 83 | void su8(unsigned char *b, int off, uint8_t val) { 84 | b[off] = val; 85 | } 86 | void su16(unsigned char *b, int off, uint16_t val) { 87 | b[off] = val >> 8; 88 | b[off+1] = val; 89 | } 90 | void su32(unsigned char *b, int off, uint32_t val) { 91 | b[off] = val >> 24; 92 | b[off+1] = val >> 16; 93 | b[off+2] = val >> 8; 94 | b[off+3] = val; 95 | } 96 | 97 | unsigned char _blank_iph[20] = {0x45, 0, 0, 0, 0, 0, 0x40, 0, 0x40, 98 | 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 99 | unsigned char _blank_tcph[20] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100 | 0, 0x50, 0, 1, 0, 0, 0, 0, 0}; 101 | 102 | void init_ip_header(unsigned char *buf) { 103 | memcpy(buf, _blank_iph, 20); 104 | memcpy(buf+12, local_ipaddr, 4); 105 | } 106 | 107 | void init_tcp_header(unsigned char *buf) { 108 | memcpy(buf, _blank_tcph, 20); 109 | } 110 | 111 | int valid_ip_header(unsigned char *buf) { 112 | if (iph_ver(buf) != 4) 113 | return 0; 114 | if (iph_ihl(buf) != 5) 115 | return 0; 116 | if (iph_len(buf) >= SLIP_MAX) 117 | return 0; 118 | /* TODO: check crc */ 119 | return 1; 120 | } 121 | 122 | int valid_tcp_header(unsigned char *buf, int len) { 123 | /* TODO: check crc */ 124 | return 1; 125 | } 126 | 127 | int dst_is_us(unsigned char *buf) { 128 | return memcmp(local_ipaddr, iph_dst(buf), 4) == 0; 129 | } 130 | 131 | int src_is(unsigned char *buf, unsigned char *src) { 132 | return memcmp(src, iph_src(buf), 4) == 0; 133 | } 134 | 135 | int is_icmp_echoreq(unsigned char *buf) { 136 | return icmp_type(buf) == 8 && icmp_code(buf) == 0; 137 | } 138 | 139 | int is_icmp_echoreply(unsigned char *buf) { 140 | return icmp_type(buf) == 0 && icmp_code(buf) == 0; 141 | } 142 | 143 | unsigned char *ip_payload(unsigned char *buf) { 144 | return buf+4*(iph_ihl(buf)); 145 | } 146 | 147 | unsigned char *icmp_echo_payload(unsigned char *buf) { 148 | return buf+8; 149 | } 150 | 151 | uint16_t crc(unsigned char *buf, int len, uint16_t start) { 152 | uint16_t crc = start; 153 | uint16_t old_crc = 0; 154 | int i = 0; 155 | 156 | len++; 157 | 158 | for (i = 0; i < len/2; i++) { 159 | crc += fu16(buf, 2*i); 160 | if (crc < old_crc) 161 | crc++; 162 | old_crc = crc; 163 | } 164 | 165 | return ~crc; 166 | } 167 | 168 | void set_iph_crc(unsigned char *buf) { 169 | su16(buf, 10, 0); 170 | su16(buf, 10, crc(buf, 20, 0)); 171 | } 172 | 173 | /* buf points to icmp header */ 174 | void set_icmp_crc(unsigned char *buf, int len) { 175 | su16(buf, 2, 0); 176 | su16(buf, 2, crc(buf, len, 0)); 177 | } 178 | 179 | /* buf points to ip header, NOT tcp header; len is length of tcp 180 | packet only */ 181 | void set_tcph_crc(unsigned char *buf, int len) { 182 | unsigned char pseudoheader[12]; 183 | uint16_t n; 184 | 185 | su16(buf, 20+16, 0); 186 | 187 | memcpy(pseudoheader+0, iph_src(buf), 4); 188 | memcpy(pseudoheader+4, iph_dst(buf), 4); 189 | pseudoheader[8] = 0; 190 | pseudoheader[9] = iph_protocol(buf); 191 | su16(pseudoheader, 10, len); 192 | 193 | n = ~crc(pseudoheader, 12, 0); 194 | su16(buf, 20+16, crc(buf+20, len, n)); 195 | } 196 |  -------------------------------------------------------------------------------- /net2.c: -------------------------------------------------------------------------------- 1 | /* Networking code for RC2014: higher-level parts 2 | * 3 | * James Stanley 2019 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "d:net.h" 11 | 12 | void net_tick() { 13 | /* TODO: drive the SIO directly so we can check for bytes 14 | available and then not block when there are none */ 15 | unsigned char *buf = read_slip_frame(); 16 | process_packet(buf, SLIP_MAX); 17 | } 18 | 19 | void process_packet(unsigned char *buf, int len) { 20 | if (!valid_ip_header(buf) || !dst_is_us(buf)) 21 | return; 22 | 23 | if (iph_len(buf) > len) 24 | return; 25 | len = iph_len(buf); 26 | 27 | if (iph_protocol(buf) == PROT_ICMP) 28 | process_icmp(buf, len); 29 | else if (iph_protocol(buf) == PROT_TCP) 30 | process_tcp(buf, len); 31 | } 32 | 33 | void process_icmp(unsigned char *buf, int len) { 34 | printf(" *** ICMP not yet implemented!!!\n"); 35 | } 36 | 37 | socket_t socktable[MAX_SOCKET]; 38 | listen_t listentable[MAX_LISTEN]; 39 | 40 | void increase_lrus() { 41 | int i; 42 | for (i = 0; i < MAX_SOCKET; i++) 43 | socktable[i].lru++; 44 | } 45 | 46 | socket_t *lru_socket() { 47 | socket_t *s = socktable; 48 | int i; 49 | 50 | for (i = 1; i < MAX_SOCKET; i++) { 51 | if (socktable[i].lru > s->lru) 52 | s = socktable+i; 53 | } 54 | 55 | /* TODO: send RST on the reused slot? */ 56 | if (s->state != CLOSED && s->close) 57 | (*(s->close))(s); 58 | 59 | return s; 60 | } 61 | 62 | socket_t *newconn_socket(uint16_t localport, uint16_t remoteport, 63 | unsigned char *remoteaddr) { 64 | int i, j; 65 | socket_t *s = NULL; 66 | 67 | for (i = 0; i < MAX_LISTEN; i++) { 68 | if (listentable[i].port == localport) 69 | break; 70 | } 71 | if (i == MAX_LISTEN) 72 | return NULL; 73 | 74 | for (j = 0; j < MAX_SOCKET; j++) { 75 | if (socktable[j].state == CLOSED) { 76 | s = socktable+j; 77 | break; 78 | } 79 | } 80 | 81 | /* no socket yet? grab the least-recently-used */ 82 | if (!s) 83 | s = lru_socket(); 84 | 85 | increase_lrus(); 86 | memset(s, sizeof(socket_t), 0); 87 | s->state = LISTEN; 88 | s->open = listentable[i].open; 89 | memcpy(s->remoteaddr, remoteaddr, 4); 90 | s->localport = localport; 91 | s->remoteport = remoteport; 92 | 93 | return s; 94 | } 95 | 96 | socket_t *lookup_socket(unsigned char *buf, int len) { 97 | int i; 98 | int ihl = iph_ihl(buf)*4; 99 | unsigned char *src = iph_src(buf); 100 | 101 | for (i = 0; i < MAX_SOCKET; i++) { 102 | if (socktable[i].state == CLOSED) 103 | continue; 104 | if (socktable[i].remoteport != tcph_sport(buf+ihl)) 105 | continue; 106 | if (memcmp(src, socktable[i].remoteaddr, 4) != 0) 107 | continue; 108 | return socktable+i; 109 | } 110 | 111 | return newconn_socket(tcph_dport(buf+ihl), tcph_sport(buf+ihl), src); 112 | } 113 | 114 | void free_socket(socket_t *s) { 115 | /* a socket in the CLOSED state is available for reuse */ 116 | s->state = CLOSED; 117 | } 118 | 119 | void tcp_reply(unsigned char *buf, int len, int flags, socket_t *s) { 120 | unsigned char *buf2 = malloc(40); 121 | 122 | /* IP header */ 123 | init_ip_header(buf2); 124 | iph_set_len(buf2, 40); 125 | iph_set_dst(buf2, iph_src(buf)); 126 | set_iph_crc(buf2); 127 | 128 | /* TCP header */ 129 | init_tcp_header(buf2+20); 130 | tcph_set_sport(buf2+20, s->localport); 131 | tcph_set_dport(buf2+20, s->remoteport); 132 | tcph_set_seq(buf2+20, s->localseq); 133 | tcph_set_ack(buf2+20, s->remoteseq); 134 | tcph_set_flags(buf2+20, flags); 135 | set_tcph_crc(buf2, 20); 136 | 137 | write_slip_frame(buf2, 40); 138 | 139 | free(buf2); 140 | } 141 | 142 | void tcp_retransmit(socket_t *s) { 143 | if (s->sendlen == 0) 144 | return; 145 | 146 | write_slip_frame(s->sendbuf, s->sendlen); 147 | } 148 | 149 | void process_tcp(unsigned char *buf, int len) { 150 | socket_t *s; 151 | int ihl = iph_ihl(buf)*4; 152 | int ihl_20 = ihl+20; 153 | 154 | if (!valid_tcp_header(buf, len)) 155 | return; 156 | 157 | s = lookup_socket(buf, len); 158 | if (!s) 159 | return; 160 | 161 | if (s->state != LISTEN && tcph_seq(buf+ihl) != s->remoteseq) 162 | return; 163 | 164 | increase_lrus(); 165 | s->lru = 0; 166 | 167 | /* TODO: tcp_retransmit(s) if we receive a duplicate ACK */ 168 | 169 | s->remoteack = tcph_ack(buf+ihl); 170 | 171 | if (s->state == CLOSED) { 172 | /* TODO: send RST? */ 173 | } else if (s->state == LISTEN) { 174 | if (tcph_flags(buf+ihl)&TCP_SYN) { 175 | s->localseq = ((uint32_t)rand())*65536 + rand(); 176 | s->remoteseq = tcph_seq(buf+ihl)+1; 177 | tcp_reply(buf, len, TCP_SYN|TCP_ACK, s); 178 | s->localseq++; 179 | s->state = SYN_RCVD; 180 | } 181 | } else if (s->state == SYN_RCVD) { 182 | if (tcph_flags(buf+ihl)&TCP_ACK) { 183 | s->state = ESTABLISHED; 184 | if (s->open) 185 | (*(s->open))(s); 186 | } 187 | } else if (s->state == SYN_SENT) { 188 | if (tcph_flags(buf+ihl)&(TCP_SYN|TCP_ACK)) { 189 | tcp_reply(buf, len, TCP_ACK, s); 190 | s->localseq++; 191 | s->state = ESTABLISHED; 192 | if (s->open) 193 | (*(s->open))(s); 194 | } 195 | } else if (s->state == ESTABLISHED) { 196 | s->remoteseq += iph_len(buf)-ihl_20; 197 | if (iph_len(buf)-ihl_20 > 0) { 198 | tcp_reply(buf, len, TCP_ACK, s); 199 | if (s->recv) 200 | (*(s->recv))(s, buf+ihl_20, iph_len(buf)-ihl_20); 201 | } 202 | if (s->remoteack == s->localseq) { 203 | /* all our packets have been acknowledged; we can 204 | try to send another */ 205 | if (s->send) 206 | (*(s->send))(s, 256); 207 | } 208 | if (tcph_flags(buf+ihl)&TCP_FIN) { 209 | s->remoteseq++; 210 | tcp_reply(buf, len, TCP_FIN|TCP_ACK, s); 211 | s->state = CLOSED; 212 | if (s->close) 213 | (*(s->close))(s); 214 | } 215 | } else if (s->state == CLOSE_WAIT) { 216 | /* ??? */ 217 | } else if (s->state == LAST_ACK) { 218 | if (tcph_flags(buf+ihl)&TCP_ACK) { 219 | free_socket(s); 220 | } 221 | } else if (s->state == FIN_WAIT_1) { 222 | if (tcph_flags(buf+ihl)&(TCP_FIN|TCP_ACK)) { 223 | tcp_reply(buf, len, TCP_ACK, s); 224 | s->state = TIME_WAIT; 225 | } else if (tcph_flags(buf+ihl)&TCP_FIN) { 226 | tcp_reply(buf, len, TCP_ACK, s); 227 | s->state = CLOSING; 228 | } else if (tcph_flags(buf+ihl)&TCP_ACK) { 229 | s->state = FIN_WAIT_2; 230 | } 231 | } else if (s->state == FIN_WAIT_2) { 232 | if (tcph_flags(buf+ihl)&TCP_FIN) { 233 | tcp_reply(buf, len, TCP_ACK, s); 234 | s->state = TIME_WAIT; 235 | } 236 | } else if (s->state == CLOSING) { 237 | if (tcph_flags(buf+ihl)&TCP_ACK) { 238 | s->state = TIME_WAIT; 239 | } 240 | } else if (s->state == TIME_WAIT) { 241 | /* ??? */ 242 | } 243 | 244 | if (s->state == TIME_WAIT) { 245 | /* XXX: we have no easy way to measure time, so just free 246 | the socket immediately if it goes into the TIME_WAIT state */ 247 | free_socket(s); 248 | } 249 | } 250 |  --------------------------------------------------------------------------------