/* * Copyright (C) 2024 Jo-Philipp Wich * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /** * # Socket Module * * The `socket` module provides functions for interacting with sockets. * * Functions can be individually imported and directly accessed using the * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#named_import named import} * syntax: * * ```javascript * import { AF_INET, SOCK_STREAM, create as socket } from 'socket'; * * let sock = socket(AF_INET, SOCK_STREAM, 0); * sock.connect('192.168.1.1', 80); * sock.send(…); * sock.recv(…); * sock.close(); * ``` * * Alternatively, the module namespace can be imported * using a wildcard import statement: * * ```javascript * import * as socket from 'socket'; * * let sock = socket.create(socket.AF_INET, socket.SOCK_STREAM, 0); * sock.connect('192.168.1.1', 80); * sock.send(…); * sock.recv(…); * sock.close(); * ``` * * Additionally, the socket module namespace may also be imported by invoking * the `ucode` interpreter with the `-lsocket` switch. * * @module socket */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ucode/module.h" #include "ucode/platform.h" #if defined(__linux__) # include #endif #if defined(__APPLE__) # include # define SOCK_NONBLOCK (1 << 16) # define SOCK_CLOEXEC (1 << 17) #endif #ifndef NI_IDN # define NI_IDN 0 #endif #ifndef AI_IDN # define AI_IDN 0 #endif #ifndef AI_CANONIDN # define AI_CANONIDN 0 #endif #define ok_return(expr) do { set_error(0, NULL); return (expr); } while(0) #define err_return(err, ...) do { set_error(err, __VA_ARGS__); return NULL; } while(0) static struct { int code; char *msg; } last_error; __attribute__((format(printf, 2, 3))) static void set_error(int errcode, const char *fmt, ...) { va_list ap; free(last_error.msg); last_error.code = errcode; last_error.msg = NULL; if (fmt) { va_start(ap, fmt); xvasprintf(&last_error.msg, fmt, ap); va_end(ap); } } static char * arg_type_(uc_type_t type) { switch (type) { case UC_INTEGER: return "an integer value"; case UC_BOOLEAN: return "a boolean value"; case UC_STRING: return "a string value"; case UC_DOUBLE: return "a double value"; case UC_ARRAY: return "an array"; case UC_OBJECT: return "an object"; case UC_REGEXP: return "a regular expression"; case UC_CLOSURE: return "a function"; case UC_RESOURCE: return "a resource value"; default: return "the expected type"; } } static bool args_get_(uc_vm_t *vm, size_t nargs, int *fdptr, ...) { const char *name, *rtype = NULL; uc_value_t **ptr, *arg; uc_type_t type, t; size_t index = 0; int *sockfd; va_list ap; bool opt; if (fdptr) { sockfd = uc_fn_this("socket"); if (!sockfd || *sockfd == -1) err_return(EBADF, "Invalid socket context"); *fdptr = *sockfd; } va_start(ap, fdptr); while (true) { name = va_arg(ap, const char *); if (!name) break; arg = uc_fn_arg(index++); type = va_arg(ap, uc_type_t); opt = va_arg(ap, int); ptr = va_arg(ap, uc_value_t **); if (type == UC_RESOURCE) { rtype = name; name = strrchr(rtype, '.'); name = name ? name + 1 : rtype; if (arg && !ucv_resource_dataptr(arg, rtype)) err_return(EINVAL, "Argument %s is not a %s resource", name, rtype); } if (!opt && !arg) err_return(EINVAL, "Argument %s is required", name); t = ucv_type(arg); if (t == UC_CFUNCTION) t = UC_CLOSURE; if (arg && type != UC_NULL && t != type) err_return(EINVAL, "Argument %s is not %s", name, arg_type_(type)); *ptr = arg; } va_end(ap); ok_return(true); } #define args_get(vm, nargs, fdptr, ...) do { \ if (!args_get_(vm, nargs, fdptr, ##__VA_ARGS__, NULL)) \ return NULL; \ } while(0) static void strbuf_free(uc_stringbuf_t *sb) { printbuf_free(sb); } static bool strbuf_grow(uc_stringbuf_t *sb, size_t size) { if (size > 0) { if (printbuf_memset(sb, sizeof(uc_string_t) + size - 1, '\0', 1)) err_return(ENOMEM, "Out of memory"); } return true; } static char * strbuf_data(uc_stringbuf_t *sb) { return sb->buf + sizeof(uc_string_t); } static size_t strbuf_size(uc_stringbuf_t *sb) { return (size_t)sb->bpos - sizeof(uc_string_t); } static uc_value_t * strbuf_finish(uc_stringbuf_t **sb, size_t final_size) { uc_string_t *us = (uc_string_t *)(*sb)->buf; size_t buffer_size = strbuf_size(*sb); if (final_size > buffer_size) final_size = buffer_size; free(*sb); *sb = NULL; us = xrealloc(us, sizeof(uc_string_t) + final_size + 1); us->length = final_size; us->str[us->length] = 0; return &us->header; } static uc_stringbuf_t * strbuf_alloc(size_t size) { uc_stringbuf_t *sb = ucv_stringbuf_new(); if (!strbuf_grow(sb, size)) { printbuf_free(sb); return NULL; } return sb; } #if defined(__linux__) static uc_value_t * hwaddr_to_uv(uint8_t *addr, size_t alen) { char buf[sizeof("FF:FF:FF:FF:FF:FF:FF:FF")], *p = buf; const char *hex = "0123456789ABCDEF"; for (size_t i = 0; i < alen && i < 8; i++) { if (i) *p++ = ':'; *p++ = hex[addr[i] / 16]; *p++ = hex[addr[i] % 16]; } return ucv_string_new(buf); } static bool uv_to_hwaddr(uc_value_t *addr, uint8_t *out, size_t *outlen) { const char *p; size_t len; memset(out, 0, 8); *outlen = 0; if (ucv_type(addr) != UC_STRING) goto err; len = ucv_string_length(addr); p = ucv_string_get(addr); while (len > 0 && isxdigit(*p) && *outlen < 8) { uint8_t n = (*p > '9') ? 10 + (*p|32) - 'a' : *p - '0'; p++, len--; if (len > 0 && isxdigit(*p)) { n = n * 16 + ((*p > '9') ? 10 + (*p|32) - 'a' : *p - '0'); p++, len--; } if (len > 0 && (*p == ':' || *p == '-' || *p == '.')) p++, len--; out[(*outlen)++] = n; } if (len == 0 || *p == 0) return true; err: err_return(EINVAL, "Invalid hardware address"); } #endif static bool sockaddr_to_uv(struct sockaddr_storage *ss, uc_value_t *addrobj) { char *ifname, addrstr[INET6_ADDRSTRLEN]; struct sockaddr_in6 *s6; struct sockaddr_in *s4; struct sockaddr_un *su; #if defined(__linux__) struct sockaddr_ll *sl; #endif ucv_object_add(addrobj, "family", ucv_uint64_new(ss->ss_family)); switch (ss->ss_family) { case AF_INET6: s6 = (struct sockaddr_in6 *)ss; inet_ntop(AF_INET6, &s6->sin6_addr, addrstr, sizeof(addrstr)); ucv_object_add(addrobj, "address", ucv_string_new(addrstr)); ucv_object_add(addrobj, "port", ucv_uint64_new(ntohs(s6->sin6_port))); ucv_object_add(addrobj, "flowinfo", ucv_uint64_new(ntohl(s6->sin6_flowinfo))); if (s6->sin6_scope_id) { ifname = if_indextoname(s6->sin6_scope_id, addrstr); if (ifname) ucv_object_add(addrobj, "interface", ucv_string_new(ifname)); else ucv_object_add(addrobj, "interface", ucv_uint64_new(s6->sin6_scope_id)); } return true; case AF_INET: s4 = (struct sockaddr_in *)ss; inet_ntop(AF_INET, &s4->sin_addr, addrstr, sizeof(addrstr)); ucv_object_add(addrobj, "address", ucv_string_new(addrstr)); ucv_object_add(addrobj, "port", ucv_uint64_new(ntohs(s4->sin_port))); return true; case AF_UNIX: su = (struct sockaddr_un *)ss; ucv_object_add(addrobj, "path", ucv_string_new(su->sun_path)); return true; #if defined(__linux__) case AF_PACKET: sl = (struct sockaddr_ll *)ss; ucv_object_add(addrobj, "protocol", ucv_uint64_new(ntohs(sl->sll_protocol))); ifname = (sl->sll_ifindex > 0) ? if_indextoname(sl->sll_ifindex, addrstr) : NULL; if (ifname) ucv_object_add(addrobj, "interface", ucv_string_new(ifname)); else if (sl->sll_ifindex != 0) ucv_object_add(addrobj, "interface", ucv_int64_new(sl->sll_ifindex)); ucv_object_add(addrobj, "hardware_type", ucv_uint64_new(sl->sll_hatype)); ucv_object_add(addrobj, "packet_type", ucv_uint64_new(sl->sll_pkttype)); ucv_object_add(addrobj, "address", hwaddr_to_uv(sl->sll_addr, sl->sll_halen)); return true; #endif } return false; } static bool parse_addr(char *addr, struct sockaddr_storage *ss) { bool v6 = (ss->ss_family == 0 || ss->ss_family == AF_INET6); bool v4 = (ss->ss_family == 0 || ss->ss_family == AF_INET); struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)ss; struct sockaddr_in *s4 = (struct sockaddr_in *)ss; unsigned long n; char *scope, *e; if (v6 && (scope = strchr(addr, '%')) != NULL) { *scope++ = 0; n = strtoul(scope, &e, 10); if (e == scope || *e != 0) { n = if_nametoindex(scope); if (n == 0) err_return(errno, "Unable to resolve interface %s", scope); } if (inet_pton(AF_INET6, addr, &s6->sin6_addr) != 1) err_return(errno, "Invalid IPv6 address"); s6->sin6_family = AF_INET6; s6->sin6_scope_id = n; return true; } else if (v6 && inet_pton(AF_INET6, addr, &s6->sin6_addr) == 1) { s6->sin6_family = AF_INET6; return true; } else if (v4 && inet_pton(AF_INET, addr, &s4->sin_addr) == 1) { s4->sin_family = AF_INET; return true; } err_return(EINVAL, "Unable to parse IP address"); } static bool uv_to_sockaddr(uc_value_t *addr, struct sockaddr_storage *ss, socklen_t *slen) { char *s, *p, addrstr[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255%2147483648")]; struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)ss; struct sockaddr_in *s4 = (struct sockaddr_in *)ss; struct sockaddr_un *su = (struct sockaddr_un *)ss; #if defined(__linux__) struct sockaddr_ll *sl = (struct sockaddr_ll *)ss; #endif uc_value_t *item; unsigned long n; size_t len; memset(ss, 0, sizeof(*ss)); if (ucv_type(addr) == UC_STRING) { s = ucv_string_get(addr); len = ucv_string_length(addr); if (memchr(s, '/', len) != NULL) { *slen = ucv_string_length(addr); if (*slen >= sizeof(su->sun_path)) *slen = sizeof(su->sun_path) - 1; memcpy(su->sun_path, s, *slen); su->sun_path[(*slen)++] = 0; su->sun_family = AF_UNIX; ok_return(true); } if (len == 0) err_return(EINVAL, "Invalid IP address"); if (*s == '[') { p = memchr(++s, ']', --len); if (!p || (size_t)(p - s) >= sizeof(addrstr)) err_return(EINVAL, "Invalid IPv6 address"); memcpy(addrstr, s, p - s); addrstr[(p - s) + 1] = 0; ss->ss_family = AF_INET6; len -= ((p - s) + 1); s = p + 1; } else if ((p = memchr(s, ':', len)) != NULL && memchr(p + 1, ':', len - ((p - s) + 1)) == NULL) { if ((size_t)(p - s) >= sizeof(addrstr)) err_return(EINVAL, "Invalid IP address"); memcpy(addrstr, s, p - s); addrstr[p - s + 1] = 0; ss->ss_family = AF_INET; len -= (p - s); s = p; } else { if (len >= sizeof(addrstr)) err_return(EINVAL, "Invalid IP address"); memcpy(addrstr, s, len); addrstr[len] = 0; ss->ss_family = 0; len = 0; s = NULL; } if (!parse_addr(addrstr, ss)) return NULL; if (s && *s == ':') { if (len <= 1) err_return(EINVAL, "Invalid port number"); for (s++, len--, n = 0; len > 0; len--, s++) { if (*s < '0' || *s > '9') err_return(EINVAL, "Invalid port number"); n = n * 10 + (*s - '0'); } if (n > 65535) err_return(EINVAL, "Invalid port number"); s6->sin6_port = htons(n); } *slen = (ss->ss_family == AF_INET6) ? sizeof(*s6) : sizeof(*s4); ok_return(true); } else if (ucv_type(addr) == UC_ARRAY) { if (ucv_array_length(addr) == 16) { uint8_t *u8 = (uint8_t *)&s6->sin6_addr; for (size_t i = 0; i < 16; i++) { item = ucv_array_get(addr, i); n = ucv_uint64_get(item); if (ucv_type(item) != UC_INTEGER || errno != 0 || n > 255) err_return(EINVAL, "Invalid IP address array"); u8[i] = n; } s6->sin6_family = AF_INET6; *slen = sizeof(*s6); ok_return(true); } else if (ucv_array_length(addr) == 4) { s4->sin_addr.s_addr = 0; for (size_t i = 0; i < 4; i++) { item = ucv_array_get(addr, i); n = ucv_uint64_get(item); if (ucv_type(item) != UC_INTEGER || errno != 0 || n > 255) err_return(EINVAL, "Invalid IP address array"); s4->sin_addr.s_addr = s4->sin_addr.s_addr * 256 + n; } s4->sin_addr.s_addr = htonl(s4->sin_addr.s_addr); s4->sin_family = AF_INET; *slen = sizeof(*s4); ok_return(true); } err_return(EINVAL, "Invalid IP address array"); } else if (ucv_type(addr) == UC_OBJECT) { n = ucv_to_unsigned(ucv_object_get(addr, "family", NULL)); if (n == 0) { if (ucv_type(ucv_object_get(addr, "path", NULL)) == UC_STRING) { n = AF_UNIX; } else { item = ucv_object_get(addr, "address", NULL); len = ucv_string_length(item); s = ucv_string_get(item); n = (s && memchr(s, ':', len) != NULL) ? AF_INET6 : AF_INET; } if (n == 0) err_return(EINVAL, "Invalid address object"); } switch (n) { case AF_INET6: item = ucv_object_get(addr, "flowinfo", NULL); s6->sin6_flowinfo = htonl(ucv_to_unsigned(item)); item = ucv_object_get(addr, "interface", NULL); if (ucv_type(item) == UC_STRING) { s6->sin6_scope_id = if_nametoindex(ucv_string_get(item)); if (s6->sin6_scope_id == 0) err_return(errno, "Unable to resolve interface %s", ucv_string_get(item)); } else if (item != NULL) { s6->sin6_scope_id = ucv_to_unsigned(item); if (errno != 0) err_return(errno, "Invalid scope ID"); } /* fall through */ case AF_INET: ss->ss_family = n; *slen = (n == AF_INET6) ? sizeof(*s6) : sizeof(*s4); item = ucv_object_get(addr, "port", NULL); n = ucv_to_unsigned(item); if (errno != 0 || n > 65535) err_return(EINVAL, "Invalid port number"); s6->sin6_port = htons(n); item = ucv_object_get(addr, "address", NULL); len = ucv_string_length(item); s = ucv_string_get(item); if (len >= sizeof(addrstr)) err_return(EINVAL, "Invalid IP address"); if (len > 0) { memcpy(addrstr, s, len); addrstr[len] = 0; if (!parse_addr(addrstr, ss)) return NULL; } ok_return(true); case AF_UNIX: item = ucv_object_get(addr, "path", NULL); len = ucv_string_length(item); if (len == 0 || len >= sizeof(su->sun_path)) err_return(EINVAL, "Invalid path value"); memcpy(su->sun_path, ucv_string_get(item), len); su->sun_path[len++] = 0; su->sun_family = AF_UNIX; *slen = len; ok_return(true); #if defined(__linux__) case AF_PACKET: item = ucv_object_get(addr, "protocol", NULL); if (item) { n = ucv_to_unsigned(item); if (errno != 0 || n > 65535) err_return(EINVAL, "Invalid protocol number"); sl->sll_protocol = htons(n); } item = ucv_object_get(addr, "address", NULL); if (uv_to_hwaddr(item, sl->sll_addr, &len)) sl->sll_halen = len; else return false; item = ucv_object_get(addr, "interface", NULL); if (ucv_type(item) == UC_STRING) { sl->sll_ifindex = if_nametoindex(ucv_string_get(item)); if (sl->sll_ifindex == 0) err_return(errno, "Unable to resolve interface %s", ucv_string_get(item)); } else if (item != NULL) { sl->sll_ifindex = ucv_to_integer(item); if (errno) err_return(errno, "Unable to convert interface to integer"); } item = ucv_object_get(addr, "hardware_type", NULL); if (item) { n = ucv_to_unsigned(item); if (errno != 0 || n > 65535) err_return(EINVAL, "Invalid hardware type"); sl->sll_hatype = n; } item = ucv_object_get(addr, "packet_type", NULL); if (item) { n = ucv_to_unsigned(item); if (errno != 0 || n > 255) err_return(EINVAL, "Invalid packet type"); sl->sll_pkttype = n; } sl->sll_family = AF_PACKET; *slen = sizeof(*sl); ok_return(true); #endif } } err_return(EINVAL, "Invalid address value"); } static bool uv_to_fileno(uc_vm_t *vm, uc_value_t *val, int *fileno) { uc_value_t *fn; int *fdptr; fdptr = (int *)ucv_resource_dataptr(val, "socket"); if (fdptr) { if (*fdptr < 0) err_return(EBADF, "Socket is closed"); *fileno = *fdptr; return true; } fn = ucv_property_get(val, "fileno"); if (ucv_is_callable(fn)) { uc_vm_stack_push(vm, ucv_get(val)); uc_vm_stack_push(vm, ucv_get(fn)); if (uc_vm_call(vm, true, 0) != EXCEPTION_NONE) return false; val = uc_vm_stack_pop(vm); } else { ucv_get(val); } *fileno = ucv_int64_get(val); ucv_put(val); if (errno != 0 || *fileno < 0) err_return(EBADF, "Invalid file descriptor number"); return true; } static bool uv_to_pollfd(uc_vm_t *vm, uc_value_t *val, struct pollfd *pfd) { int64_t flags; if (ucv_type(val) == UC_ARRAY) { if (!uv_to_fileno(vm, ucv_array_get(val, 0), &pfd->fd)) return false; flags = ucv_to_integer(ucv_array_get(val, 1)); if (errno != 0 || flags < -32768 || flags > 32767) err_return(ERANGE, "Flags value out of range"); pfd->events = flags; pfd->revents = 0; } else { if (!uv_to_fileno(vm, val, &pfd->fd)) return false; pfd->events = POLLIN | POLLERR | POLLHUP; pfd->revents = 0; } return true; } static uc_value_t * ucv_socket_new(uc_vm_t *vm, int fd) { return ucv_resource_new( ucv_resource_type_lookup(vm, "socket"), (void *)(intptr_t)fd ); } static bool xclose(int *fdptr) { bool rv = true; if (fdptr) { if (*fdptr >= 0) rv = (close(*fdptr) == 0); *fdptr = -1; } return rv; } typedef struct { const char *name; enum { DT_SIGNED, DT_UNSIGNED, DT_IPV4ADDR, DT_CALLBACK } type; union { size_t offset; bool (*to_c)(void *, uc_value_t *); } u1; union { size_t size; uc_value_t *(*to_uv)(void *); } u2; } member_t; typedef struct { size_t size; member_t *members; } struct_t; typedef struct { int level; int option; struct_t *ctype; } sockopt_t; #define STRUCT_MEMBER_NP(struct_name, member_name, data_type) \ { #member_name, data_type, \ { .offset = offsetof(struct struct_name, member_name) }, \ { .size = sizeof(((struct struct_name *)NULL)->member_name) } } #define STRUCT_MEMBER_CB(member_name, to_c_fn, to_uv_fn) \ { #member_name, DT_CALLBACK, { .to_c = to_c_fn }, { .to_uv = to_uv_fn } } #define STRUCT_MEMBER(struct_name, member_prefix, member_name, data_type) \ { #member_name, data_type, \ { .offset = offsetof(struct struct_name, member_prefix##_##member_name) }, \ { .size = sizeof(((struct struct_name *)NULL)->member_prefix##_##member_name) } } static struct_t st_timeval = { .size = sizeof(struct timeval), .members = (member_t []){ STRUCT_MEMBER(timeval, tv, sec, DT_SIGNED), STRUCT_MEMBER(timeval, tv, usec, DT_SIGNED), { 0 } } }; #if defined(__linux__) static struct_t st_ucred = { .size = sizeof(struct ucred), .members = (member_t []){ STRUCT_MEMBER_NP(ucred, pid, DT_SIGNED), STRUCT_MEMBER_NP(ucred, uid, DT_SIGNED), STRUCT_MEMBER_NP(ucred, gid, DT_SIGNED), { 0 } } }; #endif static struct_t st_linger = { .size = sizeof(struct linger), .members = (member_t []){ STRUCT_MEMBER(linger, l, onoff, DT_SIGNED), STRUCT_MEMBER(linger, l, linger, DT_SIGNED), { 0 } } }; static struct_t st_ip_mreqn = { .size = sizeof(struct ip_mreqn), .members = (member_t []){ STRUCT_MEMBER(ip_mreqn, imr, multiaddr, DT_IPV4ADDR), STRUCT_MEMBER(ip_mreqn, imr, address, DT_IPV4ADDR), STRUCT_MEMBER(ip_mreqn, imr, ifindex, DT_SIGNED), { 0 } } }; static struct_t st_ip_mreq_source = { .size = sizeof(struct ip_mreq_source), .members = (member_t []){ STRUCT_MEMBER(ip_mreq_source, imr, multiaddr, DT_IPV4ADDR), STRUCT_MEMBER(ip_mreq_source, imr, interface, DT_IPV4ADDR), STRUCT_MEMBER(ip_mreq_source, imr, sourceaddr, DT_IPV4ADDR), { 0 } } }; #if defined(__linux__) static struct_t st_ip_msfilter = { .size = sizeof(struct ip_msfilter), .members = (member_t []){ STRUCT_MEMBER(ip_msfilter, imsf, multiaddr, DT_IPV4ADDR), STRUCT_MEMBER(ip_msfilter, imsf, interface, DT_IPV4ADDR), STRUCT_MEMBER(ip_msfilter, imsf, fmode, DT_SIGNED), STRUCT_MEMBER(ip_msfilter, imsf, numsrc, DT_SIGNED), STRUCT_MEMBER(ip_msfilter, imsf, slist, DT_SIGNED), { 0 } } }; static uc_value_t * snd_wscale_to_uv(void *st) { return ucv_uint64_new(((struct tcp_info *)st)->tcpi_snd_wscale); } static uc_value_t * rcv_wscale_to_uv(void *st) { return ucv_uint64_new(((struct tcp_info *)st)->tcpi_rcv_wscale); } static bool snd_wscale_to_c(void *st, uc_value_t *uv) { ((struct tcp_info *)st)->tcpi_snd_wscale = ucv_to_unsigned(uv); if (errno) err_return(errno, "Unable to convert field snd_wscale to unsigned"); return true; } static bool rcv_wscale_to_c(void *st, uc_value_t *uv) { ((struct tcp_info *)st)->tcpi_rcv_wscale = ucv_to_unsigned(uv); if (errno) err_return(errno, "Unable to convert field rcv_wscale to unsigned"); return true; } static struct_t st_tcp_info = { .size = sizeof(struct tcp_info), .members = (member_t []){ STRUCT_MEMBER(tcp_info, tcpi, state, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, ca_state, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, retransmits, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, probes, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, backoff, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, options, DT_UNSIGNED), STRUCT_MEMBER_CB(snd_wscale, snd_wscale_to_c, snd_wscale_to_uv), STRUCT_MEMBER_CB(rcv_wscale, rcv_wscale_to_c, rcv_wscale_to_uv), STRUCT_MEMBER(tcp_info, tcpi, rto, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, ato, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, snd_mss, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, rcv_mss, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, unacked, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, sacked, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, lost, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, retrans, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, fackets, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, last_data_sent, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, last_ack_sent, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, last_data_recv, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, last_ack_recv, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, pmtu, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, rcv_ssthresh, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, rtt, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, rttvar, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, snd_ssthresh, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, snd_cwnd, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, advmss, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, reordering, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, rcv_rtt, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, rcv_space, DT_UNSIGNED), STRUCT_MEMBER(tcp_info, tcpi, total_retrans, DT_UNSIGNED), { 0 } } }; #endif static uc_value_t * ai_addr_to_uv(void *st) { uc_value_t *rv = ucv_object_new(NULL); struct sockaddr_storage ss = { 0 }; struct addrinfo *ai = st; memcpy(&ss, ai->ai_addr, ai->ai_addrlen); if (!sockaddr_to_uv(&ss, rv)) { ucv_put(rv); return NULL; } return rv; } static uc_value_t * ai_canonname_to_uv(void *st) { struct addrinfo *ai = st; return ai->ai_canonname ? ucv_string_new(ai->ai_canonname) : NULL; } /** * Represents a network address information object returned by * {@link module:socket#addrinfo|`addrinfo()`}. * * @typedef {Object} module:socket.AddressInfo * * @property {module:socket.socket.SocketAddress} addr - A socket address structure. * @property {string} [canonname=null] - The canonical hostname associated with the address. * @property {number} family - The address family (e.g., `2` for `AF_INET`, `10` for `AF_INET6`). * @property {number} flags - Additional flags indicating properties of the address. * @property {number} protocol - The protocol number. * @property {number} socktype - The socket type (e.g., `1` for `SOCK_STREAM`, `2` for `SOCK_DGRAM`). */ static struct_t st_addrinfo = { .size = sizeof(struct addrinfo), .members = (member_t []){ STRUCT_MEMBER(addrinfo, ai, flags, DT_SIGNED), STRUCT_MEMBER(addrinfo, ai, family, DT_SIGNED), STRUCT_MEMBER(addrinfo, ai, socktype, DT_SIGNED), STRUCT_MEMBER(addrinfo, ai, protocol, DT_SIGNED), STRUCT_MEMBER_CB(addr, NULL, ai_addr_to_uv), STRUCT_MEMBER_CB(canonname, NULL, ai_canonname_to_uv), { 0 } } }; #if defined(__linux__) static uc_value_t * mr_ifindex_to_uv(void *st) { char ifname[IF_NAMESIZE] = { 0 }; struct packet_mreq *mr = st; if (mr->mr_ifindex > 0 && if_indextoname(mr->mr_ifindex, ifname)) return ucv_string_new(ifname); return ucv_int64_new(mr->mr_ifindex); } static bool mr_ifindex_to_c(void *st, uc_value_t *uv) { struct packet_mreq *mr = st; if (ucv_type(uv) == UC_STRING) { mr->mr_ifindex = if_nametoindex(ucv_string_get(uv)); if (mr->mr_ifindex == 0) err_return(errno, "Unable to resolve interface %s", ucv_string_get(uv)); } else { mr->mr_ifindex = ucv_to_integer(uv); if (errno) err_return(errno, "Unable to convert interface to integer"); } return true; } static uc_value_t * mr_address_to_uv(void *st) { struct packet_mreq *mr = st; return hwaddr_to_uv(mr->mr_address, mr->mr_alen); } static bool mr_address_to_c(void *st, uc_value_t *uv) { struct packet_mreq *mr = st; size_t len; if (!uv_to_hwaddr(uv, mr->mr_address, &len)) return false; mr->mr_alen = len; return true; } static struct_t st_packet_mreq = { .size = sizeof(struct packet_mreq), .members = (member_t []){ STRUCT_MEMBER_CB(interface, mr_ifindex_to_c, mr_ifindex_to_uv), STRUCT_MEMBER(packet_mreq, mr, type, DT_UNSIGNED), STRUCT_MEMBER_CB(address, mr_address_to_c, mr_address_to_uv), { 0 } } }; static struct_t st_tpacket_req = { .size = sizeof(struct tpacket_req), .members = (member_t []){ STRUCT_MEMBER(tpacket_req, tp, block_size, DT_UNSIGNED), STRUCT_MEMBER(tpacket_req, tp, block_nr, DT_UNSIGNED), STRUCT_MEMBER(tpacket_req, tp, frame_size, DT_UNSIGNED), STRUCT_MEMBER(tpacket_req, tp, frame_nr, DT_UNSIGNED), { 0 } } }; static struct_t st_tpacket_stats = { .size = sizeof(struct tpacket_stats), .members = (member_t []){ STRUCT_MEMBER(tpacket_stats, tp, packets, DT_UNSIGNED), STRUCT_MEMBER(tpacket_stats, tp, drops, DT_UNSIGNED), { 0 } } }; static struct_t st_fanout_args = { .size = sizeof(struct fanout_args), .members = (member_t []){ STRUCT_MEMBER_NP(fanout_args, id, DT_UNSIGNED), STRUCT_MEMBER_NP(fanout_args, type_flags, DT_UNSIGNED), STRUCT_MEMBER_NP(fanout_args, max_num_members, DT_UNSIGNED), { 0 } } }; #endif #define SV_VOID (struct_t *)0 #define SV_INT (struct_t *)1 #define SV_INT_RO (struct_t *)2 #define SV_BOOL (struct_t *)3 #define SV_STRING (struct_t *)4 static sockopt_t sockopts[] = { { SOL_SOCKET, SO_ACCEPTCONN, SV_BOOL }, { SOL_SOCKET, SO_BROADCAST, SV_BOOL }, { SOL_SOCKET, SO_DEBUG, SV_BOOL }, { SOL_SOCKET, SO_ERROR, SV_INT_RO }, { SOL_SOCKET, SO_DONTROUTE, SV_BOOL }, { SOL_SOCKET, SO_KEEPALIVE, SV_BOOL }, { SOL_SOCKET, SO_LINGER, &st_linger }, { SOL_SOCKET, SO_OOBINLINE, SV_BOOL }, { SOL_SOCKET, SO_RCVBUF, SV_INT }, { SOL_SOCKET, SO_RCVLOWAT, SV_INT }, { SOL_SOCKET, SO_RCVTIMEO, &st_timeval }, { SOL_SOCKET, SO_REUSEADDR, SV_BOOL }, { SOL_SOCKET, SO_REUSEPORT, SV_BOOL }, { SOL_SOCKET, SO_SNDBUF, SV_INT }, { SOL_SOCKET, SO_SNDLOWAT, SV_INT }, { SOL_SOCKET, SO_SNDTIMEO, &st_timeval }, { SOL_SOCKET, SO_TIMESTAMP, SV_BOOL }, { SOL_SOCKET, SO_TYPE, SV_INT }, #if defined(__linux__) { SOL_SOCKET, SO_ATTACH_FILTER, SV_STRING }, { SOL_SOCKET, SO_ATTACH_BPF, SV_INT }, { SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, SV_STRING }, { SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, SV_INT }, { SOL_SOCKET, SO_BINDTODEVICE, SV_STRING }, { SOL_SOCKET, SO_DETACH_FILTER, SV_VOID }, { SOL_SOCKET, SO_DETACH_BPF, SV_VOID }, { SOL_SOCKET, SO_DOMAIN, SV_INT_RO }, { SOL_SOCKET, SO_INCOMING_CPU, SV_INT }, { SOL_SOCKET, SO_INCOMING_NAPI_ID, SV_INT_RO }, { SOL_SOCKET, SO_LOCK_FILTER, SV_INT }, { SOL_SOCKET, SO_MARK, SV_INT }, { SOL_SOCKET, SO_PASSCRED, SV_BOOL }, { SOL_SOCKET, SO_PASSSEC, SV_BOOL }, { SOL_SOCKET, SO_PEEK_OFF, SV_INT }, { SOL_SOCKET, SO_PEERCRED, &st_ucred }, { SOL_SOCKET, SO_PEERSEC, SV_STRING }, { SOL_SOCKET, SO_PRIORITY, SV_INT }, { SOL_SOCKET, SO_PROTOCOL, SV_INT }, { SOL_SOCKET, SO_RCVBUFFORCE, SV_INT }, { SOL_SOCKET, SO_RXQ_OVFL, SV_BOOL }, { SOL_SOCKET, SO_SNDBUFFORCE, SV_INT }, { SOL_SOCKET, SO_TIMESTAMPNS, SV_BOOL }, { SOL_SOCKET, SO_BUSY_POLL, SV_INT }, #endif { IPPROTO_IP, IP_ADD_MEMBERSHIP, &st_ip_mreqn }, { IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, &st_ip_mreq_source }, { IPPROTO_IP, IP_BLOCK_SOURCE, &st_ip_mreq_source }, { IPPROTO_IP, IP_DROP_MEMBERSHIP, &st_ip_mreqn }, { IPPROTO_IP, IP_DROP_SOURCE_MEMBERSHIP, &st_ip_mreq_source }, { IPPROTO_IP, IP_HDRINCL, SV_BOOL }, { IPPROTO_IP, IP_MULTICAST_IF, &st_ip_mreqn }, { IPPROTO_IP, IP_MULTICAST_LOOP, SV_BOOL }, { IPPROTO_IP, IP_MULTICAST_TTL, SV_INT }, { IPPROTO_IP, IP_OPTIONS, SV_STRING }, { IPPROTO_IP, IP_PKTINFO, SV_BOOL }, { IPPROTO_IP, IP_RECVOPTS, SV_BOOL }, { IPPROTO_IP, IP_RECVTOS, SV_BOOL }, { IPPROTO_IP, IP_RECVTTL, SV_BOOL }, { IPPROTO_IP, IP_RETOPTS, SV_BOOL }, { IPPROTO_IP, IP_TOS, SV_INT }, { IPPROTO_IP, IP_TTL, SV_INT }, { IPPROTO_IP, IP_UNBLOCK_SOURCE, &st_ip_mreq_source }, #if defined(__linux__) { IPPROTO_IP, IP_MSFILTER, &st_ip_msfilter }, { IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT, SV_BOOL }, { IPPROTO_IP, IP_FREEBIND, SV_BOOL }, { IPPROTO_IP, IP_MTU, SV_INT }, { IPPROTO_IP, IP_MTU_DISCOVER, SV_INT }, { IPPROTO_IP, IP_MULTICAST_ALL, SV_BOOL }, { IPPROTO_IP, IP_NODEFRAG, SV_BOOL }, { IPPROTO_IP, IP_PASSSEC, SV_BOOL }, { IPPROTO_IP, IP_RECVERR, SV_BOOL }, { IPPROTO_IP, IP_RECVORIGDSTADDR, SV_BOOL }, { IPPROTO_IP, IP_ROUTER_ALERT, SV_BOOL }, { IPPROTO_IP, IP_TRANSPARENT, SV_BOOL }, #endif { IPPROTO_TCP, TCP_KEEPCNT, SV_INT }, { IPPROTO_TCP, TCP_KEEPINTVL, SV_INT }, { IPPROTO_TCP, TCP_MAXSEG, SV_INT }, { IPPROTO_TCP, TCP_NODELAY, SV_BOOL }, { IPPROTO_TCP, TCP_FASTOPEN, SV_INT }, #if defined(__linux__) { IPPROTO_TCP, TCP_CONGESTION, SV_STRING }, { IPPROTO_TCP, TCP_CORK, SV_BOOL }, { IPPROTO_TCP, TCP_DEFER_ACCEPT, SV_INT }, { IPPROTO_TCP, TCP_INFO, &st_tcp_info }, { IPPROTO_TCP, TCP_KEEPIDLE, SV_INT }, { IPPROTO_TCP, TCP_LINGER2, SV_INT }, { IPPROTO_TCP, TCP_QUICKACK, SV_BOOL }, { IPPROTO_TCP, TCP_SYNCNT, SV_INT }, { IPPROTO_TCP, TCP_USER_TIMEOUT, SV_INT }, { IPPROTO_TCP, TCP_WINDOW_CLAMP, SV_INT }, { IPPROTO_TCP, TCP_FASTOPEN_CONNECT, SV_INT }, #endif #if defined(__linux__) { IPPROTO_UDP, UDP_CORK, SV_BOOL }, #endif #if defined(__linux__) { SOL_PACKET, PACKET_ADD_MEMBERSHIP, &st_packet_mreq }, { SOL_PACKET, PACKET_DROP_MEMBERSHIP, &st_packet_mreq }, { SOL_PACKET, PACKET_AUXDATA, SV_BOOL }, { SOL_PACKET, PACKET_FANOUT, &st_fanout_args }, { SOL_PACKET, PACKET_LOSS, SV_BOOL }, { SOL_PACKET, PACKET_RESERVE, SV_INT }, { SOL_PACKET, PACKET_RX_RING, &st_tpacket_req }, { SOL_PACKET, PACKET_STATISTICS, &st_tpacket_stats }, { SOL_PACKET, PACKET_TIMESTAMP, SV_INT }, { SOL_PACKET, PACKET_TX_RING, &st_tpacket_req }, { SOL_PACKET, PACKET_VERSION, SV_INT }, { SOL_PACKET, PACKET_QDISC_BYPASS, SV_BOOL }, #endif }; static char * uv_to_struct(uc_value_t *uv, struct_t *spec) { uc_value_t *fv; const char *s; uint64_t u64; int64_t s64; member_t *m; bool found; char *st; union { int8_t s8; int16_t s16; int32_t s32; int64_t s64; uint8_t u8; uint16_t u16; uint32_t u32; uint64_t u64; } v; st = xalloc(spec->size); for (size_t i = 0; spec->members[i].name; i++) { m = &spec->members[i]; fv = ucv_object_get(uv, m->name, &found); if (!found || !fv) continue; switch (spec->members[i].type) { case DT_UNSIGNED: u64 = ucv_to_unsigned(fv); if (errno) { free(st); err_return(errno, "Unable to convert field %s to unsigned", m->name); } switch (m->u2.size) { case 1: v.u8 = (uint8_t)u64; break; case 2: v.u16 = (uint16_t)u64; break; case 4: v.u32 = (uint32_t)u64; break; case 8: v.u64 = (uint64_t)u64; break; } memcpy(st + m->u1.offset, &v, m->u2.size); break; case DT_SIGNED: s64 = ucv_to_integer(fv); if (errno) { free(st); err_return(errno, "Unable to convert field %s to integer", m->name); } switch (m->u2.size) { case 1: v.s8 = (int8_t)s64; break; case 2: v.s16 = (int16_t)s64; break; case 4: v.s32 = (int32_t)s64; break; case 8: v.s64 = (int64_t)s64; break; } memcpy(st + m->u1.offset, &v, m->u2.size); break; case DT_IPV4ADDR: s = ucv_string_get(fv); if (!s || inet_pton(AF_INET, s, st + m->u1.offset) != 1) { free(st); err_return(EINVAL, "Unable to convert field %s to IP address", m->name); } break; case DT_CALLBACK: if (m->u1.to_c && !m->u1.to_c(st, fv)) { free(st); return NULL; } break; } } return st; } static uc_value_t * struct_to_uv(char *st, struct_t *spec) { char s[sizeof("255.255.255.255")]; uc_value_t *uv, *fv; member_t *m; uv = ucv_object_new(NULL); for (size_t i = 0; spec->members[i].name; i++) { m = &spec->members[i]; fv = NULL; switch (spec->members[i].type) { case DT_UNSIGNED: switch (spec->members[i].u2.size) { case 1: fv = ucv_uint64_new(*(uint8_t *)(st + m->u1.offset)); break; case 2: fv = ucv_uint64_new(*(uint16_t *)(st + m->u1.offset)); break; case 4: fv = ucv_uint64_new(*(uint32_t *)(st + m->u1.offset)); break; case 8: fv = ucv_uint64_new(*(uint64_t *)(st + m->u1.offset)); break; } break; case DT_SIGNED: switch (spec->members[i].u2.size) { case 1: fv = ucv_int64_new(*(int8_t *)(st + m->u1.offset)); break; case 2: fv = ucv_int64_new(*(int16_t *)(st + m->u1.offset)); break; case 4: fv = ucv_int64_new(*(int32_t *)(st + m->u1.offset)); break; case 8: fv = ucv_int64_new(*(int64_t *)(st + m->u1.offset)); break; } break; case DT_IPV4ADDR: if (inet_ntop(AF_INET, st + m->u1.offset, s, sizeof(s))) fv = ucv_string_new(s); break; case DT_CALLBACK: fv = m->u2.to_uv ? m->u2.to_uv(st) : NULL; break; } ucv_object_add(uv, m->name, fv); } return uv; } /** * Sets options on the socket. * * Sets the specified option on the socket to the given value. * * Returns `true` if the option was successfully set. * * Returns `null` if an error occurred. * * @function module:socket.socket#setopt * * @param {number} level * The protocol level at which the option resides. This can be a level such as * `SOL_SOCKET` for the socket API level or a specific protocol level defined * by the system. * * @param {number} option * The socket option to set. This can be an integer representing the option, * such as `SO_REUSEADDR`, or a constant defined by the system. * * @param {*} value * The value to set the option to. The type of this argument depends on the * specific option being set. It can be an integer, a boolean, a string, or a * dictionary representing the value to set. If a dictionary is provided, it is * internally translated to the corresponding C struct type required by the * option. * * @returns {?boolean} */ static uc_value_t * uc_socket_inst_setopt(uc_vm_t *vm, size_t nargs) { int sockfd, solvl, soopt, soval, ret; uc_value_t *level, *option, *value; void *valptr = NULL, *st = NULL; socklen_t vallen = 0; size_t i; args_get(vm, nargs, &sockfd, "level", UC_INTEGER, false, &level, "option", UC_INTEGER, false, &option, "value", UC_NULL, false, &value); solvl = ucv_int64_get(level); soopt = ucv_int64_get(option); for (i = 0; i < ARRAY_SIZE(sockopts); i++) { if (sockopts[i].level != solvl || sockopts[i].option != soopt) continue; switch ((uintptr_t)sockopts[i].ctype) { case (uintptr_t)SV_INT_RO: err_return(EOPNOTSUPP, "Socket option is read only"); case (uintptr_t)SV_VOID: valptr = NULL; vallen = 0; break; case (uintptr_t)SV_INT: soval = ucv_to_integer(value); if (errno) err_return(errno, "Unable to convert value to integer"); valptr = &soval; vallen = sizeof(int); break; case (uintptr_t)SV_BOOL: soval = ucv_to_unsigned(value) ? 1 : 0; if (errno) err_return(errno, "Unable to convert value to boolean"); valptr = &soval; vallen = sizeof(int); break; case (uintptr_t)SV_STRING: valptr = ucv_string_get(value); vallen = ucv_string_length(value); break; default: st = uv_to_struct(value, sockopts[i].ctype); valptr = st; vallen = sockopts[i].ctype->size; break; } break; } if (i == ARRAY_SIZE(sockopts)) err_return(EINVAL, "Unknown socket level or option"); ret = setsockopt(sockfd, solvl, soopt, valptr, vallen); free(st); if (ret == -1) err_return(errno, "setsockopt()"); ok_return(ucv_boolean_new(true)); } /** * Gets options from the socket. * * Retrieves the value of the specified option from the socket. * * Returns the value of the requested option. * * Returns `null` if an error occurred or if the option is not supported. * * @function module:socket.socket#getopt * * @param {number} level * The protocol level at which the option resides. This can be a level such as * `SOL_SOCKET` for the socket API level or a specific protocol level defined * by the system. * * @param {number} option * The socket option to retrieve. This can be an integer representing the * option, such as `SO_REUSEADDR`, or a constant defined by the system. * * @returns {?*} * The value of the requested option. The type of the returned value depends * on the specific option being retrieved. It can be an integer, a boolean, a * string, or a dictionary representing a complex data structure. */ static uc_value_t * uc_socket_inst_getopt(uc_vm_t *vm, size_t nargs) { uc_value_t *level, *option, *value = NULL; int sockfd, solvl, soopt, soval, ret; void *valptr = NULL, *st = NULL; uc_stringbuf_t *sb = NULL; socklen_t vallen; size_t i; args_get(vm, nargs, &sockfd, "level", UC_INTEGER, false, &level, "option", UC_INTEGER, false, &option); solvl = ucv_int64_get(level); soopt = ucv_int64_get(option); soval = 0; for (i = 0; i < ARRAY_SIZE(sockopts); i++) { if (sockopts[i].level != solvl || sockopts[i].option != soopt) continue; switch ((uintptr_t)sockopts[i].ctype) { case (uintptr_t)SV_VOID: err_return(EOPNOTSUPP, "Socket option is write only"); case (uintptr_t)SV_INT: case (uintptr_t)SV_INT_RO: case (uintptr_t)SV_BOOL: valptr = &soval; vallen = sizeof(int); break; case (uintptr_t)SV_STRING: sb = strbuf_alloc(64); valptr = strbuf_data(sb); vallen = strbuf_size(sb); break; default: st = xalloc(sockopts[i].ctype->size); valptr = st; vallen = sockopts[i].ctype->size; break; } break; } if (i == ARRAY_SIZE(sockopts)) err_return(EINVAL, "Unknown socket level or option"); while (true) { ret = getsockopt(sockfd, solvl, soopt, valptr, &vallen); if (sockopts[i].ctype == SV_STRING && (ret == 0 || (ret == -1 && errno == ERANGE)) && vallen > strbuf_size(sb)) { if (!strbuf_grow(sb, vallen)) return NULL; valptr = strbuf_data(sb); continue; } break; } if (ret == 0) { switch ((uintptr_t)sockopts[i].ctype) { case (uintptr_t)SV_VOID: break; case (uintptr_t)SV_INT: case (uintptr_t)SV_INT_RO: value = ucv_int64_new(soval); break; case (uintptr_t)SV_BOOL: value = ucv_boolean_new(soval); break; case (uintptr_t)SV_STRING: value = strbuf_finish(&sb, vallen); break; default: value = struct_to_uv(st, sockopts[i].ctype); break; } } strbuf_free(sb); free(st); if (ret == -1) err_return(errno, "getsockopt()"); ok_return(value); } /** * Returns the UNIX file descriptor number associated with the socket. * * Returns the file descriptor number. * * Returns `-1` if an error occurred. * * @function module:socket.socket#fileno * * @returns {number} */ static uc_value_t * uc_socket_inst_fileno(uc_vm_t *vm, size_t nargs) { int sockfd; args_get(vm, nargs, &sockfd); ok_return(ucv_int64_new(sockfd)); } /** * Query error information. * * Returns a string containing a description of the last occurred error when * the *numeric* argument is absent or false. * * Returns a positive (`errno`) or negative (`EAI_*` constant) error code number * when the *numeric* argument is `true`. * * Returns `null` if there is no error information. * * @function module:socket#error * * @param {boolean} [numeric] * Whether to return a numeric error code (`true`) or a human readable error * message (false). * * @returns {?string|?number} * * @example * // Trigger socket error by attempting to bind IPv6 address with IPv4 socket * socket.create(socket.AF_INET, socket.SOCK_STREAM, 0).bind("::", 8080); * * // Print error (should yield "Address family not supported by protocol") * print(socket.error(), "\n"); * * // Trigger resolve error * socket.addrinfo("doesnotexist.org"); * * // Query error code (should yield -2 for EAI_NONAME) * print(socket.error(true), "\n"); // */ static uc_value_t * uc_socket_error(uc_vm_t *vm, size_t nargs) { uc_value_t *numeric = uc_fn_arg(0), *rv; uc_stringbuf_t *buf; if (last_error.code == 0) return NULL; if (ucv_is_truish(numeric)) { rv = ucv_int64_new(last_error.code); } else { buf = ucv_stringbuf_new(); if (last_error.msg) ucv_stringbuf_printf(buf, "%s: ", last_error.msg); if (last_error.code >= 0) ucv_stringbuf_printf(buf, "%s", strerror(last_error.code)); else ucv_stringbuf_printf(buf, "%s", gai_strerror(last_error.code)); rv = ucv_stringbuf_finish(buf); } ok_return(rv); } /** * @typedef {Object} module:socket.socket.SocketAddress * @property {number} family * Address family, one of AF_INET, AF_INET6, AF_UNIX or AF_PACKET. * * @property {string} address * IPv4/IPv6 address string (AF_INET or AF_INET6 only) or hardware address in * hexadecimal notation (AF_PACKET only). * * @property {number} [port] * Port number (AF_INET or AF_INET6 only). * * @property {number} [flowinfo] * IPv6 flow information (AF_INET6 only). * * @property {string|number} [interface] * Link local address scope (for IPv6 sockets) or bound network interface * (for packet sockets), either a network device name string or a nonzero * positive integer representing a network interface index (AF_INET6 and * AF_PACKET only). * * @property {string} path * Domain socket filesystem path (AF_UNIX only). * * @property {number} [protocol=0] * Physical layer protocol (AF_PACKET only). * * @property {number} [hardware_type=0] * ARP hardware type (AF_PACKET only). * * @property {number} [packet_type=PACKET_HOST] * Packet type (AF_PACKET only). */ /** * Parses the provided address value into a socket address representation. * * This function parses the given address value into a socket address * representation required for a number of socket operations. The address value * can be provided in various formats: * - For IPv4 addresses, it can be a string representing the IP address, * optionally followed by a port number separated by colon, e.g. * `192.168.0.1:8080`. * - For IPv6 addresses, it must be an address string enclosed in square * brackets if a port number is specified, otherwise the brackets are * optional. The address string may also include a scope ID in the form * `%ifname` or `%number`, e.g. `[fe80::1%eth0]:8080` or `fe80::1%15`. * - Any string value containing a slash is treated as UNIX domain socket path. * - Alternatively, it can be provided as an array returned by * {@link module:core#iptoarr|iptoarr()}, representing the address octets. * - It can also be an object representing a network address, with properties * for `address` (the IP address) and `port` or a single property `path` to * denote a UNIX domain socket address. * * @function module:socket#sockaddr * * @param {string|number[]|module:socket.socket.SocketAddress} address * The address value to parse. * * @returns {?module:socket.socket.SocketAddress} * A socket address representation of the provided address value, or `null` if * the address could not be parsed. * * @example * // Parse an IP address string with port * const address1 = sockaddr('192.168.0.1:8080'); * * // Parse an IPv6 address string with port and scope identifier * const address2 = sockaddr('[fe80::1%eth0]:8080'); * * // Parse an array representing an IP address * const address3 = sockaddr([192, 168, 0, 1]); * * // Parse a network address object * const address4 = sockaddr({ address: '192.168.0.1', port: 8080 }); * * // Convert a path value to a UNIX domain socket address * const address5 = sockaddr('/var/run/daemon.sock'); */ static uc_value_t * uc_socket_sockaddr(uc_vm_t *vm, size_t nargs) { struct sockaddr_storage ss = { 0 }; uc_value_t *addr, *rv; socklen_t slen; args_get(vm, nargs, NULL, "address", UC_NULL, false, &addr); if (!uv_to_sockaddr(addr, &ss, &slen)) return NULL; rv = ucv_object_new(vm); if (!sockaddr_to_uv(&ss, rv)) { ucv_put(rv); return NULL; } ok_return(rv); } /** * Resolves the given network address into hostname and service name. * * The `nameinfo()` function provides an API for reverse DNS lookup and service * name resolution. It returns an object containing the following properties: * - `hostname`: The resolved hostname. * - `service`: The resolved service name. * * Returns an object representing the resolved hostname and service name. * Return `null` if an error occurred during resolution. * * @function module:socket#nameinfo * * @param {string|module:socket.socket.SocketAddress} address * The network address to resolve. It can be specified as: * - A string representing the IP address. * - An object representing the address with properties `address` and `port`. * * @param {number} [flags] * Optional flags that provide additional control over the resolution process, * specified as bitwise OR-ed number of `NI_*` constants. * * @returns {?{hostname: string, service: string}} * * @see {@link module:socket~"Socket Types"|Socket Types} * @see {@link module:socket~"Name Info Constants"|AName Info Constants} * * @example * // Resolve a network address into hostname and service name * const result = network.getnameinfo('192.168.1.1:80'); * print(result); // { "hostname": "example.com", "service": "http" } */ static uc_value_t * uc_socket_nameinfo(uc_vm_t *vm, size_t nargs) { char host[NI_MAXHOST], serv[NI_MAXSERV]; uc_value_t *addr, *flags, *rv; struct sockaddr_storage ss; socklen_t slen; int ret; args_get(vm, nargs, NULL, "address", UC_NULL, false, &addr, "flags", UC_INTEGER, true, &flags); if (!uv_to_sockaddr(addr, &ss, &slen)) return NULL; ret = getnameinfo((struct sockaddr *)&ss, slen, host, sizeof(host), serv, sizeof(serv), flags ? ucv_int64_get(flags) : 0); if (ret != 0) err_return((ret == EAI_SYSTEM) ? errno : ret, "getnameinfo()"); rv = ucv_object_new(vm); ucv_object_add(rv, "hostname", ucv_string_new(host)); ucv_object_add(rv, "service", ucv_string_new(serv)); ok_return(rv); } /** * Resolves the given hostname and optional service name into a list of network * addresses, according to the provided hints. * * The `addrinfo()` function provides an API for performing DNS and service name * resolution. It returns an array of objects, each representing a resolved * address. * * Returns an array of resolved addresses. * Returns `null` if an error occurred during resolution. * * @function module:socket#addrinfo * * @param {string} hostname * The hostname to resolve. * * @param {string} [service] * Optional service name to resolve. If not provided, the service field of the * resulting address information structures is left uninitialized. * * @param {Object} [hints] * Optional hints object that provides additional control over the resolution * process. It can contain the following properties: * - `family`: The preferred address family (`AF_INET` or `AF_INET6`). * - `socktype`: The socket type (`SOCK_STREAM`, `SOCK_DGRAM`, etc.). * - `protocol`: The protocol of returned addresses. * - `flags`: Bitwise OR-ed `AI_*` flags to control the resolution behavior. * * @returns {?module:socket.AddressInfo[]} * * @see {@link module:socket~"Socket Types"|Socket Types} * @see {@link module:socket~"Address Info Flags"|Address Info Flags} * * @example * // Resolve all addresses * const addresses = socket.addrinfo('example.org'); * * // Resolve IPv4 addresses for a given hostname and service * const ipv4addresses = socket.addrinfo('example.com', 'http', { family: socket.AF_INET }); * * // Resolve IPv6 addresses without specifying a service * const ipv6Addresses = socket.addrinfo('example.com', null, { family: socket.AF_INET6 }); */ static uc_value_t * uc_socket_addrinfo(uc_vm_t *vm, size_t nargs) { struct addrinfo *ai_hints = NULL, *ai_res; uc_value_t *host, *serv, *hints, *rv; char *servstr; int ret; args_get(vm, nargs, NULL, "hostname", UC_STRING, false, &host, "service", UC_NULL, true, &serv, "hints", UC_OBJECT, true, &hints); if (hints) { ai_hints = (struct addrinfo *)uv_to_struct(hints, &st_addrinfo); if (!ai_hints) return NULL; } servstr = (serv && ucv_type(serv) != UC_STRING) ? ucv_to_string(vm, serv) : NULL; ret = getaddrinfo(ucv_string_get(host), servstr ? servstr : ucv_string_get(serv), ai_hints, &ai_res); free(ai_hints); free(servstr); if (ret != 0) err_return((ret == EAI_SYSTEM) ? errno : ret, "getaddrinfo()"); rv = ucv_array_new(vm); for (struct addrinfo *ai = ai_res; ai; ai = ai->ai_next) { uc_value_t *item = struct_to_uv((char *)ai, &st_addrinfo); if (item) ucv_array_push(rv, item); } freeaddrinfo(ai_res); ok_return(rv); } /** * Represents a poll state serving as input parameter and return value type for * {@link module:socket#poll|`poll()`}. * * @typedef {Array} module:socket.PollSpec * @property {module:socket.socket} 0 * The polled socket instance. * * @property {number} 1 * Requested or returned status flags of the polled socket instance. */ /** * Polls a number of sockets for state changes. * * Returns an array of socket poll states. * Returns `null` if an error occurred. * * @function module:socket#poll * * @param {number} timeout * Amount of milliseconds to wait for socket activity before aborting the poll * call. If set to `0`, the poll call will return immediately if none of the * provided sockets has pending events, if set to a negative value, the poll * call will wait indefinitely, in all other cases the poll call will wait at * most for the given amount of milliseconds before returning. * * @param {...(module:socket.socket|module:socket.PollSpec)} sockets * An arbitrary amount of socket arguments. Each argument may be either a plain * {@link module:socket.socket|socket instance} or a `[socket, flags]` tuple * specifying the socket and requested poll flags. If a plain socket and not a * tuple is provided, the requested poll flags default to * `POLLIN|POLLERR|POLLHUP` for this socket. * * @returns {module:socket.PollSpec[]} */ static uc_value_t * uc_socket_poll(uc_vm_t *vm, size_t nargs) { struct { struct pollfd *entries; size_t count; } pfds = { 0 }; uc_value_t *timeoutarg, *rv, *item; int64_t timeout; int ret; args_get(vm, nargs, NULL, "timeout", UC_INTEGER, false, &timeoutarg); timeout = ucv_to_integer(timeoutarg); if (errno != 0 || timeout < (int64_t)INT_MIN || timeout > (int64_t)INT_MAX) err_return(ERANGE, "Invalid timeout value"); rv = ucv_array_new(vm); for (size_t i = 1; i < nargs; i++) { uc_vector_grow(&pfds); if (uv_to_pollfd(vm, uc_fn_arg(i), &pfds.entries[pfds.count])) { item = ucv_array_new_length(vm, 2); ucv_array_set(item, 0, ucv_get(uc_fn_arg(i))); ucv_array_set(rv, pfds.count++, item); } } ret = poll(pfds.entries, pfds.count, timeout); if (ret == -1) { ucv_put(rv); uc_vector_clear(&pfds); err_return(errno, "poll()"); } for (size_t i = 0; i < pfds.count; i++) ucv_array_set(ucv_array_get(rv, i), 1, ucv_int64_new(pfds.entries[i].revents)); uc_vector_clear(&pfds); ok_return(rv); } /** * Creates a network socket and connects it to the specified host and service. * * This high level function combines the functionality of * {@link module:socket#create|create()}, * {@link module:socket#addrinfo|addrinfo()} and * {@link module:socket.socket#connect|connect()} to simplify connection * establishment with the socket module. * * @function module:socket#connect * * @param {string|number[]|module:socket.socket.SocketAddress} host * The host to connect to, can be an IP address, hostname, * {@link module:socket.socket.SocketAddress|SocketAddress}, or an array value * returned by {@link module:core#iptoarr|iptoarr()}. * * @param {string|number} [service] * The service to connect to, can be a symbolic service name (such as "http") or * a port number. Optional if host is specified as * {@link module:socket.socket.SocketAddress|SocketAddress}. * * @param {Object} [hints] * Optional preferences for the socket. It can contain the following properties: * - `family`: The preferred address family (`AF_INET` or `AF_INET6`). * - `socktype`: The socket type (`SOCK_STREAM`, `SOCK_DGRAM`, etc.). * - `protocol`: The protocol of the created socket. * - `flags`: Bitwise OR-ed `AI_*` flags to control the resolution behavior. * * If no hints are not provided, the default socket type preference is set to * `SOCK_STREAM`. * * @param {number} [timeout=-1] * The timeout in milliseconds for socket connect operations. If set to a * negative value, no specifc time limit is imposed and the function will * block until either a connection was successfull or the underlying operating * system timeout is reached. * * @returns {module:socket.socket} * * @example * // Resolve host, try to connect to both resulting IPv4 and IPv6 addresses * let conn = socket.connect("example.org", 80); * * // Enforce usage of IPv6 * let conn = socket.connect("example.com", 80, { family: socket.AF_INET6 }); * * // Connect a UDP socket * let conn = socket.connect("192.168.1.1", 53, { socktype: socket.SOCK_DGRAM }); * * // Bypass name resolution by specifying a SocketAddress structure * let conn = socket.connect({ address: "127.0.0.1", port: 9000 }); * * // Use SocketAddress structure to connect a UNIX domain socket * let conn = socket.connect({ path: "/var/run/daemon.sock" }); */ static uc_value_t * uc_socket_connect(uc_vm_t *vm, size_t nargs) { struct address { struct sockaddr_storage ss; struct addrinfo ai; int flags; int fd; } *ap; struct { struct address *entries; size_t count; } addresses = { 0 }; struct { struct pollfd *entries; size_t count; } pollfds = { 0 }; struct addrinfo *ai_results, *ai_hints, *ai; uc_value_t *host, *serv, *hints, *timeout; const char *errmsg = NULL; struct pollfd *pp = NULL; size_t slot, connected; int ret, err; args_get(vm, nargs, NULL, "host", UC_NULL, false, &host, "service", UC_NULL, true, &serv, "hints", UC_OBJECT, true, &hints, "timeout", UC_INTEGER, true, &timeout); ai_hints = hints ? (struct addrinfo *)uv_to_struct(hints, &st_addrinfo) : NULL; if (ucv_type(host) == UC_STRING) { char *servstr = (ucv_type(serv) != UC_STRING) ? ucv_to_string(vm, serv) : NULL; ret = getaddrinfo(ucv_string_get(host), servstr ? servstr : ucv_string_get(serv), ai_hints ? ai_hints : &(struct addrinfo){ .ai_socktype = SOCK_STREAM }, &ai_results); if (ret != 0) { free(servstr); free(ai_hints); err_return((ret == EAI_SYSTEM) ? errno : ret, "getaddrinfo()"); } for (ai = ai_results; ai != NULL; ai = ai->ai_next) { if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) continue; uc_vector_grow(&addresses); ap = &addresses.entries[addresses.count++]; memcpy(&ap->ss, ai->ai_addr, ai->ai_addrlen); memcpy(&ap->ai, ai, sizeof(*ai)); ap->ai.ai_addr = (struct sockaddr *)&ap->ss; } freeaddrinfo(ai_results); free(servstr); } else { uc_vector_grow(&addresses); ap = &addresses.entries[addresses.count++]; if (!uv_to_sockaddr(host, &ap->ss, &ap->ai.ai_addrlen)) return NULL; if (serv) { uint64_t port = ucv_to_unsigned(serv); if (port > 65535) errno = ERANGE; if (errno != 0) { free(ai_hints); uc_vector_clear(&addresses); err_return(errno, "Invalid port number"); } ((struct sockaddr_in *)&ap->ss)->sin_port = htons(port); } ap->ai.ai_addr = (struct sockaddr *)&ap->ss; ap->ai.ai_family = ap->ss.ss_family; ap->ai.ai_socktype = ai_hints ? ai_hints->ai_socktype : SOCK_STREAM; ap->ai.ai_protocol = ai_hints ? ai_hints->ai_protocol : 0; } free(ai_hints); for (connected = 0, slot = 0, ap = &addresses.entries[slot]; slot < addresses.count; slot++, ap = &addresses.entries[slot]) { uc_vector_grow(&pollfds); pp = &pollfds.entries[pollfds.count++]; pp->events = POLLIN | POLLOUT | POLLHUP | POLLERR; pp->fd = socket(ap->ai.ai_family, ap->ai.ai_socktype, ap->ai.ai_protocol); if (pp->fd == -1) continue; if ((ap->flags = fcntl(pp->fd, F_GETFL, 0)) == -1) { xclose(&pp->fd); continue; } if (fcntl(pp->fd, F_SETFL, ap->flags | O_NONBLOCK) == -1) { xclose(&pp->fd); continue; } ret = connect(pp->fd, ap->ai.ai_addr, ap->ai.ai_addrlen); if (ret == -1 && errno != EINPROGRESS) { xclose(&pp->fd); continue; } connected++; } if (connected == 0) { err = EAI_NONAME; errmsg = "Could not connect to any host address"; goto out; } ret = poll(pollfds.entries, pollfds.count, timeout ? ucv_int64_get(timeout) : -1); if (ret == -1) { err = errno; errmsg = "poll()"; goto out; } for (slot = 0, ap = NULL, pp = NULL; slot < pollfds.count; slot++) { if (pollfds.entries[slot].revents & (POLLIN|POLLOUT)) { ap = &addresses.entries[slot]; pp = &pollfds.entries[slot]; break; } } if (!ap) { err = ETIMEDOUT; errmsg = "Connection timed out"; goto out; } if (fcntl(pp->fd, F_SETFL, ap->flags) == -1) { err = errno; errmsg = "fcntl(F_SETFL)"; goto out; } out: for (slot = 0, ret = -1; slot < pollfds.count; slot++) { if (pp == &pollfds.entries[slot]) ret = pollfds.entries[slot].fd; else xclose(&pollfds.entries[slot].fd); } uc_vector_clear(&addresses); uc_vector_clear(&pollfds); if (errmsg) err_return(err, "%s", errmsg); ok_return(ucv_socket_new(vm, ret)); } /** * Binds a listening network socket to the specified host and service. * * This high-level function combines the functionality of * {@link module:socket#create|create()}, * {@link module:socket#addrinfo|addrinfo()}, * {@link module:socket.socket#bind|bind()}, and * {@link module:socket.socket#listen|listen()} to simplify setting up a * listening socket with the socket module. * * @function module:socket#listen * * @param {string|number[]|module:socket.socket.SocketAddress} host * The host to bind to, can be an IP address, hostname, * {@link module:socket.socket.SocketAddress|SocketAddress}, or an array value * returned by {@link module:core#iptoarr|iptoarr()}. * * @param {string|number} [service] * The service to listen on, can be a symbolic service name (such as "http") or * a port number. Optional if host is specified as * {@link module:socket.socket.SocketAddress|SocketAddress}. * * @param {Object} [hints] * Optional preferences for the socket. It can contain the following properties: * - `family`: The preferred address family (`AF_INET` or `AF_INET6`). * - `socktype`: The socket type (`SOCK_STREAM`, `SOCK_DGRAM`, etc.). * - `protocol`: The protocol of the created socket. * - `flags`: Bitwise OR-ed `AI_*` flags to control the resolution behavior. * * If no hints are provided, the default socket type preference is set to * `SOCK_STREAM`. * * @param {number} [backlog=128] * The maximum length of the queue of pending connections. * * @returns {module:socket.socket} * * @example * // Listen for incoming TCP connections on port 80 * let server = socket.listen("localhost", 80); * * // Listen on IPv6 address only * let server = socket.listen("machine.local", 8080, { family: socket.AF_INET6 }); * * // Listen on a UNIX domain socket * let server = socket.listen({ path: "/var/run/server.sock" }); */ static uc_value_t * uc_socket_listen(uc_vm_t *vm, size_t nargs) { int ret, fd, curr_weight, prev_weight, socktype = 0, protocol = 0; struct addrinfo *ai_results, *ai_hints, *ai; uc_value_t *host, *serv, *hints, *backlog; struct sockaddr_storage ss = { 0 }; bool v6, lo, ll; socklen_t slen; args_get(vm, nargs, NULL, "host", UC_NULL, true, &host, "service", UC_NULL, true, &serv, "hints", UC_OBJECT, true, &hints, "backlog", UC_INTEGER, true, &backlog); ai_hints = hints ? (struct addrinfo *)uv_to_struct(hints, &st_addrinfo) : NULL; if (host == NULL || ucv_type(host) == UC_STRING) { char *servstr = (ucv_type(serv) != UC_STRING) ? ucv_to_string(vm, serv) : NULL; ret = getaddrinfo(ucv_string_get(host), servstr ? servstr : ucv_string_get(serv), ai_hints ? ai_hints : &(struct addrinfo){ .ai_flags = AI_PASSIVE | AI_ADDRCONFIG, .ai_socktype = SOCK_STREAM }, &ai_results); free(servstr); if (ret != 0) { free(ai_hints); err_return((ret == EAI_SYSTEM) ? errno : ret, "getaddrinfo()"); } for (ai = ai_results, prev_weight = -1; ai != NULL; ai = ai->ai_next) { struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)ai->ai_addr; struct sockaddr_in *s4 = (struct sockaddr_in *)ai->ai_addr; v6 = (s6->sin6_family == AF_INET6); ll = v6 ? IN6_IS_ADDR_LINKLOCAL(&s6->sin6_addr) : ((ntohl(s4->sin_addr.s_addr) & 0xffff0000) == 0xa9fe0000); lo = v6 ? IN6_IS_ADDR_LOOPBACK(&s6->sin6_addr) : ((ntohl(s4->sin_addr.s_addr) & 0xff000000) == 0x7f000000); curr_weight = (!lo << 2) | (v6 << 1) | (!ll << 0); if (curr_weight > prev_weight) { prev_weight = curr_weight; socktype = ai->ai_socktype; protocol = ai->ai_protocol; slen = ai->ai_addrlen; memcpy(&ss, ai->ai_addr, slen); } } freeaddrinfo(ai_results); } else { if (!uv_to_sockaddr(host, &ss, &slen)) { free(ai_hints); return NULL; } if (serv) { uint64_t port = ucv_to_unsigned(serv); if (port > 65535) errno = ERANGE; if (errno != 0) { free(ai_hints); err_return(errno, "Invalid port number"); } ((struct sockaddr_in *)&ss)->sin_port = htons(port); } socktype = ai_hints ? ai_hints->ai_socktype : SOCK_STREAM; protocol = ai_hints ? ai_hints->ai_protocol : 0; } free(ai_hints); if (ss.ss_family == AF_UNSPEC) err_return(EAI_NONAME, "Could not resolve host address"); fd = socket(ss.ss_family, socktype, protocol); if (fd == -1) err_return(errno, "socket()"); ret = bind(fd, (struct sockaddr *)&ss, slen); if (ret == -1) { close(fd); err_return(errno, "bind()"); } ret = listen(fd, backlog ? ucv_to_unsigned(backlog) : 128); if (ret == -1) { close(fd); err_return(errno, "listen()"); } ok_return(ucv_socket_new(vm, fd)); } /** * Represents a socket handle. * * @class module:socket.socket * @hideconstructor * * @borrows module:socket#error as module:socket.socket#error * * @see {@link module:socket#create|create()} * * @example * * const sock = create(…); * * sock.getopt(…); * sock.setopt(…); * * sock.connect(…); * sock.listen(…); * sock.accept(…); * sock.bind(…); * * sock.send(…); * sock.recv(…); * * sock.shutdown(…); * * sock.fileno(); * sock.peername(); * sock.sockname(); * * sock.close(); * * sock.error(); */ /** * Creates a socket and returns its hand * * The handle will be connected to the process stdin or stdout, depending on the * value of the mode argument. * * The mode argument may be either "r" to open the process for reading (connect * to its stdin) or "w" to open the process for writing (connect to its stdout). * * The mode character "r" or "w" may be optionally followed by "e" to apply the * FD_CLOEXEC flag onto the open descriptor. * * Returns a process handle referring to the executed process. * * Returns `null` if an error occurred. * * @function module:fs#popen * * @param {string} command * The command to be executed. * * @param {string} [mode="r"] * The open mode of the process handle. * * @returns {?module:fs.proc} * * @example * // Open a process * const process = popen('command', 'r'); */ /** * Creates a network socket instance. * * This function creates a new network socket with the specified domain and * type, determined by one of the modules `AF_*` and `SOCK_*` constants * respectively, and returns the resulting socket instance for use in subsequent * socket operations. * * The domain argument specifies the protocol family, such as AF_INET or * AF_INET6, and defaults to AF_INET if not provided. * * The type argument specifies the socket type, such as SOCK_STREAM or * SOCK_DGRAM, and defaults to SOCK_STREAM if not provided. It may also * be bitwise OR-ed with SOCK_NONBLOCK to enable non-blocking mode or * SOCK_CLOEXEC to enable close-on-exec semantics. * * The protocol argument may be used to indicate a particular protocol * to be used with the socket, and it defaults to 0 (automatically * determined protocol) if not provided. * * Returns a socket descriptor representing the newly created socket. * * Returns `null` if an error occurred during socket creation. * * @function module:socket#create * * @param {number} [domain=AF_INET] * The communication domain for the socket, e.g., AF_INET or AF_INET6. * * @param {number} [type=SOCK_STREAM] * The socket type, e.g., SOCK_STREAM or SOCK_DGRAM. It may also be * bitwise OR-ed with SOCK_NONBLOCK or SOCK_CLOEXEC. * * @param {number} [protocol=0] * The protocol to be used with the socket. * * @returns {?module:socket.socket} * A socket instance representing the newly created socket. * * @example * // Create a TCP socket * const tcp_socket = create(AF_INET, SOCK_STREAM); * * // Create a nonblocking IPv6 UDP socket * const udp_socket = create(AF_INET6, SOCK_DGRAM | SOCK_NONBLOCK); */ static uc_value_t * uc_socket_create(uc_vm_t *vm, size_t nargs) { uc_value_t *domain, *type, *protocol; int sockfd, socktype; args_get(vm, nargs, NULL, "domain", UC_INTEGER, true, &domain, "type", UC_INTEGER, true, &type, "protocol", UC_INTEGER, true, &protocol); socktype = type ? (int)ucv_int64_get(type) : SOCK_STREAM; sockfd = socket( domain ? (int)ucv_int64_get(domain) : AF_INET, #if defined(__APPLE__) socktype & ~(SOCK_NONBLOCK|SOCK_CLOEXEC), #else socktype, #endif protocol ? (int)ucv_int64_get(protocol) : 0); if (sockfd == -1) err_return(errno, "socket()"); #if defined(__APPLE__) if (socktype & SOCK_NONBLOCK) { int flags = fcntl(sockfd, F_GETFL); if (flags == -1) { close(sockfd); err_return(errno, "fcntl(F_GETFL)"); } if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) { close(sockfd); err_return(errno, "fcntl(F_SETFL)"); } } if (socktype & SOCK_CLOEXEC) { if (fcntl(sockfd, F_SETFD, FD_CLOEXEC) == -1) { close(sockfd); err_return(errno, "fcntl(F_SETFD)"); } } #endif ok_return(ucv_socket_new(vm, sockfd)); } /** * Connects the socket to a remote address. * * Attempts to establish a connection to the specified remote address. * * Returns `true` if the connection is successfully established. * Returns `null` if an error occurred during the connection attempt. * * @function module:socket.socket#connect * * @param {string|module:socket.socket.SocketAddress} address * The address of the remote endpoint to connect to. * * @param {number} port * The port number of the remote endpoint to connect to. * * @returns {?boolean} */ static uc_value_t * uc_socket_inst_connect(uc_vm_t *vm, size_t nargs) { struct sockaddr_storage ss; uc_value_t *addr; int ret, sockfd; socklen_t slen; args_get(vm, nargs, &sockfd, "address", UC_NULL, false, &addr); if (!uv_to_sockaddr(addr, &ss, &slen)) return NULL; ret = connect(sockfd, (struct sockaddr *)&ss, slen); if (ret == -1) err_return(errno, "connect()"); ok_return(ucv_boolean_new(true)); } /** * Sends data through the socket. * * Sends the provided data through the socket handle to the specified remote * address, if provided. * * Returns the number of bytes sent. * Returns `null` if an error occurred during the send operation. * * @function module:socket.socket#send * * @param {*} data * The data to be sent through the socket. String data is sent as-is, any other * type is implicitly converted to a string first before being sent on the * socket. * * @param {number} [flags] * Optional flags that modify the behavior of the send operation. * * @param {module:socket.socket.SocketAddress|number[]|string} [address] * The address of the remote endpoint to send the data to. It can be either an * IP address string, an array returned by {@link module:core#iptoarr|iptoarr()}, * or an object representing a network address. If not provided, the data is * sent to the remote endpoint the socket is connected to. * * @returns {?number} * * @see {@link module:socket#sockaddr|sockaddr()} * * @example * // Send to connected socket * let tcp_sock = socket.create(socket.AF_INET, socket.SOCK_STREAM); * tcp_sock.connect("192.168.1.1", 80); * tcp_sock.send("GET / HTTP/1.0\r\n\r\n"); * * // Send a datagram on unconnected socket * let udp_sock = socket.create(socket.AF_INET, socket.SOCK_DGRAM); * udp_sock.send("Hello there!", 0, "255.255.255.255:9000"); * udp_sock.send("Hello there!", 0, { * family: socket.AF_INET, // optional * address: "255.255.255.255", * port: 9000 * }); */ static uc_value_t * uc_socket_inst_send(uc_vm_t *vm, size_t nargs) { uc_value_t *data, *flags, *addr; struct sockaddr_storage ss = { 0 }; struct sockaddr *sa = NULL; socklen_t salen = 0; char *buf = NULL; ssize_t ret; int sockfd; args_get(vm, nargs, &sockfd, "data", UC_NULL, false, &data, "flags", UC_INTEGER, true, &flags, "address", UC_NULL, true, &addr); if (addr) { if (!uv_to_sockaddr(addr, &ss, &salen)) return NULL; sa = (struct sockaddr *)&ss; } if (ucv_type(data) != UC_STRING) buf = ucv_to_string(vm, data); ret = sendto(sockfd, buf ? buf : ucv_string_get(data), buf ? strlen(buf) : ucv_string_length(data), (flags ? ucv_int64_get(flags) : 0) | MSG_NOSIGNAL, sa, salen); free(buf); if (ret == -1) err_return(errno, "send()"); ok_return(ucv_int64_new(ret)); } /** * Receives data from the socket. * * Receives data from the socket handle, optionally specifying the maximum * length of data to receive, flags to modify the receive behavior, and an * optional address dictionary where the function will place the address from * which the data was received (for unconnected sockets). * * Returns a string containing the received data. * Returns an empty string if the remote side closed the socket. * Returns `null` if an error occurred during the receive operation. * * @function module:socket.socket#recv * * @param {number} [length=4096] * The maximum number of bytes to receive. * * @param {number} [flags] * Optional flags that modify the behavior of the receive operation. * * @param {Object} [address] * An object where the function will store the address from which the data was * received. If provided, it will be filled with the details obtained from the * sockaddr argument of the underlying `recvfrom()` syscall. See the type * definition of {@link module:socket.socket.SocketAddress|SocketAddress} for * details on the format. * * @returns {?string} */ static uc_value_t * uc_socket_inst_recv(uc_vm_t *vm, size_t nargs) { uc_value_t *length, *flags, *addrobj; struct sockaddr_storage ss = { 0 }; uc_stringbuf_t *buf; ssize_t len, ret; socklen_t sslen; int sockfd; args_get(vm, nargs, &sockfd, "length", UC_INTEGER, true, &length, "flags", UC_INTEGER, true, &flags, "address", UC_OBJECT, true, &addrobj); len = length ? ucv_to_integer(length) : 4096; if (errno || len <= 0) err_return(errno, "Invalid length argument"); buf = strbuf_alloc(len); if (!buf) return NULL; do { sslen = sizeof(ss); ret = recvfrom(sockfd, strbuf_data(buf), len, flags ? ucv_int64_get(flags) : 0, (struct sockaddr *)&ss, &sslen); } while (ret == -1 && errno == EINTR); if (ret == -1) { strbuf_free(buf); err_return(errno, "recv()"); } if (addrobj) sockaddr_to_uv(&ss, addrobj); ok_return(strbuf_finish(&buf, ret)); } /** * Binds a socket to a specific address. * * This function binds the socket to the specified address. * * Returns `true` if the socket is successfully bound. * * Returns `null` on error, e.g. when the address is in use. * * @function module:socket.socket#bind * * @param {string|module:socket.socket.SocketAddress} address * The IP address to bind the socket to. * * @returns {?boolean} * * @example * const sock = socket.create(…); * const success = sock.bind("192.168.0.1:80"); * * if (success) * print(`Socket bound successfully!\n`); * else * print(`Failed to bind socket: ${sock.error()}.\n`); */ static uc_value_t * uc_socket_inst_bind(uc_vm_t *vm, size_t nargs) { struct sockaddr_storage ss = { 0 }; uc_value_t *addr; socklen_t slen; int sockfd; args_get(vm, nargs, &sockfd, "address", UC_NULL, true, &addr); if (addr) { if (!uv_to_sockaddr(addr, &ss, &slen)) return NULL; if (bind(sockfd, (struct sockaddr *)&ss, slen) == -1) err_return(errno, "bind()"); } else { #if defined(__linux__) int sval = 0; slen = sizeof(sval); if (getsockopt(sockfd, SOL_SOCKET, SO_DOMAIN, &sval, &slen) == -1) err_return(errno, "getsockopt()"); switch (sval) { case AF_INET6: ss.ss_family = AF_INET6; slen = sizeof(struct sockaddr_in6); break; case AF_INET: ss.ss_family = AF_INET; slen = sizeof(struct sockaddr_in); break; default: err_return(EAFNOSUPPORT, "Unsupported socket address family"); } if (bind(sockfd, (struct sockaddr *)&ss, slen) == -1) err_return(errno, "bind()"); #else ss.ss_family = AF_INET6; slen = sizeof(struct sockaddr_in6); if (bind(sockfd, (struct sockaddr *)&ss, slen) == -1) { if (errno != EAFNOSUPPORT) err_return(errno, "bind()"); ss.ss_family = AF_INET; slen = sizeof(struct sockaddr_in); if (bind(sockfd, (struct sockaddr *)&ss, slen) == -1) err_return(errno, "bind()"); } #endif } ok_return(ucv_boolean_new(true)); } /** * Listen for connections on a socket. * * This function marks the socket as a passive socket, that is, as a socket that * will be used to accept incoming connection requests using `accept()`. * * The `backlog` parameter specifies the maximum length to which the queue of * pending connections may grow. If a connection request arrives when the queue * is full, the client connection might get refused. * * If `backlog` is not provided, it defaults to 128. * * Returns `true` if the socket is successfully marked as passive. * Returns `null` if an error occurred, e.g. when the requested port is in use. * * @function module:socket.socket#listen * * @param {number} [backlog=128] * The maximum length of the queue of pending connections. * * @returns {?boolean} * * @see {@link module:socket.socket#accept|accept()} * * @example * const sock = socket.create(…); * sock.bind(…); * * const success = sock.listen(10); * if (success) * print(`Socket is listening for incoming connections!\n`); * else * print(`Failed to listen on socket: ${sock.error()}\n`); */ static uc_value_t * uc_socket_inst_listen(uc_vm_t *vm, size_t nargs) { uc_value_t *backlog; int ret, sockfd; args_get(vm, nargs, &sockfd, "backlog", UC_INTEGER, true, &backlog); ret = listen(sockfd, backlog ? ucv_to_unsigned(backlog) : 128); if (ret == -1) err_return(errno, "listen()"); ok_return(ucv_boolean_new(true)); } /** * Accept a connection on a socket. * * This function accepts a connection on the socket. It extracts the first * connection request on the queue of pending connections, creates a new * connected socket, and returns a new socket handle referring to that socket. * The newly created socket is not in listening state and has no backlog. * * When a optional `address` dictionary is provided, it is populated with the * remote address details of the peer socket. * * The optional `flags` parameter is a bitwise-or-ed number of flags to modify * the behavior of accepted peer socket. Possible values are: * - `SOCK_CLOEXEC`: Enable close-on-exec semantics for the new socket. * - `SOCK_NONBLOCK`: Enable nonblocking mode for the new socket. * * Returns a socket handle representing the newly created peer socket of the * accepted connection. * * Returns `null` if an error occurred. * * @function module:socket.socket#accept * * @param {object} [address] * An optional dictionary to receive the address details of the peer socket. * See {@link module:socket.socket.SocketAddress|SocketAddress} for details. * * @param {number} [flags] * Optional flags to modify the behavior of the peer socket. * * @returns {?module:socket.socket} * * @example * const sock = socket.create(…); * sock.bind(…); * sock.listen(); * * const peerAddress = {}; * const newSocket = sock.accept(peerAddress, socket.SOCK_CLOEXEC); * if (newSocket) * print(`Accepted connection from: ${peerAddress}\n`); * else * print(`Failed to accept connection: ${sock.error()}\n`); */ static uc_value_t * uc_socket_inst_accept(uc_vm_t *vm, size_t nargs) { struct sockaddr_storage ss = { 0 }; int peerfd, sockfd, sockflags; uc_value_t *addrobj, *flags; socklen_t slen; args_get(vm, nargs, &sockfd, "address", UC_OBJECT, true, &addrobj, "flags", UC_INTEGER, true, &flags); slen = sizeof(ss); sockflags = flags ? ucv_to_integer(flags) : 0; #ifdef __APPLE__ peerfd = accept(sockfd, (struct sockaddr *)&ss, &slen); if (peerfd == -1) err_return(errno, "accept()"); if (sockflags & SOCK_CLOEXEC) { if (fcntl(peerfd, F_SETFD, FD_CLOEXEC) == -1) { close(peerfd); err_return(errno, "fcntl(F_SETFD)"); } } if (sockflags & SOCK_NONBLOCK) { sockflags = fcntl(peerfd, F_GETFL); if (sockflags == -1) { close(peerfd); err_return(errno, "fcntl(F_GETFL)"); } if (fcntl(peerfd, F_SETFL, sockflags | O_NONBLOCK) == -1) { close(peerfd); err_return(errno, "fcntl(F_SETFL)"); } } #else peerfd = accept4(sockfd, (struct sockaddr *)&ss, &slen, sockflags); if (peerfd == -1) err_return(errno, "accept4()"); #endif if (addrobj) sockaddr_to_uv(&ss, addrobj); ok_return(ucv_socket_new(vm, peerfd)); } /** * Shutdown part of a full-duplex connection. * * This function shuts down part of the full-duplex connection associated with * the socket handle. The `how` parameter specifies which half of the connection * to shut down. It can take one of the following constant values: * * - `SHUT_RD`: Disables further receive operations. * - `SHUT_WR`: Disables further send operations. * - `SHUT_RDWR`: Disables further send and receive operations. * * Returns `true` if the shutdown operation is successful. * Returns `null` if an error occurred. * * @function module:socket.socket#shutdown * * @param {number} how * Specifies which half of the connection to shut down. * It can be one of the following constant values: `SHUT_RD`, `SHUT_WR`, * or `SHUT_RDWR`. * * @returns {?boolean} * * @example * const sock = socket.create(…); * sock.connect(…); * // Perform data exchange… * * const success = sock.shutdown(socket.SHUT_WR); * if (success) * print(`Send operations on socket shut down successfully.\n`); * else * print(`Failed to shut down send operations: ${sock.error()}\n`); */ static uc_value_t * uc_socket_inst_shutdown(uc_vm_t *vm, size_t nargs) { uc_value_t *how; int sockfd, ret; args_get(vm, nargs, &sockfd, "how", UC_INTEGER, true, &how); ret = shutdown(sockfd, ucv_int64_get(how)); if (ret == -1) err_return(errno, "shutdown()"); ok_return(ucv_boolean_new(true)); } /** * Represents a credentials information object returned by * {@link module:socket.socket#peercred|`peercred()`}. * * @typedef {Object} module:socket.socket.PeerCredentials * @property {number} uid * The effective user ID the remote socket endpoint. * * @property {number} gid * The effective group ID the remote socket endpoint. * * @property {number} pid * The ID of the process the remote socket endpoint belongs to. */ /** * Retrieves the peer credentials. * * This function retrieves the remote uid, gid and pid of a connected UNIX * domain socket. * * Returns the remote credentials if the operation is successful. * Returns `null` on error. * * @function module:socket.socket#peercred * * @returns {?module:socket.socket.PeerCredentials} * * @example * const sock = socket.create(socket.AF_UNIX, …); * sock.connect(…); * * const peerCredentials = sock.peercred(); * if (peerCredentials) * print(`Peer credentials: ${peerCredentials}\n`); * else * print(`Failed to retrieve peer credentials: ${sock.error()}\n`); */ static uc_value_t * uc_socket_inst_peercred(uc_vm_t *vm, size_t nargs) { uc_value_t *rv = NULL; socklen_t optlen; int ret, sockfd; args_get(vm, nargs, &sockfd); #if defined(__linux__) struct ucred cred; optlen = sizeof(cred); ret = getsockopt(sockfd, SOL_SOCKET, SO_PEERCRED, &cred, &optlen); if (ret == -1) err_return(errno, "getsockopt()"); if (optlen != sizeof(cred)) err_return(EINVAL, "Invalid credentials received"); rv = ucv_object_new(vm); ucv_object_add(rv, "uid", ucv_uint64_new(cred.uid)); ucv_object_add(rv, "gid", ucv_uint64_new(cred.gid)); ucv_object_add(rv, "pid", ucv_int64_new(cred.pid)); #elif defined(__APPLE__) struct xucred cred; pid_t pid; optlen = sizeof(cred); ret = getsockopt(sockfd, SOL_LOCAL, LOCAL_PEERCRED, &cred, &optlen); if (ret == -1) err_return(errno, "getsockopt(LOCAL_PEERCRED)"); if (optlen != sizeof(cred) || cred.cr_version != XUCRED_VERSION) err_return(EINVAL, "Invalid credentials received"); rv = ucv_object_new(vm); ucv_object_add(rv, "uid", ucv_uint64_new(cred.cr_uid)); ucv_object_add(rv, "gid", ucv_uint64_new(cred.cr_gid)); optlen = sizeof(pid); ret = getsockopt(sockfd, SOL_LOCAL, LOCAL_PEERPID, &pid, &optlen); if (ret == -1) { ucv_put(rv); err_return(errno, "getsockopt(LOCAL_PEERPID)"); } ucv_object_add(rv, "pid", ucv_int64_new(pid)); #else err_return(ENOSYS, "Operation not supported on this system"); #endif return rv; } /** * Retrieves the remote address. * * This function retrieves the remote address of a connected socket. * * Returns the remote address if the operation is successful. * Returns `null` on error. * * @function module:socket.socket#peername * * @returns {?module:socket.socket.SocketAddress} * * @see {@link module:socket.socket#sockname|sockname()} * * @example * const sock = socket.create(…); * sock.connect(…); * * const peerAddress = sock.peername(); * if (peerAddress) * print(`Connected to ${peerAddress}\n`); * else * print(`Failed to retrieve peer address: ${sock.error()}\n`); */ static uc_value_t * uc_socket_inst_peername(uc_vm_t *vm, size_t nargs) { struct sockaddr_storage ss = { 0 }; uc_value_t *addr; socklen_t sslen; int sockfd, ret; args_get(vm, nargs, &sockfd); sslen = sizeof(ss); ret = getpeername(sockfd, (struct sockaddr *)&ss, &sslen); if (ret == -1) err_return(errno, "getpeername()"); addr = ucv_object_new(vm); sockaddr_to_uv(&ss, addr); ok_return(addr); } /** * Retrieves the local address. * * This function retrieves the local address of a bound or connected socket. * * Returns the local address if the operation is successful. * Returns `null` on error. * * @function module:socket.socket#sockname * * @returns {?module:socket.socket.SocketAddress} * * @see {@link module:socket.socket#peername|peername()} * * @example * const sock = socket.create(…); * sock.connect(…); * * const myAddress = sock.sockname(); * if (myAddress) * print(`My source IP address is ${myAddress}\n`); * else * print(`Failed to retrieve peer address: ${sock.error()}\n`); */ static uc_value_t * uc_socket_inst_sockname(uc_vm_t *vm, size_t nargs) { struct sockaddr_storage ss = { 0 }; uc_value_t *addr; socklen_t sslen; int sockfd, ret; args_get(vm, nargs, &sockfd); sslen = sizeof(ss); ret = getsockname(sockfd, (struct sockaddr *)&ss, &sslen); if (ret == -1) err_return(errno, "getsockname()"); addr = ucv_object_new(vm); sockaddr_to_uv(&ss, addr); ok_return(addr); } /** * Closes the socket. * * This function closes the socket, releasing its resources and terminating its * associated connections. * * Returns `true` if the socket was successfully closed. * Returns `null` on error. * * @function module:socket.socket#close * * @returns {?boolean} * * @example * const sock = socket.create(…); * sock.connect(…); * // Perform operations with the socket… * sock.close(); */ static uc_value_t * uc_socket_inst_close(uc_vm_t *vm, size_t nargs) { int *sockfd = uc_fn_this("socket"); if (!sockfd || *sockfd == -1) err_return(EBADF, "Invalid socket context"); if (!xclose(sockfd)) err_return(errno, "close()"); ok_return(ucv_boolean_new(true)); } static void close_socket(void *ud) { int fd = (intptr_t)ud; if (fd != -1) close(fd); } static const uc_function_list_t socket_fns[] = { { "connect", uc_socket_inst_connect }, { "bind", uc_socket_inst_bind }, { "listen", uc_socket_inst_listen }, { "accept", uc_socket_inst_accept }, { "send", uc_socket_inst_send }, { "recv", uc_socket_inst_recv }, { "setopt", uc_socket_inst_setopt }, { "getopt", uc_socket_inst_getopt }, { "fileno", uc_socket_inst_fileno }, { "shutdown", uc_socket_inst_shutdown }, { "peercred", uc_socket_inst_peercred }, { "peername", uc_socket_inst_peername }, { "sockname", uc_socket_inst_sockname }, { "close", uc_socket_inst_close }, { "error", uc_socket_error }, }; static const uc_function_list_t global_fns[] = { { "sockaddr", uc_socket_sockaddr }, { "create", uc_socket_create }, { "nameinfo", uc_socket_nameinfo }, { "addrinfo", uc_socket_addrinfo }, { "poll", uc_socket_poll }, { "connect", uc_socket_connect }, { "listen", uc_socket_listen }, { "error", uc_socket_error }, }; void uc_module_init(uc_vm_t *vm, uc_value_t *scope) { uc_function_list_register(scope, global_fns); #define ADD_CONST(x) ucv_object_add(scope, #x, ucv_int64_new(x)) /** * @typedef * @name Address Families * @description Constants representing address families and socket domains. * @property {number} AF_UNSPEC - Unspecified address family. * @property {number} AF_UNIX - UNIX domain sockets. * @property {number} AF_INET - IPv4 Internet protocols. * @property {number} AF_INET6 - IPv6 Internet protocols. * @property {number} AF_PACKET - Low-level packet interface. */ ADD_CONST(AF_UNSPEC); ADD_CONST(AF_UNIX); ADD_CONST(AF_INET); ADD_CONST(AF_INET6); #if defined(__linux__) ADD_CONST(AF_PACKET); #endif /** * @typedef * @name Socket Types * @description * The `SOCK_*` type and flag constants are used by * {@link module:socket#create|create()} to specify the type of socket to * open. The {@link module:socket.socket#accept|accept()} function * recognizes the `SOCK_NONBLOCK` and `SOCK_CLOEXEC` flags and applies them * to accepted peer sockets. * @property {number} SOCK_STREAM - Provides sequenced, reliable, two-way, connection-based byte streams. * @property {number} SOCK_DGRAM - Supports datagrams (connectionless, unreliable messages of a fixed maximum length). * @property {number} SOCK_RAW - Provides raw network protocol access. * @property {number} SOCK_PACKET - Obsolete and should not be used. * @property {number} SOCK_NONBLOCK - Enables non-blocking operation. * @property {number} SOCK_CLOEXEC - Sets the close-on-exec flag on the new file descriptor. */ ADD_CONST(SOCK_STREAM); ADD_CONST(SOCK_DGRAM); ADD_CONST(SOCK_RAW); ADD_CONST(SOCK_NONBLOCK); ADD_CONST(SOCK_CLOEXEC); #if defined(__linux__) ADD_CONST(SOCK_PACKET); #endif /** * @typedef * @name Message Flags * @description * The `MSG_*` flag constants are commonly used in conjunction with the * {@link module:socket.socket#send|send()} and * {@link module:socket.socket#recv|recv()} functions. * @property {number} MSG_CONFIRM - Confirm path validity. * @property {number} MSG_DONTROUTE - Send without using routing tables. * @property {number} MSG_DONTWAIT - Enables non-blocking operation. * @property {number} MSG_EOR - End of record. * @property {number} MSG_MORE - Sender will send more. * @property {number} MSG_NOSIGNAL - Do not generate SIGPIPE. * @property {number} MSG_OOB - Process out-of-band data. * @property {number} MSG_FASTOPEN - Send data in TCP SYN. * @property {number} MSG_CMSG_CLOEXEC - Sets the close-on-exec flag on the received file descriptor. * @property {number} MSG_ERRQUEUE - Receive errors from ICMP. * @property {number} MSG_PEEK - Peeks at incoming messages. * @property {number} MSG_TRUNC - Report if datagram truncation occurred. * @property {number} MSG_WAITALL - Wait for full message. */ ADD_CONST(MSG_DONTROUTE); ADD_CONST(MSG_DONTWAIT); ADD_CONST(MSG_EOR); ADD_CONST(MSG_NOSIGNAL); ADD_CONST(MSG_OOB); ADD_CONST(MSG_PEEK); ADD_CONST(MSG_TRUNC); ADD_CONST(MSG_WAITALL); #if defined(__linux__) ADD_CONST(MSG_CONFIRM); ADD_CONST(MSG_MORE); ADD_CONST(MSG_FASTOPEN); ADD_CONST(MSG_CMSG_CLOEXEC); ADD_CONST(MSG_ERRQUEUE); #endif /** * @typedef * @name IP Protocol Constants * @description * The `IPPROTO_IP` constant specifies the IP protocol number and may be * passed as third argument to {@link module:socket#create|create()} as well * as *level* argument value to {@link module:socket.socket#getopt|getopt()} * and {@link module:socket.socket#setopt|setopt()}. * * The `IP_*` constants are option names recognized by * {@link module:socket.socket#getopt|getopt()} * and {@link module:socket.socket#setopt|setopt()}, in conjunction with * the `IPPROTO_IP` socket level. * @property {number} IPPROTO_IP - Dummy protocol for IP. * @property {number} IP_ADD_MEMBERSHIP - Add an IP group membership. * @property {number} IP_ADD_SOURCE_MEMBERSHIP - Add an IP group/source membership. * @property {number} IP_BIND_ADDRESS_NO_PORT - Bind to the device only. * @property {number} IP_BLOCK_SOURCE - Block IP group/source. * @property {number} IP_DROP_MEMBERSHIP - Drop an IP group membership. * @property {number} IP_DROP_SOURCE_MEMBERSHIP - Drop an IP group/source membership. * @property {number} IP_FREEBIND - Allow binding to an IP address not assigned to a network interface. * @property {number} IP_HDRINCL - Header is included with data. * @property {number} IP_MSFILTER - Filter IP multicast source memberships. * @property {number} IP_MTU - Path MTU discovery. * @property {number} IP_MTU_DISCOVER - Control Path MTU discovery. * @property {number} IP_MULTICAST_ALL - Receive all multicast packets. * @property {number} IP_MULTICAST_IF - Set outgoing interface for multicast packets. * @property {number} IP_MULTICAST_LOOP - Control multicast packet looping. * @property {number} IP_MULTICAST_TTL - Set time-to-live for outgoing multicast packets. * @property {number} IP_NODEFRAG - Don't fragment IP packets. * @property {number} IP_OPTIONS - Set/get IP options. * @property {number} IP_PASSSEC - Pass security information. * @property {number} IP_PKTINFO - Receive packet information. * @property {number} IP_RECVERR - Receive all ICMP errors. * @property {number} IP_RECVOPTS - Receive all IP options. * @property {number} IP_RECVORIGDSTADDR - Receive original destination address of the socket. * @property {number} IP_RECVTOS - Receive IP TOS. * @property {number} IP_RECVTTL - Receive IP TTL. * @property {number} IP_RETOPTS - Set/get IP options. * @property {number} IP_ROUTER_ALERT - Receive ICMP msgs generated by router. * @property {number} IP_TOS - IP type of service and precedence. * @property {number} IP_TRANSPARENT - Transparent proxy support. * @property {number} IP_TTL - IP time-to-live. * @property {number} IP_UNBLOCK_SOURCE - Unblock IP group/source. */ ADD_CONST(IPPROTO_IP); ADD_CONST(IP_ADD_MEMBERSHIP); ADD_CONST(IP_ADD_SOURCE_MEMBERSHIP); ADD_CONST(IP_BLOCK_SOURCE); ADD_CONST(IP_DROP_MEMBERSHIP); ADD_CONST(IP_DROP_SOURCE_MEMBERSHIP); ADD_CONST(IP_HDRINCL); ADD_CONST(IP_MSFILTER); ADD_CONST(IP_MULTICAST_IF); ADD_CONST(IP_MULTICAST_LOOP); ADD_CONST(IP_MULTICAST_TTL); ADD_CONST(IP_OPTIONS); ADD_CONST(IP_PKTINFO); ADD_CONST(IP_RECVOPTS); ADD_CONST(IP_RECVTOS); ADD_CONST(IP_RECVTTL); ADD_CONST(IP_RETOPTS); ADD_CONST(IP_TOS); ADD_CONST(IP_TTL); ADD_CONST(IP_UNBLOCK_SOURCE); #if defined(__linux__) ADD_CONST(IP_BIND_ADDRESS_NO_PORT); ADD_CONST(IP_FREEBIND); ADD_CONST(IP_MTU); ADD_CONST(IP_MTU_DISCOVER); ADD_CONST(IP_MULTICAST_ALL); ADD_CONST(IP_NODEFRAG); ADD_CONST(IP_PASSSEC); ADD_CONST(IP_RECVERR); ADD_CONST(IP_RECVORIGDSTADDR); ADD_CONST(IP_ROUTER_ALERT); ADD_CONST(IP_TRANSPARENT); #endif /** * @typedef * @name Socket Option Constants * @description * The `SOL_SOCKET` constant is passed as *level* argument to the * {@link module:socket.socket#getopt|getopt()} and * {@link module:socket.socket#setopt|setopt()} functions in order to set * or retrieve generic socket option values. * * The `SO_*` constants are passed as *option* argument in conjunction with * the `SOL_SOCKET` level to specify the specific option to get or set on * the socket. * @property {number} SOL_SOCKET - Socket options at the socket API level. * @property {number} SO_ACCEPTCONN - Reports whether socket listening is enabled. * @property {number} SO_ATTACH_BPF - Attach BPF program to socket. * @property {number} SO_ATTACH_FILTER - Attach a socket filter. * @property {number} SO_ATTACH_REUSEPORT_CBPF - Attach BPF program for cgroup and skb program reuseport hook. * @property {number} SO_ATTACH_REUSEPORT_EBPF - Attach eBPF program for cgroup and skb program reuseport hook. * @property {number} SO_BINDTODEVICE - Bind socket to a specific interface. * @property {number} SO_BROADCAST - Allow transmission of broadcast messages. * @property {number} SO_BUSY_POLL - Enable busy polling. * @property {number} SO_DEBUG - Enable socket debugging. * @property {number} SO_DETACH_BPF - Detach BPF program from socket. * @property {number} SO_DETACH_FILTER - Detach a socket filter. * @property {number} SO_DOMAIN - Retrieves the domain of the socket. * @property {number} SO_DONTROUTE - Send packets directly without routing. * @property {number} SO_ERROR - Retrieves and clears the error status for the socket. * @property {number} SO_INCOMING_CPU - Retrieves the CPU number on which the last packet was received. * @property {number} SO_INCOMING_NAPI_ID - Retrieves the NAPI ID of the device. * @property {number} SO_KEEPALIVE - Enable keep-alive packets. * @property {number} SO_LINGER - Set linger on close. * @property {number} SO_LOCK_FILTER - Set or get the socket filter lock state. * @property {number} SO_MARK - Set the mark for packets sent through the socket. * @property {number} SO_OOBINLINE - Enables out-of-band data to be received in the normal data stream. * @property {number} SO_PASSCRED - Enable the receiving of SCM_CREDENTIALS control messages. * @property {number} SO_PASSSEC - Enable the receiving of security context. * @property {number} SO_PEEK_OFF - Returns the number of bytes in the receive buffer without removing them. * @property {number} SO_PEERCRED - Retrieves the credentials of the foreign peer. * @property {number} SO_PEERSEC - Retrieves the security context of the foreign peer. * @property {number} SO_PRIORITY - Set the protocol-defined priority for all packets. * @property {number} SO_PROTOCOL - Retrieves the protocol number. * @property {number} SO_RCVBUF - Set the receive buffer size. * @property {number} SO_RCVBUFFORCE - Set the receive buffer size forcefully. * @property {number} SO_RCVLOWAT - Set the minimum number of bytes to process for input operations. * @property {number} SO_RCVTIMEO - Set the timeout for receiving data. * @property {number} SO_REUSEADDR - Allow the socket to be bound to an address that is already in use. * @property {number} SO_REUSEPORT - Enable duplicate address and port bindings. * @property {number} SO_RXQ_OVFL - Reports if the receive queue has overflown. * @property {number} SO_SNDBUF - Set the send buffer size. * @property {number} SO_SNDBUFFORCE - Set the send buffer size forcefully. * @property {number} SO_SNDLOWAT - Set the minimum number of bytes to process for output operations. * @property {number} SO_SNDTIMEO - Set the timeout for sending data. * @property {number} SO_TIMESTAMP - Enable receiving of timestamps. * @property {number} SO_TIMESTAMPNS - Enable receiving of nanosecond timestamps. * @property {number} SO_TYPE - Retrieves the type of the socket (e.g., SOCK_STREAM). */ ADD_CONST(SOL_SOCKET); ADD_CONST(SO_ACCEPTCONN); ADD_CONST(SO_BROADCAST); ADD_CONST(SO_DEBUG); ADD_CONST(SO_DONTROUTE); ADD_CONST(SO_ERROR); ADD_CONST(SO_KEEPALIVE); ADD_CONST(SO_LINGER); ADD_CONST(SO_OOBINLINE); ADD_CONST(SO_RCVBUF); ADD_CONST(SO_RCVLOWAT); ADD_CONST(SO_RCVTIMEO); ADD_CONST(SO_REUSEADDR); ADD_CONST(SO_REUSEPORT); ADD_CONST(SO_SNDBUF); ADD_CONST(SO_SNDLOWAT); ADD_CONST(SO_SNDTIMEO); ADD_CONST(SO_TIMESTAMP); ADD_CONST(SO_TYPE); #if defined(__linux__) ADD_CONST(SO_ATTACH_BPF); ADD_CONST(SO_ATTACH_FILTER); ADD_CONST(SO_ATTACH_REUSEPORT_CBPF); ADD_CONST(SO_ATTACH_REUSEPORT_EBPF); ADD_CONST(SO_BINDTODEVICE); ADD_CONST(SO_BUSY_POLL); ADD_CONST(SO_DETACH_BPF); ADD_CONST(SO_DETACH_FILTER); ADD_CONST(SO_DOMAIN); ADD_CONST(SO_INCOMING_CPU); ADD_CONST(SO_INCOMING_NAPI_ID); ADD_CONST(SO_LOCK_FILTER); ADD_CONST(SO_MARK); ADD_CONST(SO_PASSCRED); ADD_CONST(SO_PASSSEC); ADD_CONST(SO_PEEK_OFF); ADD_CONST(SO_PEERCRED); ADD_CONST(SO_PEERSEC); ADD_CONST(SO_PRIORITY); ADD_CONST(SO_PROTOCOL); ADD_CONST(SO_RCVBUFFORCE); ADD_CONST(SO_RXQ_OVFL); ADD_CONST(SO_SNDBUFFORCE); ADD_CONST(SO_TIMESTAMPNS); #endif /** * @typedef * @name TCP Protocol Constants * @description * The `IPPROTO_TCP` constant specifies the TCP protocol number and may be * passed as third argument to {@link module:socket#create|create()} as well * as *level* argument value to {@link module:socket.socket#getopt|getopt()} * and {@link module:socket.socket#setopt|setopt()}. * * The `TCP_*` constants are *option* argument values recognized by * {@link module:socket.socket#getopt|getopt()} * and {@link module:socket.socket#setopt|setopt()}, in conjunction with * the `IPPROTO_TCP` socket level. * @property {number} IPPROTO_TCP - TCP protocol. * @property {number} TCP_CONGESTION - Set the congestion control algorithm. * @property {number} TCP_CORK - Delay packet transmission until full-sized packets are available. * @property {number} TCP_DEFER_ACCEPT - Delay accepting incoming connections until data arrives. * @property {number} TCP_FASTOPEN - Enable TCP Fast Open. * @property {number} TCP_FASTOPEN_CONNECT - Perform TFO connect. * @property {number} TCP_INFO - Retrieve TCP statistics. * @property {number} TCP_KEEPCNT - Number of keepalive probes. * @property {number} TCP_KEEPIDLE - Time before keepalive probes begin. * @property {number} TCP_KEEPINTVL - Interval between keepalive probes. * @property {number} TCP_LINGER2 - Lifetime of orphaned FIN_WAIT2 state sockets. * @property {number} TCP_MAXSEG - Maximum segment size. * @property {number} TCP_NODELAY - Disable Nagle's algorithm. * @property {number} TCP_QUICKACK - Enable quick ACKs. * @property {number} TCP_SYNCNT - Number of SYN retransmits. * @property {number} TCP_USER_TIMEOUT - Set the user timeout. * @property {number} TCP_WINDOW_CLAMP - Set the maximum window. */ ADD_CONST(IPPROTO_TCP); ADD_CONST(TCP_FASTOPEN); ADD_CONST(TCP_KEEPCNT); ADD_CONST(TCP_KEEPINTVL); ADD_CONST(TCP_MAXSEG); ADD_CONST(TCP_NODELAY); #if defined(__linux__) ADD_CONST(TCP_CONGESTION); ADD_CONST(TCP_CORK); ADD_CONST(TCP_DEFER_ACCEPT); ADD_CONST(TCP_FASTOPEN_CONNECT); ADD_CONST(TCP_INFO); ADD_CONST(TCP_KEEPIDLE); ADD_CONST(TCP_LINGER2); ADD_CONST(TCP_QUICKACK); ADD_CONST(TCP_SYNCNT); ADD_CONST(TCP_USER_TIMEOUT); ADD_CONST(TCP_WINDOW_CLAMP); #endif /** * @typedef * @name Packet Socket Constants * @description * The `SOL_PACKET` constant specifies the packet socket level and may be * passed as *level* argument value to * {@link module:socket.socket#getopt|getopt()} and * {@link module:socket.socket#setopt|setopt()}. * * Most `PACKET_*` constants are *option* argument values recognized by * {@link module:socket.socket#getopt|getopt()} * and {@link module:socket.socket#setopt|setopt()}, in conjunction with * the `SOL_PACKET` socket level. * * The constants `PACKET_MR_PROMISC`, `PACKET_MR_MULTICAST` and * `PACKET_MR_ALLMULTI` are used in conjunction with the * `PACKET_ADD_MEMBERSHIP` and `PACKET_DROP_MEMBERSHIP` options to specify * the packet socket receive mode. * * The constants `PACKET_HOST`, `PACKET_BROADCAST`, `PACKET_MULTICAST`, * `PACKET_OTHERHOST` and `PACKET_OUTGOING` may be used as *packet_type* * value in {@link module:socket.socket.SocketAddress|socket address} * structures. * @property {number} SOL_PACKET - Socket options at the packet API level. * @property {number} PACKET_ADD_MEMBERSHIP - Add a multicast group membership. * @property {number} PACKET_DROP_MEMBERSHIP - Drop a multicast group membership. * @property {number} PACKET_AUXDATA - Receive auxiliary data (packet info). * @property {number} PACKET_FANOUT - Configure packet fanout. * @property {number} PACKET_LOSS - Retrieve the current packet loss statistics. * @property {number} PACKET_RESERVE - Reserve space for packet headers. * @property {number} PACKET_RX_RING - Configure a receive ring buffer. * @property {number} PACKET_STATISTICS - Retrieve packet statistics. * @property {number} PACKET_TIMESTAMP - Retrieve packet timestamps. * @property {number} PACKET_TX_RING - Configure a transmit ring buffer. * @property {number} PACKET_VERSION - Set the packet protocol version. * @property {number} PACKET_QDISC_BYPASS - Bypass queuing discipline for outgoing packets. * * @property {number} PACKET_MR_PROMISC - Enable promiscuous mode. * @property {number} PACKET_MR_MULTICAST - Receive multicast packets. * @property {number} PACKET_MR_ALLMULTI - Receive all multicast packets. * * @property {number} PACKET_HOST - Receive packets destined for this host. * @property {number} PACKET_BROADCAST - Receive broadcast packets. * @property {number} PACKET_MULTICAST - Receive multicast packets. * @property {number} PACKET_OTHERHOST - Receive packets destined for other hosts. * @property {number} PACKET_OUTGOING - Transmit packets. */ #if defined(__linux__) ADD_CONST(SOL_PACKET); ADD_CONST(PACKET_ADD_MEMBERSHIP); ADD_CONST(PACKET_DROP_MEMBERSHIP); ADD_CONST(PACKET_AUXDATA); ADD_CONST(PACKET_FANOUT); ADD_CONST(PACKET_LOSS); ADD_CONST(PACKET_RESERVE); ADD_CONST(PACKET_RX_RING); ADD_CONST(PACKET_STATISTICS); ADD_CONST(PACKET_TIMESTAMP); ADD_CONST(PACKET_TX_RING); ADD_CONST(PACKET_VERSION); ADD_CONST(PACKET_QDISC_BYPASS); ADD_CONST(PACKET_MR_PROMISC); ADD_CONST(PACKET_MR_MULTICAST); ADD_CONST(PACKET_MR_ALLMULTI); ADD_CONST(PACKET_HOST); ADD_CONST(PACKET_BROADCAST); ADD_CONST(PACKET_MULTICAST); ADD_CONST(PACKET_OTHERHOST); ADD_CONST(PACKET_OUTGOING); #endif /** * @typedef * @name UDP Protocol Constants * @description * The `IPPROTO_UDP` constant specifies the UDP protocol number and may be * passed as third argument to {@link module:socket#create|create()} as well * as *level* argument value to {@link module:socket.socket#getopt|getopt()} * and {@link module:socket.socket#setopt|setopt()}. * * The `UDP_*` constants are *option* argument values recognized by * {@link module:socket.socket#getopt|getopt()} * and {@link module:socket.socket#setopt|setopt()}, in conjunction with * the `IPPROTO_UDP` socket level. * @property {number} IPPROTO_UDP - UDP protocol. * @property {number} UDP_CORK - Cork data until flush. */ ADD_CONST(IPPROTO_UDP); #if defined(__linux__) ADD_CONST(UDP_CORK); #endif /** * @typedef * @name Shutdown Constants * @description * The `SHUT_*` constants are passed as argument to the * {@link module:socket.socket#shutdown|shutdown()} function to specify * which direction of a full duplex connection to shut down. * @property {number} SHUT_RD - Disallow further receptions. * @property {number} SHUT_WR - Disallow further transmissions. * @property {number} SHUT_RDWR - Disallow further receptions and transmissions. */ ADD_CONST(SHUT_RD); ADD_CONST(SHUT_WR); ADD_CONST(SHUT_RDWR); /** * @typedef * @name Address Info Flags * @description * The `AI_*` flags may be passed as bitwise OR-ed number in the *flags* * property of the *hints* dictionary argument of * {@link module:socket#addrinfo|addrinfo()}. * @property {number} AI_ADDRCONFIG - Address configuration flag. * @property {number} AI_ALL - Return IPv4 and IPv6 socket addresses. * @property {number} AI_CANONIDN - Canonicalize using the IDNA standard. * @property {number} AI_CANONNAME - Fill in the canonical name field. * @property {number} AI_IDN - Enable IDN encoding. * @property {number} AI_NUMERICHOST - Prevent hostname resolution. * @property {number} AI_NUMERICSERV - Prevent service name resolution. * @property {number} AI_PASSIVE - Use passive socket. * @property {number} AI_V4MAPPED - Map IPv6 addresses to IPv4-mapped format. */ ADD_CONST(AI_ADDRCONFIG); ADD_CONST(AI_ALL); ADD_CONST(AI_CANONIDN); ADD_CONST(AI_CANONNAME); ADD_CONST(AI_IDN); ADD_CONST(AI_NUMERICHOST); ADD_CONST(AI_NUMERICSERV); ADD_CONST(AI_PASSIVE); ADD_CONST(AI_V4MAPPED); /** * @typedef * @name Name Info Constants * @description * The `NI_*` flags may be passed as bitwise OR-ed number via the *flags* * argument of {@link module:socket#nameinfo|nameinfo()}. * @property {number} NI_DGRAM - Datagram socket type. * @property {number} NI_IDN - Enable IDN encoding. * @property {number} NI_NAMEREQD - Hostname resolution required. * @property {number} NI_NOFQDN - Do not force fully qualified domain name. * @property {number} NI_NUMERICHOST - Return numeric form of the hostname. * @property {number} NI_NUMERICSERV - Return numeric form of the service name. */ ADD_CONST(NI_DGRAM); ADD_CONST(NI_IDN); ADD_CONST(NI_MAXHOST); ADD_CONST(NI_MAXSERV); ADD_CONST(NI_NAMEREQD); ADD_CONST(NI_NOFQDN); ADD_CONST(NI_NUMERICHOST); ADD_CONST(NI_NUMERICSERV); /** * @typedef * @name Poll Event Constants * @description * The following constants represent event types for polling operations and * are set or returned as part of a * {@link module:socket.PollSpec|PollSpec} tuple by the * {@link module:socket#poll|poll()} function. When passed via an argument * PollSpec to `poll()`, they specify the I/O events to watch for on the * corresponding handle. When appearing in a PollSpec returned by `poll()`, * they specify the I/O events that occurred on a watched handle. * @property {number} POLLIN - Data available to read. * @property {number} POLLPRI - Priority data available to read. * @property {number} POLLOUT - Writable data available. * @property {number} POLLERR - Error condition. * @property {number} POLLHUP - Hang up. * @property {number} POLLNVAL - Invalid request. * @property {number} POLLRDHUP - Peer closed or shutdown writing. */ ADD_CONST(POLLIN); ADD_CONST(POLLPRI); ADD_CONST(POLLOUT); ADD_CONST(POLLERR); ADD_CONST(POLLHUP); ADD_CONST(POLLNVAL); #if defined(__linux__) ADD_CONST(POLLRDHUP); #endif uc_type_declare(vm, "socket", socket_fns, close_socket); }