From 2f75bae1a1514b862ba6c1c8a465759b72b518cb Mon Sep 17 00:00:00 2001 From: simon Date: Mon, 23 Oct 2000 11:55:11 +0000 Subject: [PATCH] Created a shiny new abstraction for the socket handling. Has many advantages: - protocol modules can call sk_write() without having to worry about writes blocking, because blocking writes are handled in the abstraction layer and retried later. - `Lost connection while sending' is a thing of the past. - is no longer needed in most modules, because "putty.h" doesn't have to declare `SOCKET' variables any more, only the abstracted `Socket' type. - select()-equivalent between multiple sockets will now be handled sensibly, which opens the way for things like SSH port forwarding. git-svn-id: svn://svn.tartarus.org/sgt/putty@745 cda61777-01e9-0310-a592-d414129be87e --- network.h | 44 +++++++ winnet.c | 431 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 475 insertions(+) create mode 100644 network.h create mode 100644 winnet.c diff --git a/network.h b/network.h new file mode 100644 index 00000000..71fe333b --- /dev/null +++ b/network.h @@ -0,0 +1,44 @@ +/* + * Networking abstraction in PuTTY. + * + * The way this works is: a back end can choose to open any number + * of sockets - including zero, which might be necessary in some. + * It can register a function to be called when data comes in on + * any given one, and it can call the networking abstraction to + * send data without having to worry about blocking. The stuff + * behind the abstraction takes care of selects and nonblocking + * writes and all that sort of painful gubbins. + * + * If urgent data comes in on a socket, the back end will read and + * discard up to the urgent pointer, then read the urgent byte and + * send _that_ to the receiver function with `urgent' set. + */ + +typedef struct Socket_tag *Socket; +typedef struct SockAddr_tag *SockAddr; +typedef int (*sk_receiver_t)(Socket s, int urgent, char *data, int len); + +void sk_init(void); /* called once at program startup */ + +SockAddr sk_namelookup(char *host, char **canonicalname); +void sk_addr_free(SockAddr addr); + +Socket sk_new(SockAddr addr, int port, sk_receiver_t receiver); +void sk_close(Socket s); +void sk_write(Socket s, char *buf, int len); +void sk_write_oob(Socket s, char *buf, int len); + +/* + * Each socket abstraction contains a `void *' private field in + * which the client can keep state. + */ +void sk_set_private_ptr(Socket s, void *ptr); +void *sk_get_private_ptr(Socket s); + +/* + * Special error values are returned from sk_namelookup and sk_new + * if there's a problem. These functions extract an error message, + * or return NULL if there's no problem. + */ +char *sk_addr_error(SockAddr addr); +char *sk_socket_error(Socket addr); diff --git a/winnet.c b/winnet.c new file mode 100644 index 00000000..ad43853d --- /dev/null +++ b/winnet.c @@ -0,0 +1,431 @@ +/* + * Windows networking abstraction. + */ + +#include +#include +#include + +#include "putty.h" +#include "network.h" +#include "tree234.h" + +#define BUFFER_GRANULE 512 + +struct Socket_tag { + char *error; + SOCKET s; + sk_receiver_t receiver; + void *private_ptr; + struct buffer *head, *tail; + int writable; + int in_oob, sending_oob; +}; + +struct SockAddr_tag { + char *error; + unsigned long address; +}; + +struct buffer { + struct buffer *next; + int buflen, bufpos; + char buf[BUFFER_GRANULE]; +}; + +static tree234 *sktree; + +static int cmpfortree(void *av, void *bv) { + Socket a = (Socket)av, b = (Socket)bv; + unsigned long as = (unsigned long)a->s, bs = (unsigned long)b->s; + if (as < bs) return -1; + if (as > bs) return +1; + return 0; +} + +static int cmpforsearch(void *av, void *bv) { + Socket b = (Socket)bv; + unsigned long as = (unsigned long)av, bs = (unsigned long)b->s; + if (as < bs) return -1; + if (as > bs) return +1; + return 0; +} + +void sk_init(void) { + sktree = newtree234(cmpfortree); +} + +SockAddr sk_namelookup(char *host, char **canonicalname) { + SockAddr ret = smalloc(sizeof(struct SockAddr_tag)); + unsigned long a; + struct hostent *h; + + if ( (a = inet_addr(host)) == (unsigned long) INADDR_NONE) { + if ( (h = gethostbyname(host)) == NULL) { + DWORD err = WSAGetLastError(); + ret->error = (err == WSAENETDOWN ? "Network is down" : + err == WSAHOST_NOT_FOUND ? "Host does not exist" : + err == WSATRY_AGAIN ? "Host not found" : + "gethostbyname: unknown error"); + } else { + ret->error = NULL; + memcpy (&a, h->h_addr, sizeof(a)); + *canonicalname = h->h_name; + } + } else { + *canonicalname = host; + } + ret->address = ntohl(a); + + return ret; +} + +void sk_addr_free(SockAddr addr) { + sfree(addr); +} + +Socket sk_new(SockAddr addr, int port, sk_receiver_t receiver) { + SOCKET s; + SOCKADDR_IN a; + DWORD err; + char *errstr; + Socket ret; + extern char *do_select(SOCKET skt, int startup); + + /* + * Create Socket structure. + */ + ret = smalloc(sizeof(struct Socket_tag)); + ret->error = NULL; + ret->receiver = receiver; + ret->head = ret->tail = NULL; + ret->writable = 1; /* to start with */ + ret->in_oob = FALSE; + + /* + * Open socket. + */ + s = socket(AF_INET, SOCK_STREAM, 0); + ret->s = s; + + if (s == INVALID_SOCKET) { + err = WSAGetLastError(); + ret->error = (err == WSAENETDOWN ? "Network is down" : + err == WSAEAFNOSUPPORT ? "TCP/IP support not present" : + "socket(): unknown error"); + return ret; + } + + /* + * Bind to local address. + */ + a.sin_family = AF_INET; + a.sin_addr.s_addr = htonl(INADDR_ANY); + a.sin_port = htons(0); + if (bind (s, (struct sockaddr *)&a, sizeof(a)) == SOCKET_ERROR) { + err = WSAGetLastError(); + ret->error = (err == WSAENETDOWN ? "Network is down" : + "bind(): unknown error"); + return ret; + } + + /* + * Connect to remote address. + */ + a.sin_addr.s_addr = htonl(addr->address); + a.sin_port = htons((short)port); + if (connect (s, (struct sockaddr *)&a, sizeof(a)) == SOCKET_ERROR) { + err = WSAGetLastError(); + ret->error = (err == WSAENETDOWN ? "Network is down" : + err == WSAECONNREFUSED ? "Connection refused" : + err == WSAENETUNREACH ? "Network is unreachable" : + err == WSAEHOSTUNREACH ? "No route to host" : + "connect(): unknown error"); + return ret; + } + + /* Set up a select mechanism. This could be an AsyncSelect on a + * window, or an EventSelect on an event object. */ + errstr = do_select(s, 1); + if (errstr) { + ret->error = errstr; + return ret; + } + + add234(sktree, ret); + + return ret; +} + +void sk_close(Socket s) { + del234(sktree, s); + do_select(s->s, 0); + closesocket(s->s); + free(s); +} + +char *winsock_error_string(int error) { + switch (error) { + case WSAEACCES: return "Network error: Permission denied"; + case WSAEADDRINUSE: return "Network error: Address already in use"; + case WSAEADDRNOTAVAIL: return "Network error: Cannot assign requested address"; + case WSAEAFNOSUPPORT: return "Network error: Address family not supported by protocol family"; + case WSAEALREADY: return "Network error: Operation already in progress"; + case WSAECONNABORTED: return "Network error: Software caused connection abort"; + case WSAECONNREFUSED: return "Network error: Connection refused"; + case WSAECONNRESET: return "Network error: Connection reset by peer"; + case WSAEDESTADDRREQ: return "Network error: Destination address required"; + case WSAEFAULT: return "Network error: Bad address"; + case WSAEHOSTDOWN: return "Network error: Host is down"; + case WSAEHOSTUNREACH: return "Network error: No route to host"; + case WSAEINPROGRESS: return "Network error: Operation now in progress"; + case WSAEINTR: return "Network error: Interrupted function call"; + case WSAEINVAL: return "Network error: Invalid argument"; + case WSAEISCONN: return "Network error: Socket is already connected"; + case WSAEMFILE: return "Network error: Too many open files"; + case WSAEMSGSIZE: return "Network error: Message too long"; + case WSAENETDOWN: return "Network error: Network is down"; + case WSAENETRESET: return "Network error: Network dropped connection on reset"; + case WSAENETUNREACH: return "Network error: Network is unreachable"; + case WSAENOBUFS: return "Network error: No buffer space available"; + case WSAENOPROTOOPT: return "Network error: Bad protocol option"; + case WSAENOTCONN: return "Network error: Socket is not connected"; + case WSAENOTSOCK: return "Network error: Socket operation on non-socket"; + case WSAEOPNOTSUPP: return "Network error: Operation not supported"; + case WSAEPFNOSUPPORT: return "Network error: Protocol family not supported"; + case WSAEPROCLIM: return "Network error: Too many processes"; + case WSAEPROTONOSUPPORT: return "Network error: Protocol not supported"; + case WSAEPROTOTYPE: return "Network error: Protocol wrong type for socket"; + case WSAESHUTDOWN: return "Network error: Cannot send after socket shutdown"; + case WSAESOCKTNOSUPPORT: return "Network error: Socket type not supported"; + case WSAETIMEDOUT: return "Network error: Connection timed out"; + case WSAEWOULDBLOCK: return "Network error: Resource temporarily unavailable"; + case WSAEDISCON: return "Network error: Graceful shutdown in progress"; + default: return "Unknown network error"; + } +} + +/* + * The function which tries to send on a socket once it's deemed + * writable. + */ +void try_send(Socket s) { + while (s->head) { + int nsent; + DWORD err; + int len, urgentflag; + + if (s->sending_oob) { + urgentflag = MSG_OOB; + len = s->sending_oob; + } else { + urgentflag = 0; + len = s->head->buflen - s->head->bufpos; + } + + nsent = send(s->s, s->head->buf + s->head->bufpos, len, urgentflag); + if (nsent <= 0) { + err = (nsent < 0 ? WSAGetLastError() : 0); + if (err == WSAEWOULDBLOCK) { + /* Perfectly normal: we've sent all we can for the moment. */ + s->writable = FALSE; + return; + } else if (nsent == 0 || + err == WSAECONNABORTED || + err == WSAECONNRESET) { + /* + * FIXME. This will have to be done better when we + * start managing multiple sockets (e.g. SSH port + * forwarding), because if we get CONNRESET while + * trying to write a particular forwarded socket + * then it isn't necessarily the end of the world. + * Ideally I'd like to pass the error code back to + * somewhere the next select_result() will see it, + * but that might be hard. Perhaps I should pass it + * back to be queued in the Windows front end bit. + */ + fatalbox(winsock_error_string(err)); + } else { + fatalbox(winsock_error_string(err)); + } + } else { + s->head->bufpos += nsent; + if (s->sending_oob) + s->sending_oob -= nsent; + if (s->head->bufpos >= s->head->buflen) { + struct buffer *tmp = s->head; + s->head = tmp->next; + free(tmp); + if (!s->head) + s->tail = NULL; + } + } + } +} + +void sk_write(Socket s, char *buf, int len) { + /* + * Add the data to the buffer list on the socket. + */ + if (s->tail && s->tail->buflen < BUFFER_GRANULE) { + int copylen = min(len, BUFFER_GRANULE - s->tail->buflen); + memcpy(s->tail->buf + s->tail->buflen, buf, copylen); + buf += copylen; + len -= copylen; + s->tail->buflen += copylen; + } + while (len > 0) { + int grainlen = min(len, BUFFER_GRANULE); + struct buffer *newbuf; + newbuf = smalloc(sizeof(struct buffer)); + newbuf->bufpos = 0; + newbuf->buflen = grainlen; + memcpy(newbuf->buf, buf, grainlen); + buf += grainlen; + len -= grainlen; + if (s->tail) + s->tail->next = newbuf; + else + s->head = s->tail = newbuf; + newbuf->next = NULL; + s->tail = newbuf; + } + + /* + * Now try sending from the start of the buffer list. + */ + if (s->writable) + try_send(s); +} + +void sk_write_oob(Socket s, char *buf, int len) { + /* + * Replace the buffer list on the socket with the data. + */ + if (!s->head) { + s->head = smalloc(sizeof(struct buffer)); + } else { + struct buffer *walk = s->head->next; + while (walk) { + struct buffer *tmp = walk; + walk = tmp->next; + free(tmp); + } + } + s->head->next = NULL; + s->tail = s->head; + s->head->buflen = len; + memcpy(s->head->buf, buf, len); + + /* + * Set the Urgent marker. + */ + s->sending_oob = len; + + /* + * Now try sending from the start of the buffer list. + */ + if (s->writable) + try_send(s); +} + +int select_result(WPARAM wParam, LPARAM lParam) { + int ret; + DWORD err; + char buf[BUFFER_GRANULE]; + Socket s; + int atmark; + + /* wParam is the socket itself */ + s = find234(sktree, (void *)wParam, cmpforsearch); + if (!s) + return 1; /* boggle */ + + if ((err = WSAGETSELECTERROR(lParam)) != 0) { + fatalbox(winsock_error_string(err)); + } + + switch (WSAGETSELECTEVENT(lParam)) { + case FD_READ: + ret = recv(s->s, buf, sizeof(buf), 0); + if (ret < 0) { + err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK) { + break; + } + } + if (ret < 0) { + fatalbox(winsock_error_string(err)); + } else { + int type = s->in_oob ? 2 : 0; + s->in_oob = FALSE; + return s->receiver(s, type, buf, ret); + } + break; + case FD_OOB: + /* + * Read all data up to the OOB marker, and send it to the + * receiver with urgent==1 (OOB pending). + */ + atmark = 1; + s->in_oob = TRUE; + /* Some WinSock wrappers don't support this call, so we + * deliberately don't check the return value. If the call + * fails and does nothing, we will get back atmark==1, + * which is good enough to keep going at least. */ + ioctlsocket(s->s, SIOCATMARK, &atmark); + ret = recv(s->s, buf, sizeof(buf), MSG_OOB); + if (ret <= 0) { + fatalbox(ret == 0 ? "Internal networking trouble" : + winsock_error_string(WSAGetLastError())); + } else { + return s->receiver(s, atmark ? 2 : 1, buf, ret); + } + break; + case FD_WRITE: + s->writable = 1; + try_send(s); + break; + case FD_CLOSE: + /* Signal a close on the socket. */ + return s->receiver(s, 0, NULL, 0); + break; + } + + return 1; +} + +/* + * Each socket abstraction contains a `void *' private field in + * which the client can keep state. + */ +void sk_set_private_ptr(Socket s, void *ptr) { + s->private_ptr = ptr; +} +void *sk_get_private_ptr(Socket s) { + return s->private_ptr; +} + +/* + * Special error values are returned from sk_namelookup and sk_new + * if there's a problem. These functions extract an error message, + * or return NULL if there's no problem. + */ +char *sk_addr_error(SockAddr addr) { + return addr->error; +} +char *sk_socket_error(Socket s) { + return s->error; +} + +/* + * For Plink: enumerate all sockets currently active. + */ +SOCKET first_socket(enum234 *e) { + Socket s = first234(sktree, e); + return s ? s->s : INVALID_SOCKET; +} +SOCKET next_socket(enum234 *e) { + Socket s = next234(e); + return s ? s->s : INVALID_SOCKET; +} -- 2.11.0