├── .gitignore ├── deps └── dbuf │ ├── clib.json │ ├── package.json │ ├── dbuf.c │ └── dbuf.h ├── package.json ├── CHANGELOG.md ├── COPYING ├── netdial.h ├── README.md ├── test-echoserver.c └── netdial.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.[do] 2 | /mkfile 3 | /test-* 4 | !/test-*.c 5 | -------------------------------------------------------------------------------- /deps/dbuf/clib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dbuf", 3 | "version": "0.1.0", 4 | "repo": "aperezdc/dbuf", 5 | "description": "Resizable data buffers", 6 | "license": "MIT", 7 | "keywords": [ 8 | "dynamic", 9 | "data", 10 | "buffer" 11 | ], 12 | "src": [ 13 | "dbuf.h", 14 | "dbuf.c" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /deps/dbuf/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dbuf", 3 | "version": "0.1.0", 4 | "repo": "aperezdc/dbuf", 5 | "description": "Resizable data buffers", 6 | "license": "MIT", 7 | "keywords": [ 8 | "dynamic", 9 | "data", 10 | "buffer" 11 | ], 12 | "src": [ 13 | "dbuf.h", 14 | "dbuf.c" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "netdial", 3 | "version": "0.1.0", 4 | "repo": "aperezdc/netdial", 5 | "description": "", 6 | "license": "MIT", 7 | "keywords": [ 8 | "network", 9 | "sockets", 10 | "utility" 11 | ], 12 | "src": [ 13 | "netdial.h", 14 | "netdial.c" 15 | ], 16 | "dependencies": { 17 | "aperezdc/dbuf": "0.1.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.1.0] - 2020-10-04 9 | 10 | Initial release. 11 | 12 | [0.1.0]: https://github.com/aperezdc/netdial/releases/tag/0.1.0 13 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Adrian Perez 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /netdial.h: -------------------------------------------------------------------------------- 1 | /* 2 | * netdial.h 3 | * Copyright (C) 2020 Adrian Perez de Castro 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #ifndef NETDIAL_H 9 | #define NETDIAL_H 10 | 11 | enum { 12 | NDdefault = 0, 13 | 14 | NDblocking = 1 << 1, 15 | NDexeckeep = 1 << 2, 16 | 17 | /* Unix socket flags. */ 18 | NDpasscred = 1 << 9, 19 | NDpassec = 1 << 10, 20 | 21 | /* Common socket flags. */ 22 | NDbroadcast = 1 << 17, 23 | NDdebug = 1 << 18, 24 | NDkeepalive = 1 << 19, 25 | NDreuseaddr = 1 << 20, 26 | NDreuseport = 1 << 21, 27 | }; 28 | 29 | enum { 30 | /* Hangup flags. */ 31 | NDclose = 0, 32 | NDread = 1 << 1, 33 | NDwrite = 1 << 2, 34 | NDrdwr = NDread | NDwrite, 35 | }; 36 | 37 | enum { 38 | /* Address kind. */ 39 | NDlocal, 40 | NDremote, 41 | }; 42 | 43 | extern int netdial(const char *address, int flags); 44 | extern int netannounce(const char *address, int flags, int backlog); 45 | extern int netaccept(int fd, int flags, char **remoteaddr); 46 | extern int nethangup(int fd, int flags); 47 | extern int netaddress(int fd, int kind, char **address); 48 | 49 | #endif /* !NETDIAL_H */ 50 | -------------------------------------------------------------------------------- /deps/dbuf/dbuf.c: -------------------------------------------------------------------------------- 1 | /* 2 | * dbuf.c 3 | * Copyright (C) 2020 Adrian Perez de Castro 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #include "dbuf.h" 9 | #include 10 | #include 11 | #include 12 | 13 | enum { 14 | CHUNKSIZE = 512, /* bytes */ 15 | }; 16 | 17 | /* Wraps malloc(), realloc(), and free(). */ 18 | void* 19 | mrealloc(void *ptr, size_t size) 20 | { 21 | if (size) { 22 | ptr = ptr ? realloc(ptr, size) : malloc(size); 23 | } else if (ptr) { 24 | free(ptr); 25 | ptr = NULL; 26 | } 27 | return ptr; 28 | } 29 | 30 | /* Reallocates a buffer and sets the .alloc member. */ 31 | static inline void 32 | brealloc(struct dbuf *b, size_t size) 33 | { 34 | if (size) { 35 | const size_t new_size = CHUNKSIZE * ((size / CHUNKSIZE) + 1) + 1; 36 | assert(new_size >= size); 37 | if (new_size != b->alloc) 38 | b->data = mrealloc(b->data, (b->alloc = new_size)); 39 | } else if (b->data) { 40 | free(b->data); 41 | b->data = NULL; 42 | b->alloc = 0; 43 | } else { 44 | assert(b->alloc == 0); 45 | } 46 | } 47 | 48 | /* Calls brealloc() and sets the .size member. */ 49 | static inline void 50 | bresize(struct dbuf *b, size_t size) 51 | { 52 | brealloc(b, size); 53 | b->size = size; 54 | } 55 | 56 | struct dbuf* 57 | dbuf_new(size_t prealloc) 58 | { 59 | struct dbuf *b = mrealloc(NULL, sizeof(struct dbuf)); 60 | *b = DBUF_INIT; 61 | bresize(b, prealloc); 62 | return b; 63 | } 64 | 65 | void 66 | dbuf_free(struct dbuf *b) 67 | { 68 | assert(b); 69 | dbuf_clear(b); 70 | mrealloc(b, 0); 71 | } 72 | 73 | void 74 | dbuf_resize(struct dbuf *b, size_t size) 75 | { 76 | assert(b); 77 | bresize(b, size); 78 | } 79 | 80 | void 81 | dbuf_clear(struct dbuf *b) 82 | { 83 | assert(b); 84 | bresize(b, 0); 85 | } 86 | 87 | void 88 | dbuf_addmem(struct dbuf *b, const void *data, size_t size) 89 | { 90 | assert(b); 91 | assert(data); 92 | 93 | const size_t bsize = b->size; 94 | bresize(b, bsize + size); 95 | memcpy(b->data + bsize, data, size); 96 | } 97 | 98 | void 99 | dbuf_addstr(struct dbuf *b, const char *s) 100 | { 101 | assert(b); 102 | assert(s); 103 | 104 | const size_t bsize = b->size; 105 | const size_t slen = strlen(s); 106 | bresize(b, bsize + slen); 107 | memcpy(b->data + bsize, s, slen); 108 | } 109 | 110 | void 111 | dbuf_addfmtv(struct dbuf *b, const char *format, va_list args) 112 | { 113 | assert(b); 114 | assert(format); 115 | 116 | va_list saved; 117 | va_copy(saved, args); 118 | 119 | const size_t available = (b->alloc - b->size); 120 | const size_t needed = available 121 | ? vsnprintf((char*) b->data + b->size, available - 1, format, args) 122 | : vsnprintf(NULL, 0, format, args); 123 | 124 | if (needed >= available) { 125 | brealloc(b, b->alloc + needed); 126 | vsnprintf((char*) b->data + b->size, b->alloc - 1, format, saved); 127 | } 128 | 129 | b->size += needed; 130 | va_end(saved); 131 | } 132 | 133 | char* 134 | dbuf_str(struct dbuf *b) 135 | { 136 | assert(b); 137 | 138 | if (!b->alloc) 139 | brealloc(b, 1); 140 | 141 | b->data[b->size] = '\0'; 142 | return (char*) b->data; 143 | } 144 | -------------------------------------------------------------------------------- /deps/dbuf/dbuf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * dbuf.h 3 | * Copyright (C) 2020 Adrian Perez de Castro 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #ifndef DBUF_H 9 | #define DBUF_H 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #if !(defined(__GNUC__) && __GNUC__ >= 3) && !defined(__attribute__) 18 | # define __attribute__(dummy) 19 | #endif 20 | 21 | struct dbuf { 22 | uint8_t *data; 23 | size_t size; 24 | size_t alloc; 25 | }; 26 | 27 | #define DBUF_INIT ((struct dbuf) { .data = NULL, .size = 0, .alloc = 0 }) 28 | 29 | struct dbuf* dbuf_new(size_t prealloc) 30 | __attribute__((warn_unused_result)); 31 | 32 | void dbuf_free(struct dbuf*) 33 | __attribute__((nonnull(1))); 34 | 35 | void dbuf_resize(struct dbuf*, size_t) 36 | __attribute__((nonnull(1))); 37 | 38 | void dbuf_clear(struct dbuf*) 39 | __attribute__((nonnull(1))); 40 | 41 | void dbuf_addmem(struct dbuf*, const void*, size_t) 42 | __attribute__((nonnull(1, 2))); 43 | 44 | void dbuf_addstr(struct dbuf*, const char*) 45 | __attribute__((nonnull(1, 2))); 46 | 47 | void dbuf_addfmtv(struct dbuf*, const char*, va_list) 48 | __attribute__((nonnull(1, 2))); 49 | 50 | char* dbuf_str(struct dbuf*) 51 | __attribute__((warn_unused_result)) 52 | __attribute__((nonnull(1))); 53 | 54 | 55 | static inline size_t dbuf_size(const struct dbuf*) 56 | __attribute__((warn_unused_result)) 57 | __attribute__((nonnull(1))); 58 | 59 | size_t 60 | dbuf_size(const struct dbuf *b) 61 | { 62 | assert(b); 63 | return b->size; 64 | } 65 | 66 | 67 | static inline uint8_t* dbuf_data(struct dbuf*) 68 | __attribute__((warn_unused_result)) 69 | __attribute__((nonnull(1))); 70 | 71 | uint8_t* 72 | dbuf_data(struct dbuf *b) 73 | { 74 | assert(b); 75 | return b->data; 76 | } 77 | 78 | 79 | static inline const uint8_t* dbuf_cdata(const struct dbuf*) 80 | __attribute__((warn_unused_result)) 81 | __attribute__((nonnull(1))); 82 | 83 | const uint8_t* dbuf_cdata(const struct dbuf *b) 84 | { 85 | assert(b); 86 | return b->data; 87 | } 88 | 89 | 90 | static inline bool dbuf_empty(const struct dbuf*) 91 | __attribute__((warn_unused_result)) 92 | __attribute__((nonnull(1))); 93 | 94 | bool 95 | dbuf_empty(const struct dbuf *b) 96 | { 97 | assert(b); 98 | return b->size == 0; 99 | } 100 | 101 | 102 | static inline void dbuf_addch(struct dbuf*, char) 103 | __attribute__((nonnull(1))); 104 | 105 | void 106 | dbuf_addch(struct dbuf *b, char c) 107 | { 108 | assert(b); 109 | dbuf_addmem(b, &c, 1); 110 | } 111 | 112 | 113 | static inline void dbuf_addbuf(struct dbuf*, const struct dbuf*) 114 | __attribute__((nonnull(1, 2))); 115 | 116 | void 117 | dbuf_addbuf(struct dbuf *b, const struct dbuf *o) 118 | { 119 | assert(b); 120 | assert(o); 121 | 122 | dbuf_addmem(b, dbuf_cdata(o), dbuf_size(o)); 123 | } 124 | 125 | static inline void dbuf_addfmt(struct dbuf*, const char*, ...) 126 | __attribute__((nonnull(1, 2))); 127 | 128 | void 129 | dbuf_addfmt(struct dbuf *b, const char *format, ...) 130 | { 131 | assert(b); 132 | assert(format); 133 | 134 | va_list args; 135 | va_start(args, format); 136 | dbuf_addfmtv(b, format, args); 137 | va_end(args); 138 | } 139 | 140 | #endif /* !DBUF_H */ 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netdial 2 | 3 | This is `netdial`, a small opinionated C utility library to ease some of the 4 | tedious parts of programming with the BSD sockets API while providing sane 5 | defaults. 6 | 7 | 8 | ## Features 9 | 10 | * Support for Unix and IP (TCP, UDP) sockets. 11 | * Usage of [address strings](#address-strings) to describe sockets. 12 | * Sane defaults for socket file descriptors: 13 | - Non-blocking (`O_NONBLOCK`). 14 | - Close on exec (`O_CLOEXEC`). 15 | * Uses modern best practices internally: `getaddrinfo` + `getnameinfo`, 16 | `accept4` where available, etc. 17 | 18 | ### Goals 19 | 20 | * Providing a simplified, convenience API for creating, initiating and 21 | accepting socket connections. 22 | * Encouraging good network programming practices (e.g. non-blocking 23 | file descriptors as default). 24 | 25 | ### Non-Goals 26 | 27 | * Completely replacing the BSD sockets API. 28 | * Supporting `SOCK_DGRAM` for Unix sockets. 29 | * Supporting protocol families other than IP or Unix sockets. 30 | 31 | 32 | ## Address Strings 33 | 34 | Socket addresses are represented as strings of the form 35 | `:[:]`, for example `tcp6:perezdecastro.org:www-http`. 36 | 37 | The `` field is mandatory and determines the address family and 38 | connection type. It must be one of `unix`, `unixp`, `tcp`, `udp`, `tcp4`, 39 | `udp4`, `tcp6`, or `udp6`. 40 | 41 | ### Unix Socket Addresses 42 | 43 | For `unix` and `unixp` addresses the `` field must be the socket path 44 | and the `` field must be omitted. The `unixp` type choses a 45 | `SOCK_SEQPACKET` instead of `SOCK_STREAM` as the protocol. Unix sockets with 46 | protocol `SOCK_DGRAM` are not supported (see [non-goals](#non-goals) above). 47 | 48 | ### IP Socket Addresses 49 | 50 | For `tcp` and `udp` sockets the `` field is the address where to listen 51 | or to connect to. The unversioned `` names will choose either IPv4 or 52 | v6 depending on name resolution and what is supported by your system, while 53 | the versioned ones can be used to explicitly choose the IP version to use. 54 | When specifying IP addresses directly, IPv6 addresses must be specified in 55 | between square brackets. IPv6 zone names (and indexes) are supported with the 56 | usual syntax, using a percent sign as separator. 57 | 58 | Note that the `` field may be left empty, in which case the address 59 | string represents “any address”, which is equivalent to `0.0.0.0` and `::` for 60 | IPv4 and v6 addresses, respectively. This is particularly convenient when 61 | passing addresses to [netannounce()](#netannounce) for creating listening 62 | sockets. 63 | 64 | 65 | ## API Reference 66 | 67 | ### netdial 68 | 69 | ```c 70 | int netdial(const char *address, int flags); 71 | ``` 72 | 73 | Creates a socket connected to `address` (see [Address 74 | Strings](#address-strings)), with the given `flags` (see [Socket 75 | Flags](#socket-flags)). 76 | 77 | Returns the socket file descriptor. On error, returns `-1` and sets the 78 | `errno` variable appropriately. 79 | 80 | ### netannounce 81 | 82 | ```c 83 | int netannounce(const char *address, int flags, int backlog); 84 | ``` 85 | 86 | Creates a socket listening at `address` (see [Address 87 | Strings](#address-strings)), with the given `flags` (see [Socket 88 | Flags](#socket-flags)). The `backlog` argument defines the maximum amount of 89 | pending connections to queue unaccepted e.g. using [netaccept()](#netaccept). 90 | 91 | Returns the socket file descriptor. On error, returns `-1` and sets the 92 | `errno` variable appropriately. 93 | 94 | ### netaccept 95 | 96 | ```c 97 | int netaccept(int fd, int flags, char **remoteaddress); 98 | ``` 99 | 100 | Takes the next connection in the queue of pending connections for the `fd` 101 | socket, creates a new socket file descriptor for it with the given `flags` 102 | (see [Socket Flags](#socket-flags)), and returns it. 103 | 104 | The `fd` socket must be bound and listening for connections as created by 105 | [netannounce()](#netannounce), and use a connection oriented protocol (`unix`, 106 | `unixp`, `tcp4`, `tcp6`). 107 | 108 | If the `remoteaddress` argument is not `NULL`, it is set to the address of the 109 | remote peer. This is the same address returned by [netaddress()](#netaddress) 110 | when used with `NDremote`. The caller is responsible of calling `free()` on 111 | the returned value. 112 | 113 | Returns the socket file descriptor for the accepted socket connection. On 114 | error, returns `-1` and sets the `errno` variable appropriately. 115 | 116 | ### nethangup 117 | 118 | ```c 119 | int nethangup(int fd, int how); 120 | enum { NDclose, NDread, NDwrite, NDrdwr }; /* how */ 121 | ``` 122 | 123 | Hangs up the `fd` socket connection, partially or completely depending on the 124 | value of the `how` argument: 125 | 126 | * `NDclose`: Completely closes the socket, which cannot be used afterwards. 127 | * `NDread`: Half-closes the socket for reading; writing data and manipulating 128 | socket state is still possible. 129 | * `NDwrite`: Half-closes the socket for writing; reading data and manipulating 130 | socket state is still possible. 131 | * `NDrdwr`: Closes the socket for data transfer; only manipulating socket 132 | state is possible. 133 | 134 | ### netaddress 135 | 136 | ```c 137 | int netaddress(int fd, int kind, char **address); 138 | enum { NDlocal, NDremote }; /* kind */ 139 | ``` 140 | 141 | Obtain an address associated with the `fd` socket, depending on the value of 142 | the `kind` argument: 143 | 144 | * `NDlocal`: Local socket address. 145 | * `NDremote`: Remote peer socket address. 146 | 147 | The `address` argument must not be `NULL` and will be used to store the 148 | requested address. The caller is responsible of calling `free()` on the 149 | returned value. 150 | 151 | Returns `0` on success. On error, returns `-1` and sets the `errno` variable 152 | appropriately. 153 | 154 | ### Socket Flags 155 | 156 | ```c 157 | enum { 158 | NDdefault, 159 | 160 | /* Common socket flags. */ 161 | NDblocking, 162 | NDexeckeep, 163 | NDdebug, 164 | NDreuseaddr, 165 | NDreuseport, 166 | 167 | /* UDP socket flags. */ 168 | NDbroadcast, 169 | 170 | /* TCP socket flags. */ 171 | NDkeepalive, 172 | 173 | /* Unix socket flags. */ 174 | NDpasscred, 175 | NDpassec, 176 | }; 177 | ``` 178 | 179 | Functions that create socket file descriptors receive a `flags` argument, 180 | which is a *bitwise or* (C operator `|`) of the following values: 181 | 182 | * `NDdefault`: Use default options for the socket (non-blocking, 183 | close-on-exec). 184 | * `NDblocking`: Do no set the socket in non-blocking mode; reading or writing 185 | may block. 186 | * `NDexeckeep`: Do not set the close-on-exec flag; the socket will be usable 187 | after the program calls `exec*()`. 188 | * `NDdebug`: Enable socket debugging. 189 | * `NDreuseaddr`: Set the `SO_REUSEADDR` socket option. 190 | * `NDreuseport`: Set the `SO_REUSEPORT` socket option. 191 | * `NDbroadcast`: For UDP sockets, allow sending data to broadcast addresses. 192 | * `NDkeepalive`: For TCP sockets, enable sensing keep-alive messages. 193 | * `NDpasscred`: For Unix sockets, enable receiving the `SCM_CREDENTIALS` 194 | control message. 195 | * `NDpassec`: For Unix sockets, enable receiving the `SCM_SECURITY` control 196 | message. 197 | -------------------------------------------------------------------------------- /test-echoserver.c: -------------------------------------------------------------------------------- 1 | /* 2 | * test-echoserver.c 3 | * Copyright (C) 2020 Adrian Perez de Castro 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #include "netdial.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | enum { 21 | Chunksize = 1024U, 22 | }; 23 | 24 | struct chunk { 25 | uint8_t buf[Chunksize]; 26 | size_t len; 27 | size_t off; 28 | struct chunk *next; 29 | }; 30 | 31 | struct chunkq { 32 | struct chunk *head; 33 | struct chunk *tail; 34 | }; 35 | 36 | static struct chunk* 37 | mkchunk(void) 38 | { 39 | struct chunk *c = malloc(sizeof(struct chunk)); 40 | c->len = c->off = 0; 41 | c->next = NULL; 42 | return c; 43 | } 44 | 45 | static void 46 | freechunk(struct chunk **c) 47 | { 48 | assert(c); 49 | free(*c); 50 | c = NULL; 51 | } 52 | 53 | static void 54 | pushchunk(struct chunkq *q, struct chunk *c) 55 | { 56 | assert(q); 57 | assert(c); 58 | 59 | if (q->tail) { 60 | assert(q->tail->next == NULL); 61 | assert(c->next == NULL); 62 | 63 | q->tail->next = c; 64 | q->tail = c; 65 | } else { 66 | assert(q->head == NULL); 67 | 68 | q->head = q->tail = c; 69 | } 70 | } 71 | 72 | static struct chunk* 73 | popchunk(struct chunkq *q) 74 | { 75 | assert(q); 76 | assert(q->head); 77 | 78 | struct chunk *c = q->head; 79 | q->head = q->head->next; 80 | return c; 81 | } 82 | 83 | static struct chunk* 84 | firstchunk(struct chunkq *q) 85 | { 86 | assert(q); 87 | assert(q->head); 88 | return q->head; 89 | } 90 | 91 | static bool 92 | haschunk(const struct chunkq* q) 93 | { 94 | assert(q); 95 | return q->head != NULL; 96 | } 97 | 98 | struct conn { 99 | struct event rev; 100 | struct event wev; 101 | struct chunkq chunks; 102 | size_t nbytes; 103 | }; 104 | 105 | static void 106 | freeconn(struct conn **conn) 107 | { 108 | assert(conn); 109 | 110 | event_del(&(*conn)->rev); 111 | while (haschunk(&(*conn)->chunks)) { 112 | struct chunk *c = popchunk(&(*conn)->chunks); 113 | freechunk(&c); 114 | } 115 | 116 | free(*conn); 117 | conn = NULL; 118 | } 119 | 120 | static void 121 | handle_conn_read(int fd, short events, void *data) 122 | { 123 | struct conn *conn = data; 124 | 125 | for (;;) { 126 | /* Try to read as many chunks as possible before blocking. */ 127 | fprintf(stderr, 128 | "[#%d] Attempting to read %u bytes.\n", 129 | fd, Chunksize); 130 | 131 | struct chunk *c = mkchunk(); 132 | ssize_t r = read(fd, c->buf, Chunksize); 133 | if (r == 0) { 134 | /* Client disconnected. */ 135 | fprintf(stderr, 136 | "[#%d] Closed, exchanged %zu bytes.\n", 137 | fd, conn->nbytes); 138 | freechunk(&c); 139 | freeconn(&conn); 140 | nethangup(fd, NDclose); 141 | return; 142 | } 143 | 144 | if (r == -1) { 145 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 146 | fprintf(stderr, 147 | "[#%d] Not ready, will read later.\n", 148 | fd); 149 | break; 150 | } 151 | 152 | fprintf(stderr, 153 | "[#%d] Closed, read error: %s.\n", 154 | fd, strerror(errno)); 155 | freechunk(&c); 156 | freeconn(&conn); 157 | nethangup(fd, NDclose); 158 | return; 159 | } 160 | 161 | fprintf(stderr, "[#%d] Read %zd bytes.\n", fd, r); 162 | c->len = r; 163 | pushchunk(&conn->chunks, c); 164 | 165 | if (r < Chunksize) { 166 | fprintf(stderr, 167 | "[#%d] Short read, will read later.\n", 168 | fd); 169 | break; 170 | } 171 | } 172 | 173 | /* Schedule writing. */ 174 | if (haschunk(&conn->chunks)) 175 | event_add(&conn->wev, NULL); 176 | } 177 | 178 | static void 179 | handle_conn_write(int fd, short events, void *data) 180 | { 181 | struct conn *conn = data; 182 | 183 | while (haschunk(&conn->chunks)) { 184 | struct chunk *c = firstchunk(&conn->chunks); 185 | assert(c->len - c->off > 0); 186 | size_t n = c->len - c->off; 187 | 188 | fprintf(stderr, 189 | "[#%d] Attempting to write %zu bytes.\n", 190 | fd, n); 191 | 192 | ssize_t r = write(fd, c->buf + c->off, n); 193 | 194 | if (r == -1) { 195 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 196 | /* Try again later. */ 197 | event_add(&conn->wev, NULL); 198 | return; 199 | } 200 | 201 | fprintf(stderr, 202 | "[#%d] Closed, read error: %s.\n", 203 | fd, strerror(errno)); 204 | freechunk(&c); 205 | freeconn(&conn); 206 | nethangup(fd, NDclose); 207 | return; 208 | } 209 | 210 | fprintf(stderr, 211 | "[#%d] Wrote %zd bytes, %zd pending.\n", 212 | fd, r, n - r); 213 | 214 | conn->nbytes += r; 215 | 216 | if (r < n) { 217 | /* Data pending to write. Update offset and reschedule. */ 218 | c->off += n; 219 | event_add(&conn->wev, NULL); 220 | return; 221 | } 222 | 223 | /* Chunk completely written. */ 224 | popchunk(&conn->chunks); 225 | freechunk(&c); 226 | } 227 | } 228 | 229 | static void 230 | handle_accept(int fd, short events, void *data) 231 | { 232 | struct event_base *evbase = data; 233 | 234 | for (unsigned n = 0;; n++) { 235 | char *remote = NULL; 236 | int nfd = netaccept(fd, NDdefault, &remote); 237 | if (nfd < 0) { 238 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 239 | fprintf(stderr, "[#%d] Accepted %u new connections.\n", fd, n); 240 | } else { 241 | fprintf(stderr, "[#%d] Netaccept: %s.\n", fd, strerror(errno)); 242 | } 243 | break; 244 | } 245 | 246 | fprintf(stderr, "[#%d] New connection <%s>\n", nfd, remote); 247 | free(remote); 248 | 249 | struct conn *conn = calloc(1, sizeof(struct conn)); 250 | 251 | event_set(&conn->rev, nfd, EV_READ | EV_PERSIST, 252 | handle_conn_read, conn); 253 | event_base_set(evbase, &conn->rev); 254 | 255 | event_set(&conn->wev, nfd, EV_WRITE, 256 | handle_conn_write, conn); 257 | event_base_set(evbase, &conn->wev); 258 | 259 | /* Start reading only. */ 260 | event_add(&conn->rev, NULL); 261 | } 262 | } 263 | 264 | static void 265 | handle_signal(int signum, short events, void *data) 266 | { 267 | struct event *ev = data; 268 | 269 | fprintf(stderr, "\rExiting gracefully...\n"); 270 | signal_del(ev); 271 | 272 | event_base_loopexit(ev->ev_base, NULL); 273 | } 274 | 275 | int 276 | main(int argc, char *argv[]) 277 | { 278 | if (argc != 2) { 279 | fprintf(stderr, "Usage: %s
\n", argv[0]); 280 | fprintf(stderr, "Example: %s tcp:localhost:echo\n", argv[0]); 281 | return EXIT_FAILURE; 282 | } 283 | 284 | setenv("EVENT_SHOW_METHOD", "1", 1); 285 | 286 | int fd = netannounce(argv[1], NDdefault, 0); 287 | if (fd < 0) { 288 | fprintf(stderr, "Cannot announce %s: %s.\n", argv[1], strerror(errno)); 289 | return EXIT_FAILURE; 290 | } 291 | 292 | struct event_base *evbase = event_init(); 293 | char *localaddr = NULL; 294 | if (netaddress(fd, NDlocal, &localaddr) == -1) { 295 | fprintf(stderr, "Cannot obtain local socket address: %s.\n", strerror(errno)); 296 | nethangup(fd, NDclose); 297 | return EXIT_FAILURE; 298 | } 299 | 300 | fprintf(stderr, "[#%d] Listening on <%s>.\n", fd, localaddr); 301 | free(localaddr); 302 | localaddr = NULL; 303 | 304 | struct event evaccept; 305 | event_set(&evaccept, fd, EV_READ | EV_PERSIST, handle_accept, evbase); 306 | event_base_set(evbase, &evaccept); 307 | event_add(&evaccept, NULL); 308 | 309 | struct event evsignal; 310 | signal_set(&evsignal, SIGINT, handle_signal, &evsignal); 311 | event_base_set(evbase, &evaccept); 312 | signal_add(&evsignal, NULL); 313 | 314 | event_base_dispatch(evbase); 315 | event_base_free(evbase); 316 | 317 | nethangup(fd, NDclose); 318 | return EXIT_SUCCESS; 319 | } 320 | -------------------------------------------------------------------------------- /netdial.c: -------------------------------------------------------------------------------- 1 | /* 2 | * netdial.c 3 | * Copyright (C) 2020 Adrian Perez de Castro 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #define _POSIX_C_SOURCE 201112L 9 | #define _DEFAULT_SOURCE 10 | 11 | #if defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__) 12 | # define AUTODETECTED_ACCEPT4 1 13 | # define _BSD_SOURCE 14 | #endif /* __*BSD__ */ 15 | 16 | #if defined(__linux__) 17 | # define AUTODETECTED_ACCEPT4 1 18 | #endif /* __linux__ */ 19 | 20 | #if !defined(AUTODETECTED_ACCEPT4) 21 | # define AUTODETECTED_ACCEPT4 0 22 | #endif /* !AUTODETECTED_ACCEPT4 */ 23 | 24 | #if !defined(HAVE_ACCEPT4) 25 | # define HAVE_ACCEPT4 AUTODETECTED_ACCEPT4 26 | #endif /* !HAVE_ACCEPT4 */ 27 | 28 | #include "dbuf/dbuf.h" 29 | #include "netdial.h" 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #ifndef nelem 44 | #define nelem(v) (sizeof(v) / sizeof(v[0])) 45 | #endif /* !nelem */ 46 | 47 | #if HAVE_ACCEPT4 48 | extern int accept4(int, struct sockaddr*, socklen_t*, int); 49 | #else /* !HAVE_ACCEPT4 */ 50 | static inline int 51 | accept4(int fd, struct sockaddr *sa, socklen_t *salen, int flags) 52 | { 53 | int nfd = accept(fd, sa, salen); 54 | if (nfd < 0) 55 | return nfd; 56 | 57 | const int of = fcntl(nfd, F_GETFD); 58 | if (of == -1) 59 | goto beach; 60 | 61 | int nf = of; 62 | if (flags & SOCK_NONBLOCK) 63 | nf |= O_NONBLOCK; 64 | else 65 | nf &= ~O_NONBLOCK; 66 | 67 | if (nf != of) 68 | if (fcntl(nfd, F_SETFD, nf) == -1) 69 | goto beach; 70 | 71 | return nfd; 72 | 73 | beach: 74 | close(nfd); 75 | return -1; 76 | } 77 | #endif /* HAVE_ACCEPT4 */ 78 | 79 | #ifndef SO_PASSCRED 80 | #define SO_PASSCRED 0 81 | #endif /* !SO_PASSCRED */ 82 | 83 | #ifndef SO_PASSEC 84 | #define SO_PASSEC 0 85 | #endif /* !SO_PASSEC */ 86 | 87 | static const struct { 88 | int ndflag; 89 | int sockopt; 90 | } optflags[] = { 91 | { NDpasscred, SO_PASSCRED }, 92 | { NDpassec, SO_PASSEC }, 93 | { NDbroadcast, SO_BROADCAST }, 94 | { NDdebug, SO_DEBUG }, 95 | { NDkeepalive, SO_KEEPALIVE }, 96 | { NDreuseaddr, SO_REUSEADDR }, 97 | { NDreuseport, SO_REUSEPORT }, 98 | }; 99 | 100 | enum { 101 | NDsockflagmask = 0x000000FF, 102 | NDunixoptmask = 0x0000FF00, 103 | NDsockoptmask = 0xFFFF0000, 104 | }; 105 | 106 | static const struct { 107 | const char *name; 108 | int family, socktype; 109 | } nettypes[] = { 110 | { "tcp", AF_UNSPEC, SOCK_STREAM }, 111 | { "udp", AF_UNSPEC, SOCK_DGRAM }, 112 | { "tcp4", AF_INET, SOCK_STREAM }, 113 | { "udp4", AF_INET, SOCK_DGRAM }, 114 | { "tcp6", AF_INET6, SOCK_STREAM }, 115 | { "udp6", AF_INET6, SOCK_DGRAM }, 116 | { "unix", AF_UNIX, SOCK_STREAM }, 117 | { "unixp", AF_UNIX, SOCK_SEQPACKET }, 118 | }; 119 | 120 | static bool 121 | getnettype(const char *name, unsigned namelen, int *family, int *socktype) 122 | { 123 | assert(name); 124 | assert(family); 125 | assert(socktype); 126 | 127 | if (!namelen) 128 | return false; 129 | 130 | for (unsigned i = 0; i < nelem(nettypes); i++) { 131 | if (strncasecmp(name, nettypes[i].name, namelen) == 0) { 132 | *family = nettypes[i].family; 133 | *socktype = nettypes[i].socktype; 134 | return true; 135 | } 136 | } 137 | 138 | return false; 139 | } 140 | 141 | static const char* 142 | getnetname(int family, int socktype) 143 | { 144 | for (unsigned i = 0; i < nelem(nettypes); i++) 145 | if (nettypes[i].family == family && nettypes[i].socktype == socktype) 146 | return nettypes[i].name; 147 | return NULL; 148 | } 149 | 150 | struct netaddr { 151 | int family, socktype; 152 | char address[NI_MAXHOST + 1]; 153 | char service[NI_MAXSERV + 1]; 154 | uint16_t addrlen; 155 | uint16_t servlen; 156 | }; 157 | 158 | static bool 159 | netaddrparse(const char *str, struct netaddr *na) 160 | { 161 | assert(na); 162 | 163 | if (!str) 164 | return false; 165 | 166 | *na = (struct netaddr) {}; 167 | 168 | const char *type = str; 169 | const char *colon = strchr(type, ':'); 170 | if (!colon) 171 | return false; 172 | 173 | const unsigned typelen = colon - type; 174 | if (!getnettype(type, typelen, &na->family, &na->socktype)) 175 | return false; 176 | 177 | const char *node = ++colon; 178 | unsigned nodelen; 179 | if (*node == '[') { 180 | /* Bracketed IPv6 address. */ 181 | const char *endbracket = strchr(++node, ']'); 182 | if (!endbracket) 183 | return false; 184 | nodelen = endbracket++ - node; 185 | if (*endbracket != ':') 186 | return false; 187 | colon = endbracket; 188 | if (na->family == AF_UNSPEC) 189 | na->family = AF_INET6; 190 | else if (na->family != AF_INET6) 191 | return false; 192 | } else { 193 | colon = strchr(node, ':'); 194 | nodelen = colon ? colon - node : strlen(node); 195 | } 196 | if (nodelen > NI_MAXHOST) 197 | return false; 198 | 199 | memcpy(na->address, node, nodelen); 200 | na->address[nodelen] = '\0'; 201 | na->addrlen = nodelen; 202 | 203 | /* Port is optional for Unix sockets. */ 204 | if (!colon) 205 | return na->family == AF_UNIX; 206 | 207 | const char *service = ++colon; 208 | const unsigned servicelen = strlen(service); 209 | if (servicelen > NI_MAXSERV) 210 | return false; 211 | 212 | memcpy(na->service, service, servicelen); 213 | na->service[servicelen] = '\0'; 214 | na->servlen = servicelen; 215 | 216 | return true; 217 | } 218 | 219 | static bool 220 | applyflags(int fd, int flags) 221 | { 222 | for (unsigned i = 0; i < nelem(optflags); i++) { 223 | if (optflags[i].sockopt == 0) { 224 | /* TODO: Flag is unsupported in this build, log warning. */ 225 | continue; 226 | } 227 | 228 | if (flags & optflags[i].ndflag) { 229 | static const int value = 1; 230 | if (setsockopt(fd, SOL_SOCKET, optflags[i].sockopt, &value, sizeof(value))) 231 | return false; 232 | } 233 | } 234 | 235 | return true; 236 | } 237 | 238 | static int 239 | unixsocket(const struct netaddr *na, int flags, 240 | int (*op)(int, const struct sockaddr*, socklen_t)) 241 | { 242 | assert(na); 243 | assert(op); 244 | 245 | struct sockaddr_un name = { .sun_family = AF_UNIX }; 246 | if (na->addrlen >= sizeof(name.sun_path)) { 247 | errno = ERANGE; 248 | return -1; 249 | } 250 | strncpy(name.sun_path, na->address, na->addrlen); 251 | 252 | int socktype = na->socktype; 253 | if (!(flags & NDexeckeep)) 254 | socktype |= SOCK_CLOEXEC; 255 | if (!(flags & NDblocking)) 256 | socktype |= SOCK_NONBLOCK; 257 | 258 | int fd = socket(AF_UNIX, socktype, 0); 259 | if (fd == -1) 260 | return -1; 261 | 262 | if ((*op)(fd, (const struct sockaddr*) &name, sizeof(name)) == -1) { 263 | close(fd); 264 | return -1; 265 | } 266 | 267 | return fd; 268 | } 269 | 270 | static struct addrinfo* 271 | netaddrinfo(const struct netaddr *na, int *errcode, bool listen) 272 | { 273 | assert(na); 274 | assert(errcode); 275 | 276 | const struct addrinfo hints = { 277 | .ai_family = na->family, 278 | .ai_socktype = na->socktype, 279 | .ai_flags = (listen ? AI_PASSIVE : 0), 280 | }; 281 | 282 | struct addrinfo *result = NULL; 283 | if ((*errcode = getaddrinfo(na->addrlen ? na->address : NULL, 284 | na->service, &hints, &result))) { 285 | if (result) 286 | freeaddrinfo(result); 287 | return NULL; 288 | } 289 | 290 | return result; 291 | } 292 | 293 | static int 294 | inetsocket(const struct netaddr *na, int flags, 295 | int (*op)(int, const struct sockaddr*, socklen_t)) 296 | { 297 | int sockflags = 0; 298 | if (!(flags & NDexeckeep)) 299 | sockflags |= SOCK_CLOEXEC; 300 | if (!(flags & NDblocking)) 301 | sockflags |= SOCK_NONBLOCK; 302 | 303 | /* TODO: Use "errcode" for something. */ 304 | int errcode; 305 | struct addrinfo *ra = netaddrinfo(na, &errcode, op == bind); 306 | if (!ra) 307 | return -1; 308 | 309 | int fd = -1; 310 | for (struct addrinfo *ai = ra; ai; ai = ai->ai_next) { 311 | const int socktype = ai->ai_socktype | sockflags; 312 | if ((fd = socket(ai->ai_family, socktype, ai->ai_protocol)) == -1) 313 | continue; 314 | 315 | if ((*op)(fd, ai->ai_addr, ai->ai_addrlen) != -1) 316 | break; 317 | 318 | close(fd); 319 | fd = -1; 320 | } 321 | 322 | freeaddrinfo(ra); 323 | return fd; 324 | } 325 | 326 | int 327 | netdial(const char *address, int flags) 328 | { 329 | struct netaddr na; 330 | if (!netaddrparse(address, &na)) { 331 | errno = EINVAL; 332 | return -1; 333 | } 334 | 335 | int fd; 336 | if (na.family == AF_UNIX) { 337 | fd = unixsocket(&na, flags, connect); 338 | } else { 339 | flags &= ~NDunixoptmask; 340 | fd = inetsocket(&na, flags, connect); 341 | } 342 | 343 | if (fd == -1) 344 | return -1; 345 | 346 | if (!applyflags(fd, flags)) { 347 | close(fd); 348 | return -1; 349 | } 350 | 351 | return fd; 352 | } 353 | 354 | int 355 | netannounce(const char *address, int flags, int backlog) 356 | { 357 | struct netaddr na; 358 | if (!netaddrparse(address, &na)) { 359 | errno = EINVAL; 360 | return -1; 361 | } 362 | 363 | int fd; 364 | if (na.family == AF_UNIX) { 365 | fd = unixsocket(&na, flags, bind); 366 | } else { 367 | flags &= ~NDunixoptmask; 368 | fd = inetsocket(&na, flags, bind); 369 | } 370 | 371 | if (fd == -1) 372 | return -1; 373 | 374 | if (!applyflags(fd, flags) || listen(fd, (backlog > 0) ? backlog : 5) < 0) { 375 | close(fd); 376 | return -1; 377 | } 378 | 379 | return fd; 380 | } 381 | 382 | static char* 383 | mknetaddr(int fd, const struct sockaddr_storage *sa, socklen_t salen) 384 | { 385 | assert(sa); 386 | 387 | int socktype; 388 | socklen_t socktypelen = sizeof(socktype); 389 | if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &socktype, &socktypelen)) 390 | socktype = SOCK_STREAM; 391 | 392 | const char *netname = getnetname(sa->ss_family, socktype); 393 | if (!netname) 394 | return NULL; 395 | 396 | struct dbuf b = DBUF_INIT; 397 | dbuf_addstr(&b, netname); 398 | dbuf_addch(&b, ':'); 399 | 400 | switch (sa->ss_family) { 401 | case AF_UNIX: 402 | dbuf_addmem(&b, ((const struct sockaddr_un*) sa)->sun_path, salen); 403 | break; 404 | case AF_INET: 405 | case AF_INET6: { 406 | char host[NI_MAXHOST + 1]; 407 | char serv[NI_MAXSERV + 1]; 408 | if (getnameinfo((const struct sockaddr*) sa, salen, 409 | host, sizeof(host), 410 | serv, sizeof(serv), 411 | socktype == SOCK_DGRAM ? NI_DGRAM : 0 | 412 | NI_NUMERICHOST | 413 | NI_NUMERICSERV)) { 414 | dbuf_clear(&b); 415 | return NULL; 416 | } 417 | 418 | dbuf_addstr(&b, host); 419 | dbuf_addch(&b, ':'); 420 | dbuf_addstr(&b, serv); 421 | break; 422 | } 423 | } 424 | 425 | return dbuf_str(&b); 426 | } 427 | 428 | int 429 | netaccept(int fd, int flags, char **remoteaddr) 430 | { 431 | struct sockaddr_storage sa = {}; 432 | socklen_t salen = sizeof(sa); 433 | int nfd = accept4(fd, (struct sockaddr*) &sa, &salen, 434 | (flags & NDblocking) ? 0 : SOCK_NONBLOCK | 435 | (flags & NDexeckeep) ? 0 : SOCK_CLOEXEC); 436 | if (nfd == -1) 437 | return -1; 438 | 439 | if (remoteaddr) 440 | *remoteaddr = mknetaddr(nfd, &sa, salen); 441 | 442 | return nfd; 443 | } 444 | 445 | int 446 | nethangup(int fd, int flags) 447 | { 448 | switch (flags & NDrdwr) { 449 | /* Half-close. */ 450 | case NDread: 451 | return shutdown(fd, SHUT_RD); 452 | case NDwrite: 453 | return shutdown(fd, SHUT_WR); 454 | case NDrdwr: 455 | return shutdown(fd, SHUT_RDWR); 456 | 457 | case NDclose: { 458 | int listen = 0; 459 | socklen_t len = sizeof(listen); 460 | if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &listen, &len)) 461 | return -1; 462 | 463 | struct sockaddr_storage ss; 464 | if (listen) { 465 | len = sizeof(ss); 466 | if (getsockname(fd, (struct sockaddr*) &ss, &len)) 467 | return -1; 468 | } 469 | 470 | if (close(fd)) 471 | return -1; 472 | 473 | if (listen && ss.ss_family == AF_UNIX) { 474 | /* XXX: .sun_path it might not be null terminated. */ 475 | char node[len + 1]; 476 | strncpy(node, ((struct sockaddr_un*) &ss)->sun_path, len); 477 | return unlink(node); 478 | } 479 | 480 | return 0; 481 | } 482 | 483 | default: 484 | errno = EINVAL; 485 | return -1; 486 | } 487 | } 488 | 489 | int 490 | netaddress(int fd, int kind, char **address) 491 | { 492 | if ((kind != NDlocal && kind != NDremote) || !address) { 493 | errno = EINVAL; 494 | return -1; 495 | } 496 | 497 | struct sockaddr_storage sa = {}; 498 | socklen_t salen = sizeof(sa); 499 | int (*getname)(int, struct sockaddr*, socklen_t*) = NULL; 500 | 501 | switch (kind) { 502 | case NDlocal: 503 | getname = getsockname; 504 | break; 505 | case NDremote: 506 | getname = getpeername; 507 | break; 508 | default: 509 | assert(!"Unrechable"); 510 | abort(); 511 | } 512 | 513 | if ((*getname)(fd, (struct sockaddr*) &sa, &salen) == -1) 514 | return -1; 515 | 516 | return (*address = mknetaddr(fd, &sa, salen)) ? 0 : -1; 517 | } 518 | --------------------------------------------------------------------------------