socket_util.c (10738B)
1 #include "common/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] ? (strcmp(host, "*") == 0 ? NULL : 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' || strcmp(host, "*") == 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] ? (strcmp(host, "*") == 0 ? NULL : 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' || strcmp(host, "*") == 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 }