├── README.md ├── examples ├── broadcast.c ├── compile.sh ├── dial.c ├── get.c └── listen.c ├── fnet.c └── fnet.h /README.md: -------------------------------------------------------------------------------- 1 | # fnet - FILE* over your socks. 2 | 3 | #### Create, send and receive data over your tcp, udp and unix sockets with familiar C standard library FILE* IO interface. 4 | 5 | ```c 6 | NetConn *c = fnetdial("tcp", "127.0.0.1:9999"); 7 | fprintf(fnetf(c), "hello!\n"); 8 | ``` 9 | 10 | #### Dial/Listen should be familiar to Plan9'ers and Gophers. 11 | ```c 12 | NetConn* fnetdial(char *proto, char *addr); 13 | NetConn* fnetlisten(char *proto, char *addr); 14 | NetConn* fnetaccept(NetConn*); 15 | char* fneterr(void); 16 | FILE* fnetf(NetConn*); 17 | char* fnetlocaddr(NetConn*); 18 | char* fnetremaddr(NetConn*); 19 | void fnetclose(NetConn*); 20 | ``` 21 | #### Check examples of dialing and listening. 22 | ```bash 23 | cd examples/ 24 | ./compile dial && ./compile listen 25 | # ./listen tcp 127.0.0.1:9999 # ./dial tcp 127.0.0.1:9999 26 | ``` 27 | 28 | ## License 29 | MIT 30 | -------------------------------------------------------------------------------- /examples/broadcast.c: -------------------------------------------------------------------------------- 1 | /* 2 | Run on multiple devices in the same local network to chat with each other. 3 | Messages get broadcast across the entire local network. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include "fnet.h" 10 | 11 | int fork(void); 12 | 13 | int 14 | main(int argc, char **argv) 15 | { 16 | NetConn *send, *recv; 17 | char nick[32], buf[256], msg[256]; 18 | int n, nick_len; 19 | 20 | send = fnetdial("udp", "255.255.255.255:54321"); 21 | if(!send){ 22 | fprintf(stderr, "fnetdial: %s\n", fneterr()); 23 | return -1; 24 | } 25 | recv = fnetlisten("udp", "0.0.0.0:54321"); 26 | if(!recv){ 27 | fprintf(stderr, "fnetlisten: %s\n", fneterr()); 28 | fnetclose(send); 29 | return -1; 30 | } 31 | 32 | do { 33 | printf("Enter nickname: "); 34 | if(fgets(nick, sizeof(nick), stdin)){ 35 | nick[strcspn(nick, "\n")] = '\0'; 36 | } 37 | } while(nick[0] == '\0' || strspn(nick, " \t") == strlen(nick)); 38 | 39 | nick_len = strlen(nick); 40 | 41 | if(fork()){ 42 | while(1){ 43 | printf("> %s: ", nick); 44 | 45 | if(fgets(msg, sizeof(msg), stdin)){ 46 | msg[strcspn(msg, "\n")] = '\0'; 47 | if(msg[0] == '\0' || strspn(msg, " \t") == strlen(msg)) 48 | continue; 49 | 50 | n = snprintf(buf, sizeof(buf), "%s: %s\n", nick, msg); 51 | if(n < 0 || n >= sizeof(buf)) 52 | continue; 53 | 54 | fputs(buf, fnetf(send)); 55 | } 56 | } 57 | }else{ 58 | while(fgets(buf, sizeof(buf), fnetf(recv)) > 0){ 59 | buf[strcspn(buf, "\n")] = '\0'; 60 | if(buf[0] == '\0') 61 | continue; 62 | 63 | if(strncmp(buf, nick, nick_len) == 0 && buf[nick_len] == ':') 64 | continue; 65 | 66 | printf("\n< %s\n> %s: ", buf, nick); 67 | } 68 | } 69 | 70 | fnetclose(send); 71 | fnetclose(recv); 72 | return 0; 73 | } 74 | -------------------------------------------------------------------------------- /examples/compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ $# -eq 0 ]; then 4 | echo "Provide 'dial', 'listen', 'broadcast', 'get'" 5 | exit 1 6 | fi 7 | 8 | if [ "$1" = "dial" ]; then 9 | echo "compiling dial" 10 | cc \ 11 | -I ../. \ 12 | ../fnet.c \ 13 | dial.c -o dial 14 | 15 | elif [ "$1" = "listen" ]; then 16 | echo "compiling listen" 17 | cc \ 18 | -I ../. \ 19 | ../fnet.c \ 20 | listen.c -o listen 21 | 22 | elif [ "$1" = "broadcast" ]; then 23 | echo "compiling broadcast" 24 | cc \ 25 | -I ../. \ 26 | ../fnet.c \ 27 | broadcast.c -o broadcast 28 | 29 | elif [ "$1" = "get" ]; then 30 | echo "compiling get" 31 | cc \ 32 | -I ../. \ 33 | ../fnet.c \ 34 | get.c -o get 35 | 36 | else 37 | echo "Allowed args are: dial, listen, broadcast, get" 38 | fi 39 | -------------------------------------------------------------------------------- /examples/dial.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "fnet.h" 3 | 4 | int fork(void); 5 | 6 | int 7 | main(int argc, char **argv) 8 | { 9 | NetConn *c; 10 | char buf[256]; 11 | 12 | if(argc < 3){ 13 | printf("forgot to pass proto or address? (e.g. \"%s tcp 127.0.0.1:9999\")\n", argv[0]); 14 | return -1; 15 | } 16 | 17 | c = fnetdial(argv[1], argv[2]); 18 | if(!c){ 19 | fprintf(stderr, "fnetdial: %s\n", fneterr()); 20 | return -1; 21 | } 22 | 23 | printf("Connected (%s -> %s)\n", fnetlocaddr(c), fnetremaddr(c)); 24 | 25 | if(fork()){ 26 | while(fgets(buf, sizeof(buf), stdin)){ 27 | fputs(buf, fnetf(c)); 28 | } 29 | }else{ 30 | while(fgets(buf, sizeof(buf), fnetf(c))){ 31 | fputs(buf, stdout); 32 | } 33 | } 34 | 35 | fnetclose(c); 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /examples/get.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "fnet.h" 7 | 8 | int resolvhost(char *hostname, char *buf, size_t n); 9 | 10 | int 11 | main(int argc, char **argv) 12 | { 13 | NetConn *c; 14 | char ip[128], buf[256<<10], *body; 15 | 16 | if (argc < 2){ 17 | printf("forgot to pass link? (e.g. \"%s example.com\")\n", argv[0]); 18 | return -1; 19 | } 20 | 21 | if(resolvhost(argv[1], ip, sizeof(ip)) < 0) { 22 | fprintf(stderr, "resolvhost: can't resolve host\n"); 23 | return -1; 24 | } 25 | 26 | c = fnetdial("tcp", strcat(ip, ":80")); 27 | if(!c){ 28 | fprintf(stderr, "fnetdial: %s\n", fneterr()); 29 | return -1; 30 | } 31 | 32 | fprintf(fnetf(c), 33 | "GET / HTTP/1.1\r\n" 34 | "Host: %s\r\n" 35 | "Connection: close\r\n\r\n", argv[1]); 36 | 37 | fread(buf, sizeof(buf), 1, fnetf(c)); 38 | 39 | body = strstr(buf, "\r\n\r\n"); 40 | if(body){ 41 | body += 4; /* move past "\r\n\r\n" */ 42 | fwrite(buf, 1, body - buf, stderr); /* headers */ 43 | fputs(body, stdout); 44 | } 45 | fnetclose(c); 46 | return 0; 47 | } 48 | 49 | int 50 | resolvhost(char *hostname, char *buf, size_t n) 51 | { 52 | struct addrinfo hints, *res; 53 | void *addr; 54 | 55 | memset(&hints, 0, sizeof(hints)); 56 | hints.ai_family = AF_UNSPEC; 57 | hints.ai_socktype = SOCK_STREAM; 58 | 59 | if(getaddrinfo(hostname, NULL, &hints, &res) != 0){ 60 | return -1; 61 | } 62 | 63 | if(res->ai_family == AF_INET){ 64 | struct sockaddr_in *ipv4 = (struct sockaddr_in *)res->ai_addr; 65 | addr = &(ipv4->sin_addr); 66 | }else{ 67 | struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)res->ai_addr; 68 | addr = &(ipv6->sin6_addr); 69 | } 70 | 71 | if(inet_ntop(res->ai_family, addr, buf, n) == NULL){ 72 | freeaddrinfo(res); 73 | return -1; 74 | } 75 | 76 | freeaddrinfo(res); 77 | return 0; 78 | } 79 | -------------------------------------------------------------------------------- /examples/listen.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "fnet.h" 4 | 5 | int fork(void); 6 | int stream(char **argv); 7 | int dgram(char **argv); 8 | 9 | int 10 | main(int argc, char **argv) 11 | { 12 | if(argc < 3){ 13 | printf("forgot to pass proto or address? (e.g. \"%s tcp 127.0.0.1:9999\")\n", argv[0]); 14 | return -1; 15 | } 16 | if(strstr(argv[1], "udp")){ 17 | return dgram(argv); 18 | }else{ 19 | return stream(argv); 20 | } 21 | return 0; 22 | } 23 | 24 | int 25 | stream(char **argv) 26 | { 27 | NetConn *serv, *client; 28 | char buf[256]; 29 | int n; 30 | 31 | serv = fnetlisten(argv[1], argv[2]); 32 | if(!serv){ 33 | fprintf(stderr, "fnetdial: %s\n", fneterr()); 34 | return -1; 35 | } 36 | 37 | for(;;){ 38 | client = fnetaccept(serv); 39 | if(!client){ 40 | fprintf(stderr, "fnetaccept: %s\n", fneterr()); 41 | continue; 42 | } 43 | printf("Connected (%s <- %s)\n", fnetlocaddr(client), fnetremaddr(client)); 44 | if(fork()){ 45 | fnetclose(client); 46 | }else{ 47 | for(;;){ 48 | n = snprintf(buf, sizeof(buf), "%s: ", fnetremaddr(client)); 49 | if (!fgets(buf + n, sizeof(buf) - n, fnetf(client))) 50 | break; 51 | fputs(buf, stdout); 52 | 53 | fprintf(fnetf(client), "hello %s!\n", fnetremaddr(client)); 54 | } 55 | printf("Closed (%s <- %s)\n", fnetlocaddr(client), fnetremaddr(client)); 56 | return 0; 57 | } 58 | } 59 | fnetclose(serv); 60 | return 0; 61 | } 62 | 63 | int 64 | dgram(char **argv) 65 | { 66 | NetConn *in; 67 | char buf[256]; 68 | int n; 69 | 70 | in = fnetlisten(argv[1], argv[2]); 71 | if(!in){ 72 | fprintf(stderr, "fnetdial: %s\n", fneterr()); 73 | return -1; 74 | } 75 | 76 | for(;;){ 77 | fnetaccept(in); 78 | printf("New Msg (%s <- %s)\n", fnetlocaddr(in), fnetremaddr(in)); 79 | 80 | n = snprintf(buf, sizeof(buf), "%s: ", fnetremaddr(in)); 81 | if(!fgets(buf + n, sizeof(buf) - n, fnetf(in))) 82 | break; 83 | fputs(buf, stdout); 84 | 85 | fprintf(fnetf(in), "hello %s!\n", fnetremaddr(in)); 86 | } 87 | fnetclose(in); 88 | return 0; 89 | } 90 | -------------------------------------------------------------------------------- /fnet.c: -------------------------------------------------------------------------------- 1 | /* started 2025.03.08 - MIT - https://github.com/skullchap/fnet */ 2 | 3 | #define nil ((void*)0) 4 | 5 | #ifndef _WIN32 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | typedef int sockt; 13 | #else 14 | #define _CRT_SECURE_NO_WARNINGS 15 | #define _WINSOCK_DEPRECATED_NO_WARNINGS 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #pragma comment(lib, "ws2_32.lib") 22 | 23 | typedef SOCKET sockt; 24 | #define close closesocket 25 | #endif 26 | 27 | #include 28 | #include 29 | #include 30 | #include "fnet.h" 31 | 32 | typedef unsigned char uchar; 33 | typedef unsigned short ushort; 34 | typedef unsigned int uint; 35 | typedef unsigned long ulong; 36 | 37 | typedef struct NetAddr NetAddr; 38 | typedef struct ParsedV4V6 ParsedV4V6; 39 | typedef enum ConnType ConnType; 40 | typedef enum SockDomain SockDomain; 41 | typedef enum SockType SockType; 42 | 43 | enum 44 | { 45 | ProtoStrMaxLen= 8, 46 | AddrStrMaxLen= 120 47 | }; 48 | 49 | enum ConnType 50 | { 51 | Dial, 52 | Listen 53 | }; 54 | 55 | enum SockDomain 56 | { 57 | Local, 58 | Unix= Local, 59 | IPv4, 60 | IPv6 61 | }; 62 | 63 | enum SockType 64 | { 65 | Stream, 66 | DgRam, 67 | }; 68 | 69 | struct ParsedV4V6 70 | { 71 | char ip[AddrStrMaxLen]; 72 | ushort port; 73 | int isv6; 74 | }; 75 | 76 | struct NetAddr 77 | { 78 | char proto[ProtoStrMaxLen], addr[AddrStrMaxLen]; 79 | }; 80 | 81 | struct NetConn 82 | { 83 | FILE *f; 84 | ConnType conntype; 85 | SockDomain sockdomain; 86 | SockType socktype; 87 | ParsedV4V6 v4v6; 88 | NetAddr remote, local; 89 | sockt fd; 90 | }; 91 | 92 | static NetConn* newnetconn(char *proto, char *addr, ConnType ct); 93 | static int setsockdomaintype(NetConn *c, char *proto, char *addr); 94 | static sockt sockdial(NetConn *c); 95 | static sockt socklisten(NetConn *c); 96 | static sockt sockaccept(NetConn *c); 97 | static int setlocaddr(NetConn *c); 98 | static int setremaddr(NetConn *c); 99 | static FILE* fdfile(sockt fd); 100 | static int parsev4v6(char *addr, ParsedV4V6 *result); 101 | static int setfneterr(char *fmt, ...); 102 | static char* sockerrstr(); 103 | static char* errnostr(); 104 | 105 | static 106 | int 107 | setsockdomaintype(NetConn *c, char *proto, char *addr) 108 | { 109 | int v4v6= 0; 110 | ParsedV4V6 paddr; 111 | 112 | if(strncmp("tcp", proto, ProtoStrMaxLen) == 0){ 113 | v4v6 = 1; 114 | c->socktype = Stream; 115 | }else if(strncmp("udp", proto, ProtoStrMaxLen) == 0){ 116 | v4v6 = 1; 117 | c->socktype = DgRam; 118 | }else if(strncmp("unix", proto, ProtoStrMaxLen) == 0){ 119 | c->sockdomain = Unix; 120 | c->socktype = Stream; 121 | }else{ 122 | setfneterr("unknown proto (%s)", proto); 123 | return -1; 124 | } 125 | if(v4v6){ 126 | if(parsev4v6(addr, &paddr) < 0) 127 | return -1; 128 | if(paddr.isv6) 129 | c->sockdomain = IPv6; 130 | else 131 | c->sockdomain = IPv4; 132 | c->v4v6 = paddr; 133 | } 134 | return 0; 135 | } 136 | 137 | static 138 | NetConn* 139 | newnetconn(char *proto, char *addr, ConnType ct) 140 | { 141 | NetConn *c; 142 | 143 | c = calloc(1, sizeof(NetConn)); 144 | if(!c) 145 | return nil; 146 | 147 | c->conntype = ct; 148 | if(setsockdomaintype(c, proto, addr) < 0){ 149 | free(c); 150 | return nil; 151 | } 152 | strncpy(c->local.proto, proto, ProtoStrMaxLen); 153 | strncpy(c->remote.proto, proto, ProtoStrMaxLen); 154 | if(ct == Dial) 155 | strncpy(c->remote.addr, addr, AddrStrMaxLen); 156 | else 157 | strncpy(c->local.addr, addr, AddrStrMaxLen); 158 | return c; 159 | } 160 | 161 | NetConn* 162 | fnetdial(char *proto, char *addr) 163 | { 164 | NetConn *c = newnetconn(proto, addr, Dial); 165 | if(!c) 166 | return nil; 167 | if((c->fd = sockdial(c)) < 0){ 168 | return nil; 169 | } 170 | if(!(c->f = fdfile(c->fd))){ 171 | close(c->fd); 172 | return nil; 173 | } 174 | return c; 175 | } 176 | 177 | NetConn* 178 | fnetlisten(char *proto, char *addr) 179 | { 180 | NetConn *c = newnetconn(proto, addr, Listen); 181 | if(!c) 182 | return nil; 183 | if((c->fd = socklisten(c)) < 0){ 184 | return nil; 185 | } 186 | if(!(c->f = fdfile(c->fd))){ 187 | close(c->fd); 188 | return nil; 189 | } 190 | return c; 191 | } 192 | 193 | NetConn* 194 | fnetaccept(NetConn *servc) 195 | { 196 | NetConn *clientc; 197 | 198 | if(servc->socktype == DgRam){ 199 | setremaddr(servc); 200 | return servc; 201 | } 202 | 203 | clientc = newnetconn(servc->local.proto, servc->local.addr, Listen); 204 | if(!clientc) 205 | return nil; 206 | 207 | if((clientc->fd = sockaccept(servc)) < 0){ 208 | fnetclose(clientc); 209 | return nil; 210 | } 211 | if(!(clientc->f = fdfile(clientc->fd))){ 212 | close(clientc->fd); 213 | fnetclose(clientc); 214 | return nil; 215 | } 216 | return clientc; 217 | } 218 | 219 | FILE* fnetf(NetConn *c) {return c->f;} 220 | void fnetclose(NetConn *c) {if(c->f)fclose(c->f); free(c);} 221 | 222 | char* 223 | fnetlocaddr(NetConn *c) 224 | { 225 | if(c->conntype == Dial && c->local.addr[0] == '\0') 226 | setlocaddr(c); 227 | return c->local.addr; 228 | } 229 | 230 | char* 231 | fnetremaddr(NetConn *c) 232 | { 233 | if(c->conntype == Listen && c->remote.addr[0] == '\0') 234 | setremaddr(c); 235 | return c->remote.addr; 236 | } 237 | 238 | 239 | typedef struct sockaddr_in SockaddrIn; 240 | typedef struct sockaddr_in6 SockaddrIn6; 241 | typedef struct sockaddr_un SockaddrUnix; 242 | typedef struct sockaddr Sockaddr; 243 | 244 | static 245 | sockt 246 | sockdial(NetConn *c) 247 | { 248 | sockt fd; 249 | SockaddrIn addrV4; 250 | SockaddrIn6 addrV6; 251 | SockaddrUnix addrUnix; 252 | uchar domain, type; 253 | void *paddr; uint addrlen; 254 | 255 | switch(c->sockdomain) 256 | { 257 | case Unix: domain = AF_UNIX; break; 258 | case IPv4: domain = AF_INET; break; 259 | case IPv6: domain = AF_INET6; break; 260 | default: setfneterr("bad socket domain (%d)", c->sockdomain); return -1; 261 | } 262 | switch(c->socktype) 263 | { 264 | case Stream: type = SOCK_STREAM; break; 265 | case DgRam: type = SOCK_DGRAM; break; 266 | default: setfneterr("bad socket type (%d)", c->socktype); return -1; 267 | } 268 | 269 | #ifndef _WIN32 270 | fd = socket(domain, type, 0); 271 | if(fd < 0){ 272 | setfneterr("socket creation failed (%s)", sockerrstr()); 273 | return -1; 274 | } 275 | #else 276 | fd = WSASocketA(domain, type, 0, 0, 0, 0); 277 | if(fd == INVALID_SOCKET){ 278 | setfneterr("socket creation failed (%s)", sockerrstr()); 279 | return -1; 280 | } 281 | #endif 282 | if(c->socktype == DgRam){ 283 | int b=1; 284 | void *p= &b; 285 | setsockopt(fd, SOL_SOCKET, SO_BROADCAST, p, sizeof(b)); 286 | } 287 | 288 | switch(c->sockdomain) 289 | { 290 | case IPv4: 291 | memset(&addrV4, 0, sizeof(addrV4)); 292 | addrV4.sin_family = AF_INET; 293 | addrV4.sin_port = htons(c->v4v6.port); 294 | addrV4.sin_addr.s_addr = inet_addr(c->v4v6.ip); 295 | paddr = &addrV4; 296 | addrlen = sizeof(addrV4); 297 | break; 298 | case IPv6: 299 | memset(&addrV6, 0, sizeof(addrV6)); 300 | addrV6.sin6_family = AF_INET6; 301 | addrV6.sin6_port = htons(c->v4v6.port); 302 | inet_pton(AF_INET6, c->v4v6.ip, &addrV6.sin6_addr); 303 | paddr = &addrV6; 304 | addrlen = sizeof(addrV6); 305 | break; 306 | case Unix: 307 | memset(&addrUnix, 0, sizeof(addrUnix)); 308 | addrUnix.sun_family = AF_UNIX; 309 | strncpy(addrUnix.sun_path, c->remote.addr, sizeof(addrUnix.sun_path) - 1); 310 | paddr = &addrUnix; 311 | addrlen = sizeof(addrUnix); 312 | break; 313 | default: 314 | close(fd); 315 | setfneterr("bad domain (%d)", c->sockdomain); 316 | return -1; 317 | } 318 | 319 | if(connect(fd, paddr, addrlen) < 0){ 320 | close(fd); 321 | setfneterr("connection failed (%s)", sockerrstr()); 322 | return -1; 323 | } 324 | return fd; 325 | } 326 | 327 | static 328 | sockt 329 | socklisten(NetConn *c) 330 | { 331 | sockt fd; 332 | SockaddrIn addrV4; 333 | SockaddrIn6 addrV6; 334 | SockaddrUnix addrUnix; 335 | uchar domain, type; 336 | void *paddr; uint addrlen; 337 | 338 | switch(c->sockdomain) 339 | { 340 | case Unix: domain = AF_UNIX; break; 341 | case IPv4: domain = AF_INET; break; 342 | case IPv6: domain = AF_INET6; break; 343 | default: setfneterr("bad socket domain (%d)", c->sockdomain); return -1; 344 | } 345 | switch(c->socktype) 346 | { 347 | case Stream: type = SOCK_STREAM; break; 348 | case DgRam: type = SOCK_DGRAM; break; 349 | default: setfneterr("bad socket type (%d)", c->socktype); return -1; 350 | } 351 | 352 | #ifndef _WIN32 353 | fd = socket(domain, type, 0); 354 | if(fd < 0){ 355 | setfneterr("socket creation failed (%s)", sockerrstr()); 356 | return -1; 357 | } 358 | #else 359 | fd = WSASocketA(domain, type, 0, 0, 0, 0); 360 | if(fd == INVALID_SOCKET){ 361 | setfneterr("socket creation failed (%s)", sockerrstr()); 362 | return -1; 363 | } 364 | #endif 365 | 366 | switch(c->sockdomain) 367 | { 368 | case IPv4: 369 | memset(&addrV4, 0, sizeof(addrV4)); 370 | addrV4.sin_family = AF_INET; 371 | addrV4.sin_port = htons(c->v4v6.port); 372 | addrV4.sin_addr.s_addr = inet_addr(c->v4v6.ip); 373 | paddr = &addrV4; 374 | addrlen = sizeof(addrV4); 375 | break; 376 | case IPv6: 377 | memset(&addrV6, 0, sizeof(addrV6)); 378 | addrV6.sin6_family = AF_INET6; 379 | addrV6.sin6_port = htons(c->v4v6.port); 380 | inet_pton(AF_INET6, c->v4v6.ip, &addrV6.sin6_addr); 381 | paddr = &addrV6; 382 | addrlen = sizeof(addrV6); 383 | break; 384 | case Unix: 385 | memset(&addrUnix, 0, sizeof(addrUnix)); 386 | addrUnix.sun_family = AF_UNIX; 387 | strncpy(addrUnix.sun_path, c->local.addr, sizeof(addrUnix.sun_path) - 1); 388 | paddr = &addrUnix; 389 | addrlen = sizeof(addrUnix); 390 | break; 391 | default: 392 | close(fd); 393 | setfneterr("bad domain (%d)", c->sockdomain); 394 | return -1; 395 | } 396 | 397 | if(bind(fd, paddr, addrlen) < 0){ 398 | close(fd); 399 | setfneterr("bind failed (%s)", sockerrstr()); 400 | return -1; 401 | } 402 | 403 | if(c->socktype == Stream){ 404 | if(listen(fd, 128) < 0){ 405 | close(fd); 406 | setfneterr("listen failed (%s)", sockerrstr()); 407 | return -1; 408 | } 409 | } 410 | return fd; 411 | } 412 | 413 | sockt 414 | sockaccept(NetConn *c) 415 | { 416 | sockt cfd = accept(c->fd, nil, nil); 417 | #ifndef _WIN32 418 | if(cfd < 0){ 419 | setfneterr("accept failed (%s)", sockerrstr()); 420 | return -1; 421 | } 422 | #else 423 | if(cfd == INVALID_SOCKET){ 424 | setfneterr("accept failed (%s)", sockerrstr()); 425 | return -1; 426 | } 427 | #endif 428 | return cfd; 429 | } 430 | 431 | static 432 | int 433 | setlocaddr(NetConn *c) 434 | { 435 | SockaddrIn addrV4; 436 | SockaddrIn6 addrV6; 437 | void *paddr; uint addrlen; 438 | char buf[AddrStrMaxLen]; 439 | 440 | switch(c->sockdomain) 441 | { 442 | case IPv4: 443 | paddr = &addrV4; 444 | addrlen = sizeof(addrV4); 445 | break; 446 | case IPv6: 447 | paddr = &addrV6; 448 | addrlen = sizeof(addrV6); 449 | break; 450 | case Unix: 451 | strncpy(c->local.addr, c->remote.addr, AddrStrMaxLen); 452 | return 0; 453 | default: 454 | setfneterr("bad domain (%d)", c->sockdomain); 455 | return -1; 456 | } 457 | 458 | if(getsockname(c->fd, paddr, &addrlen) < 0){ 459 | setfneterr("getsockname failed (%s)", sockerrstr()); 460 | return -1; 461 | } 462 | if(c->sockdomain == IPv4){ 463 | if(inet_ntop(AF_INET, &addrV4.sin_addr, buf, sizeof(buf)) == nil){ 464 | setfneterr("inet_ntop failed (%s)", sockerrstr()); 465 | return -1; 466 | } 467 | snprintf(c->local.addr, AddrStrMaxLen, "%s:%d", buf, ntohs(addrV4.sin_port)); 468 | } 469 | if(c->sockdomain == IPv6){ 470 | if(inet_ntop(AF_INET6, &addrV6.sin6_addr, buf, sizeof(buf)) == nil){ 471 | setfneterr("inet_ntop failed (%s)", sockerrstr()); 472 | return -1; 473 | } 474 | snprintf(c->local.addr, AddrStrMaxLen, "%s:%d", buf, ntohs(addrV6.sin6_port)); 475 | } 476 | return 0; 477 | } 478 | 479 | static 480 | int 481 | setremaddr(NetConn *c) 482 | { 483 | SockaddrIn addrV4; 484 | SockaddrIn6 addrV6; 485 | void *paddr; uint addrlen; 486 | char buf[AddrStrMaxLen]; 487 | 488 | switch(c->sockdomain) 489 | { 490 | case IPv4: 491 | paddr = &addrV4; 492 | addrlen = sizeof(addrV4); 493 | break; 494 | case IPv6: 495 | paddr = &addrV6; 496 | addrlen = sizeof(addrV6); 497 | break; 498 | case Unix: 499 | strncpy(c->remote.addr, c->local.addr, AddrStrMaxLen); 500 | return 0; 501 | default: 502 | setfneterr("bad domain (%d)", c->sockdomain); 503 | return -1; 504 | } 505 | 506 | if(c->socktype == Stream){ 507 | if(getpeername(c->fd, paddr, &addrlen) < 0){ 508 | setfneterr("getpeername failed (%s)", sockerrstr()); 509 | return -1; 510 | } 511 | }else{ 512 | char b[1]; 513 | SockaddrIn addr; 514 | addr.sin_family = AF_UNSPEC; 515 | memset(&addr, 0, sizeof(addr)); 516 | connect(c->fd, (Sockaddr*)&addr, sizeof(addr)); 517 | 518 | if(recvfrom(c->fd, b, 1, MSG_PEEK, paddr, &addrlen) < 0){ 519 | setfneterr("recvfrom failed (%s)", sockerrstr()); 520 | return -1; 521 | } 522 | if(connect(c->fd, paddr, addrlen) < 0){ 523 | setfneterr("connection failed (%s)", errnostr()); 524 | return -1; 525 | } 526 | } 527 | 528 | if(c->sockdomain == IPv4){ 529 | if(inet_ntop(AF_INET, &addrV4.sin_addr, buf, sizeof(buf)) == nil){ 530 | setfneterr("inet_ntop failed (%s)", sockerrstr()); 531 | return -1; 532 | } 533 | snprintf(c->remote.addr, AddrStrMaxLen, "%s:%d", buf, ntohs(addrV4.sin_port)); 534 | } 535 | if(c->sockdomain == IPv6){ 536 | if(inet_ntop(AF_INET6, &addrV6.sin6_addr, buf, sizeof(buf)) == nil){ 537 | setfneterr("inet_ntop failed (%s)", sockerrstr()); 538 | return -1; 539 | } 540 | snprintf(c->remote.addr, AddrStrMaxLen, "%s:%d", buf, ntohs(addrV6.sin6_port)); 541 | } 542 | return 0; 543 | } 544 | 545 | #ifndef _WIN32 546 | static 547 | FILE* 548 | fdfile(sockt fd) 549 | { 550 | FILE *f = fdopen(fd, "r+"); 551 | if(f == nil){ 552 | setfneterr("fdopen failed (%s)", errnostr()); 553 | return nil; 554 | } 555 | setvbuf(f, nil, _IOLBF, 0); 556 | return f; 557 | } 558 | #else 559 | static 560 | FILE* 561 | fdfile(sockt sock) 562 | { 563 | int fd; 564 | FILE *f; 565 | 566 | fd = _open_osfhandle((intptr_t)sock, _O_RDWR); 567 | if(fd == -1){ 568 | setfneterr("open_osfhandle failed (%s)", errnostr()); 569 | return nil; 570 | } 571 | 572 | f = _fdopen(fd, "r+"); 573 | if(f == nil){ 574 | setfneterr("fdopen failed (%s)", errnostr()); 575 | return nil; 576 | } 577 | setvbuf(f, nil, _IOLBF, 0); 578 | return f; 579 | } 580 | #endif 581 | 582 | static 583 | int 584 | validate_ipv4(char *ip_str) 585 | { 586 | SockaddrIn sa; 587 | return inet_pton(AF_INET, ip_str, &(sa.sin_addr)) == 1; 588 | } 589 | 590 | static 591 | int 592 | validate_ipv6(char *ip_str) 593 | { 594 | SockaddrIn6 sa6; 595 | return inet_pton(AF_INET6, ip_str, &(sa6.sin6_addr)) == 1; 596 | } 597 | 598 | static 599 | int 600 | parsev4v6(char *addr, ParsedV4V6 *result) 601 | { 602 | char ip[AddrStrMaxLen]; 603 | int port = 0; 604 | char *colon_pos; 605 | char *port_part; 606 | 607 | if(addr[0] == '['){ 608 | char *rbrak = strchr(addr, ']'); 609 | if(!rbrak){ 610 | setfneterr("invalid IPv6 format or missing port"); 611 | return -1; 612 | } 613 | colon_pos = strchr(rbrak, ':'); 614 | if(!colon_pos){ 615 | setfneterr("missing port for IPv6"); 616 | return -1; 617 | } 618 | strncpy(ip, addr + 1, rbrak - addr - 1); 619 | ip[rbrak - addr - 1] = '\0'; 620 | port_part = colon_pos + 1; 621 | }else{ 622 | colon_pos = strrchr(addr, ':'); 623 | if(!colon_pos){ 624 | setfneterr("missing port for IP address"); 625 | return -1; 626 | } 627 | strncpy(ip, addr, colon_pos - addr); 628 | ip[colon_pos - addr] = '\0'; 629 | port_part = colon_pos + 1; 630 | } 631 | 632 | if(validate_ipv4(ip)){ 633 | result->isv6 = 0; 634 | }else if(validate_ipv6(ip)){ 635 | result->isv6 = 1; 636 | }else{ 637 | setfneterr("invalid IP address"); 638 | return -1; 639 | } 640 | 641 | if(sscanf(port_part, "%d", &port) != 1 || port < 1 || port > 65535){ 642 | setfneterr("invalid port"); 643 | return -1; 644 | } 645 | 646 | strncpy(result->ip, ip, AddrStrMaxLen); 647 | result->port = port; 648 | return 0; 649 | } 650 | 651 | 652 | /* error.c */ 653 | 654 | #include 655 | #include 656 | 657 | # if __STDC_VERSION__ <= 202311L /* in C23 thread_local is a builtin keyword */ 658 | /* https://stackoverflow.com/a/18298965 */ 659 | # ifndef thread_local 660 | # if __STDC_VERSION__ >= 201112L && !defined __STDC_NO_THREADS__ 661 | # define thread_local _Thread_local 662 | # elif defined _WIN32 && ( \ 663 | defined _MSC_VER || \ 664 | defined __ICL || \ 665 | defined __DMC__ || \ 666 | defined __BORLANDC__ ) 667 | # define thread_local __declspec(thread) 668 | /* note that ICC (linux) and Clang are covered by __GNUC__ */ 669 | # elif defined __GNUC__ || \ 670 | defined __SUNPRO_C || \ 671 | defined __xlC__ 672 | # define thread_local __thread 673 | # else 674 | # warning "Cannot define thread_local" 675 | # define thread_local 676 | # define no_thread_local 677 | # endif 678 | # endif 679 | # endif 680 | 681 | static thread_local char estr[256]; 682 | 683 | char* fneterr(void) {return estr;} 684 | char* errnostr(void) {return strerror(errno);} 685 | 686 | char* 687 | sockerrstr(void) 688 | { 689 | #ifndef _WIN32 690 | return strerror(errno); 691 | #else 692 | int err; 693 | static thread_local char msgbuf[256]; 694 | 695 | msgbuf[0] = '\0'; 696 | err = WSAGetLastError(); 697 | FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 698 | nil, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 699 | msgbuf, sizeof(msgbuf), nil); 700 | 701 | size_t len = strlen(msgbuf); 702 | while(len > 0 && (msgbuf[len - 1] == '\r' || msgbuf[len - 1] == '\n')){ 703 | msgbuf[--len] = '\0'; 704 | } 705 | 706 | return msgbuf; 707 | #endif 708 | } 709 | 710 | int 711 | setfneterr(char *fmt, ...) 712 | { 713 | va_list args; 714 | va_start(args, fmt); 715 | if(vsnprintf(estr, sizeof(estr), fmt, args) < 0){ 716 | static char emsg[] = "setfneterr(): vsnprintf error"; 717 | memcpy(estr, emsg, sizeof(emsg)); 718 | } 719 | va_end(args); 720 | return 0; 721 | } 722 | 723 | int 724 | fnetinit(void) 725 | { 726 | #ifdef _WIN32 727 | WSADATA wsaData; 728 | if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0){ 729 | setfneterr("WSAStartup failed (%s)", sockerrstr()); 730 | return -1; 731 | } 732 | #endif 733 | return 0; 734 | } 735 | 736 | int 737 | fnetcleanup(void) 738 | { 739 | #ifdef _WIN32 740 | return WSACleanup(); 741 | #endif 742 | return 0; 743 | } 744 | -------------------------------------------------------------------------------- /fnet.h: -------------------------------------------------------------------------------- 1 | /* started 2025.03.08 - MIT - https://github.com/skullchap/fnet */ 2 | 3 | typedef struct NetConn NetConn; 4 | 5 | NetConn* fnetdial(char *proto, char *addr); 6 | NetConn* fnetlisten(char *proto, char *addr); 7 | NetConn* fnetaccept(NetConn*); 8 | char* fneterr(void); 9 | FILE* fnetf(NetConn*); 10 | char* fnetlocaddr(NetConn*); 11 | char* fnetremaddr(NetConn*); 12 | void fnetclose(NetConn*); 13 | 14 | /* needed for windows */ 15 | int fnetinit(void); 16 | int fnetcleanup(void); 17 | --------------------------------------------------------------------------------