socket-util.c

Socket helper utils
git clone git://git.finwo.net/lib/socket-util.c
Log | Files | Refs | README | LICENSE

socket-util.c (13971B)


      1 #include "socket-util.h"
      2 
      3 #include <arpa/inet.h>
      4 #include <errno.h>
      5 #include <fcntl.h>
      6 #include <grp.h>
      7 #include <netdb.h>
      8 #include <netinet/in.h>
      9 #include <pwd.h>
     10 #include <stdio.h>
     11 #include <stdlib.h>
     12 #include <string.h>
     13 #include <sys/socket.h>
     14 #include <sys/stat.h>
     15 #include <sys/un.h>
     16 #include <unistd.h>
     17 
     18 #include "rxi/log.h"
     19 
     20 int set_socket_nonblocking(int fd, int nonblock) {
     21   int flags = fcntl(fd, F_GETFL, 0);
     22   if (flags < 0) return -1;
     23   if (nonblock)
     24     flags |= O_NONBLOCK;
     25   else
     26     flags &= ~O_NONBLOCK;
     27   return fcntl(fd, F_SETFL, flags) == 0 ? 0 : -1;
     28 }
     29 
     30 int *tcp_listen(const char *addr, const char *default_host, const char *default_port) {
     31   char host[256] = "";
     32   char port[32]  = "";
     33 
     34   if (default_host && default_host[0]) {
     35     snprintf(host, sizeof(host), "%s", default_host);
     36   }
     37   if (default_port && default_port[0]) {
     38     snprintf(port, sizeof(port), "%s", default_port);
     39   }
     40 
     41   if (!addr || !addr[0]) {
     42     if (!host[0] || !port[0]) {
     43       log_error("tcp_listen: empty address and no defaults");
     44       return NULL;
     45     }
     46   } else if (addr[0] == '[') {
     47     const char *close_bracket = strchr(addr, ']');
     48     if (!close_bracket) {
     49       log_error("tcp_listen: invalid IPv6 format: missing ']'");
     50       return NULL;
     51     }
     52     size_t hlen = (size_t)(close_bracket - addr - 1);
     53     if (hlen > 0) {
     54       if (hlen >= sizeof(host)) hlen = sizeof(host) - 1;
     55       memcpy(host, addr + 1, hlen);
     56       host[hlen] = '\0';
     57     }
     58     const char *colon = close_bracket + 1;
     59     if (*colon == ':') {
     60       snprintf(port, sizeof(port), "%s", colon + 1);
     61     } else if (*colon != '\0') {
     62       log_error("tcp_listen: invalid IPv6 format: expected ':' after ']'");
     63       return NULL;
     64     }
     65   } else {
     66     int         leading_colon = (addr[0] == ':');
     67     const char *p             = leading_colon ? addr + 1 : addr;
     68     int         is_port_only  = 1;
     69     for (const char *q = p; *q; q++) {
     70       if (*q < '0' || *q > '9') {
     71         is_port_only = 0;
     72         break;
     73       }
     74     }
     75 
     76     const char *colon = strrchr(addr, ':');
     77     if (leading_colon && is_port_only) {
     78       snprintf(port, sizeof(port), "%s", p);
     79     } else if (is_port_only) {
     80       if (default_host && default_host[0]) {
     81         snprintf(host, sizeof(host), "%s", default_host);
     82       }
     83       snprintf(port, sizeof(port), "%s", p);
     84     } else if (colon) {
     85       size_t hlen = (size_t)(colon - addr);
     86       if (hlen > 0) {
     87         if (hlen >= sizeof(host)) hlen = sizeof(host) - 1;
     88         memcpy(host, addr, hlen);
     89         host[hlen] = '\0';
     90       }
     91       snprintf(port, sizeof(port), "%s", colon + 1);
     92     } else {
     93       snprintf(host, sizeof(host), "%s", addr);
     94     }
     95   }
     96 
     97   if (!port[0]) {
     98     log_error("tcp_listen: no port specified");
     99     return NULL;
    100   }
    101 
    102   struct addrinfo hints, *res = NULL;
    103   memset(&hints, 0, sizeof(hints));
    104   hints.ai_family   = AF_UNSPEC;
    105   hints.ai_socktype = SOCK_STREAM;
    106   hints.ai_flags    = AI_PASSIVE;
    107   if (getaddrinfo(host[0] ? host : NULL, port, &hints, &res) != 0 || !res) {
    108     log_error("tcp_listen: getaddrinfo failed for %s:%s", host, port);
    109     return NULL;
    110   }
    111 
    112   int *fds = malloc(sizeof(int) * 3);
    113   if (!fds) {
    114     freeaddrinfo(res);
    115     return NULL;
    116   }
    117   fds[0] = 0;
    118 
    119   int              listen_all = (host[0] == '\0');
    120   struct addrinfo *p;
    121 
    122   for (p = res; p; p = p->ai_next) {
    123     if (p->ai_family == AF_INET) {
    124       int fd = socket(AF_INET, SOCK_STREAM, 0);
    125       if (fd < 0) continue;
    126       int opt = 1;
    127       setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    128       if (bind(fd, p->ai_addr, p->ai_addrlen) == 0 && listen(fd, 8) == 0) {
    129         set_socket_nonblocking(fd, 1);
    130         fds[++fds[0]] = fd;
    131       } else {
    132         close(fd);
    133       }
    134     } else if (p->ai_family == AF_INET6 && listen_all) {
    135       int fd = socket(AF_INET6, SOCK_STREAM, 0);
    136       if (fd < 0) continue;
    137       int opt = 1;
    138       setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    139       setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
    140       if (bind(fd, p->ai_addr, p->ai_addrlen) == 0 && listen(fd, 8) == 0) {
    141         set_socket_nonblocking(fd, 1);
    142         fds[++fds[0]] = fd;
    143       } else {
    144         close(fd);
    145       }
    146     }
    147   }
    148 
    149   freeaddrinfo(res);
    150 
    151   if (fds[0] == 0) {
    152     log_error("tcp_listen: failed to bind to %s:%s", host, port);
    153     free(fds);
    154     return NULL;
    155   }
    156 
    157   return fds;
    158 }
    159 
    160 int *udp_recv(const char *addr, const char *default_host, const char *default_port) {
    161   char host[256] = "";
    162   char port[32]  = "";
    163 
    164   if (default_host && default_host[0]) {
    165     snprintf(host, sizeof(host), "%s", default_host);
    166   }
    167   if (default_port && default_port[0]) {
    168     snprintf(port, sizeof(port), "%s", default_port);
    169   }
    170 
    171   if (!addr || !addr[0]) {
    172     if (!host[0] || !port[0]) {
    173       log_error("udp_recv: empty address and no defaults");
    174       return NULL;
    175     }
    176   } else if (addr[0] == '[') {
    177     const char *close_bracket = strchr(addr, ']');
    178     if (!close_bracket) {
    179       log_error("udp_recv: invalid IPv6 format: missing ']'");
    180       return NULL;
    181     }
    182     size_t hlen = (size_t)(close_bracket - addr - 1);
    183     if (hlen > 0) {
    184       if (hlen >= sizeof(host)) hlen = sizeof(host) - 1;
    185       memcpy(host, addr + 1, hlen);
    186       host[hlen] = '\0';
    187     }
    188     const char *colon = close_bracket + 1;
    189     if (*colon == ':') {
    190       snprintf(port, sizeof(port), "%s", colon + 1);
    191     } else if (*colon != '\0') {
    192       log_error("udp_recv: invalid IPv6 format: expected ':' after ']'");
    193       return NULL;
    194     }
    195   } else {
    196     int         leading_colon = (addr[0] == ':');
    197     const char *p             = leading_colon ? addr + 1 : addr;
    198     int         is_port_only  = 1;
    199     for (const char *q = p; *q; q++) {
    200       if (*q < '0' || *q > '9') {
    201         is_port_only = 0;
    202         break;
    203       }
    204     }
    205 
    206     const char *colon = strrchr(addr, ':');
    207     if (leading_colon && is_port_only) {
    208       snprintf(port, sizeof(port), "%s", p);
    209     } else if (is_port_only) {
    210       if (default_host && default_host[0]) {
    211         snprintf(host, sizeof(host), "%s", default_host);
    212       }
    213       snprintf(port, sizeof(port), "%s", p);
    214     } else if (colon) {
    215       size_t hlen = (size_t)(colon - addr);
    216       if (hlen > 0) {
    217         if (hlen >= sizeof(host)) hlen = sizeof(host) - 1;
    218         memcpy(host, addr, hlen);
    219         host[hlen] = '\0';
    220       }
    221       snprintf(port, sizeof(port), "%s", colon + 1);
    222     } else {
    223       snprintf(host, sizeof(host), "%s", addr);
    224     }
    225   }
    226 
    227   if (!port[0]) {
    228     log_error("udp_recv: no port specified");
    229     return NULL;
    230   }
    231 
    232   struct addrinfo hints, *res = NULL;
    233   memset(&hints, 0, sizeof(hints));
    234   hints.ai_family   = AF_UNSPEC;
    235   hints.ai_socktype = SOCK_DGRAM;
    236   hints.ai_flags    = AI_PASSIVE;
    237   if (getaddrinfo(host[0] ? host : NULL, port, &hints, &res) != 0 || !res) {
    238     log_error("udp_recv: getaddrinfo failed for %s:%s", host, port);
    239     return NULL;
    240   }
    241 
    242   int *fds = malloc(sizeof(int) * 3);
    243   if (!fds) {
    244     freeaddrinfo(res);
    245     return NULL;
    246   }
    247   fds[0] = 0;
    248 
    249   int              listen_all = (host[0] == '\0');
    250   struct addrinfo *p;
    251 
    252   for (p = res; p; p = p->ai_next) {
    253     if (p->ai_family == AF_INET) {
    254       int fd = socket(AF_INET, SOCK_DGRAM, 0);
    255       if (fd < 0) continue;
    256       int opt = 1;
    257       setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    258       if (bind(fd, p->ai_addr, p->ai_addrlen) == 0) {
    259         set_socket_nonblocking(fd, 1);
    260         fds[++fds[0]] = fd;
    261       } else {
    262         close(fd);
    263       }
    264     } else if (p->ai_family == AF_INET6 && listen_all) {
    265       int fd = socket(AF_INET6, SOCK_DGRAM, 0);
    266       if (fd < 0) continue;
    267       int opt = 1;
    268       setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    269       setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
    270       if (bind(fd, p->ai_addr, p->ai_addrlen) == 0) {
    271         set_socket_nonblocking(fd, 1);
    272         fds[++fds[0]] = fd;
    273       } else {
    274         close(fd);
    275       }
    276     }
    277   }
    278 
    279   freeaddrinfo(res);
    280 
    281   if (fds[0] == 0) {
    282     log_error("udp_recv: failed to bind to %s:%s", host, port);
    283     free(fds);
    284     return NULL;
    285   }
    286 
    287   return fds;
    288 }
    289 
    290 int *unix_listen(const char *path, int sock_type, const char *owner) {
    291   if (!path || !path[0]) {
    292     log_error("unix_listen: empty path");
    293     return NULL;
    294   }
    295 
    296   char *path_copy = strdup(path);
    297   if (!path_copy) {
    298     return NULL;
    299   }
    300 
    301   char *dir = strdup(path);
    302   if (!dir) {
    303     free(path_copy);
    304     return NULL;
    305   }
    306 
    307   char *last_slash = strrchr(dir, '/');
    308   if (last_slash && last_slash != dir) {
    309     *last_slash = '\0';
    310     if (strlen(dir) > 0) {
    311       mkdir(dir, 0755);
    312     }
    313   } else if (!last_slash) {
    314     dir[0] = '.';
    315     dir[1] = '\0';
    316   }
    317   free(dir);
    318 
    319   unlink(path_copy);
    320 
    321   int *fds = malloc(sizeof(int) * 2);
    322   if (!fds) {
    323     free(path_copy);
    324     return NULL;
    325   }
    326   fds[0] = 0;
    327 
    328   int fd = socket(AF_UNIX, sock_type, 0);
    329   if (fd < 0) {
    330     log_error("unix_listen: socket failed: %s", strerror(errno));
    331     free(path_copy);
    332     free(fds);
    333     return NULL;
    334   }
    335 
    336   struct sockaddr_un addr;
    337   memset(&addr, 0, sizeof(addr));
    338   addr.sun_family = AF_UNIX;
    339   strncpy(addr.sun_path, path_copy, sizeof(addr.sun_path) - 1);
    340 
    341   if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
    342     log_error("unix_listen: bind failed: %s", strerror(errno));
    343     close(fd);
    344     free(path_copy);
    345     free(fds);
    346     return NULL;
    347   }
    348 
    349   if (owner && owner[0]) {
    350     uid_t uid        = -1;
    351     gid_t gid        = -1;
    352     char *owner_copy = strdup(owner);
    353     if (owner_copy) {
    354       char *colon = strchr(owner_copy, ':');
    355       if (colon) {
    356         *colon = '\0';
    357         colon++;
    358         if (colon[0]) {
    359           struct passwd *pw = getpwnam(owner_copy);
    360           if (pw) {
    361             uid              = pw->pw_uid;
    362             struct group *gr = getgrnam(colon);
    363             if (gr) {
    364               gid = gr->gr_gid;
    365             }
    366           }
    367         }
    368       } else {
    369         struct passwd *pw = getpwnam(owner_copy);
    370         if (pw) {
    371           uid = pw->pw_uid;
    372           gid = pw->pw_gid;
    373         }
    374       }
    375       free(owner_copy);
    376     }
    377     if (uid != (uid_t)-1 || gid != (gid_t)-1) {
    378       if (fchown(fd, uid, gid) < 0) {
    379         log_error("unix_listen: fchown failed: %s", strerror(errno));
    380         close(fd);
    381         unlink(path_copy);
    382         free(path_copy);
    383         free(fds);
    384         return NULL;
    385       }
    386     }
    387   }
    388 
    389   if (sock_type == SOCK_STREAM) {
    390     if (listen(fd, 8) < 0) {
    391       log_error("unix_listen: listen failed: %s", strerror(errno));
    392       close(fd);
    393       unlink(path_copy);
    394       free(path_copy);
    395       free(fds);
    396       return NULL;
    397     }
    398   }
    399 
    400   set_socket_nonblocking(fd, 1);
    401 
    402   fds[++fds[0]] = fd;
    403 
    404   free(path_copy);
    405   return fds;
    406 }
    407 
    408 int *merge_fd_arrays(int **arrays, int count) {
    409   if (!arrays || count <= 0) {
    410     return NULL;
    411   }
    412 
    413   int total_count = 0;
    414   for (int i = 0; i < count; i++) {
    415     if (arrays[i] && arrays[i][0] > 0) {
    416       total_count += arrays[i][0];
    417     }
    418   }
    419 
    420   if (total_count == 0) {
    421     for (int i = 0; i < count; i++) {
    422       free(arrays[i]);
    423     }
    424     return NULL;
    425   }
    426 
    427   int *merged = malloc(sizeof(int) * (total_count + 1));
    428   if (!merged) {
    429     for (int i = 0; i < count; i++) {
    430       free(arrays[i]);
    431     }
    432     return NULL;
    433   }
    434 
    435   merged[0] = 0;
    436   int idx   = 1;
    437 
    438   for (int i = 0; i < count; i++) {
    439     if (arrays[i] && arrays[i][0] > 0) {
    440       for (int j = 1; j <= arrays[i][0]; j++) {
    441         merged[idx++] = arrays[i][j];
    442       }
    443       merged[0] += arrays[i][0];
    444     }
    445     free(arrays[i]);
    446   }
    447 
    448   return merged;
    449 }
    450 
    451 void sockaddr_to_string(const struct sockaddr *addr, char *buf, size_t buf_size) {
    452   if (!addr || !buf || buf_size == 0) return;
    453 
    454   if (addr->sa_family == AF_INET) {
    455     struct sockaddr_in *sin = (struct sockaddr_in *)addr;
    456     inet_ntop(AF_INET, &sin->sin_addr, buf, buf_size);
    457     size_t len = strlen(buf);
    458     if (buf_size - len > 6) {
    459       snprintf(buf + len, buf_size - len, ":%d", ntohs(sin->sin_port));
    460     }
    461   } else if (addr->sa_family == AF_INET6) {
    462     struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr;
    463     buf[0]                    = '[';
    464     inet_ntop(AF_INET6, &sin6->sin6_addr, buf + 1, (socklen_t)(buf_size - 1));
    465     size_t len = strlen(buf);
    466     if (buf_size - len > 6) {
    467       snprintf(buf + len, buf_size - len, "]:%d", ntohs(sin6->sin6_port));
    468     }
    469   } else {
    470     buf[0] = '\0';
    471   }
    472 }
    473 
    474 int string_to_sockaddr(const char *str, struct sockaddr_storage *addr) {
    475   if (!str || !addr) return -1;
    476   memset(addr, 0, sizeof(*addr));
    477 
    478   const char *port_str = strrchr(str, ':');
    479   if (!port_str) return -1;
    480 
    481   char   host[256];
    482   size_t host_len = (size_t)(port_str - str);
    483   if (host_len >= sizeof(host)) return -1;
    484   memcpy(host, str, host_len);
    485   host[host_len] = '\0';
    486 
    487   int port = atoi(port_str + 1);
    488   if (port <= 0 || port > 65535) return -1;
    489 
    490   if (host[0] == '[' && host[host_len - 1] == ']') {
    491     host[host_len - 1]        = '\0';
    492     struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr;
    493     sin6->sin6_family         = AF_INET6;
    494     sin6->sin6_port           = htons((uint16_t)port);
    495     if (inet_pton(AF_INET6, host + 1, &sin6->sin6_addr) != 1) return -1;
    496   } else {
    497     struct sockaddr_in *sin = (struct sockaddr_in *)addr;
    498     sin->sin_family         = AF_INET;
    499     sin->sin_port           = htons((uint16_t)port);
    500     if (inet_pton(AF_INET, host, &sin->sin_addr) != 1) return -1;
    501   }
    502 
    503   return 0;
    504 }
    505 
    506 int sockaddr_equal(const struct sockaddr *a, const struct sockaddr *b) {
    507   if (!a || !b || a->sa_family != b->sa_family) return 0;
    508 
    509   if (a->sa_family == AF_INET) {
    510     struct sockaddr_in *sin_a = (struct sockaddr_in *)a;
    511     struct sockaddr_in *sin_b = (struct sockaddr_in *)b;
    512     return sin_a->sin_addr.s_addr == sin_b->sin_addr.s_addr && sin_a->sin_port == sin_b->sin_port;
    513   } else if (a->sa_family == AF_INET6) {
    514     struct sockaddr_in6 *sin6_a = (struct sockaddr_in6 *)a;
    515     struct sockaddr_in6 *sin6_b = (struct sockaddr_in6 *)b;
    516     return memcmp(&sin6_a->sin6_addr, &sin6_b->sin6_addr, sizeof(sin6_a->sin6_addr)) == 0 &&
    517            sin6_a->sin6_port == sin6_b->sin6_port;
    518   }
    519   return 0;
    520 }