/* * 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 #include #include #include "ucode/module.h" #include "ucode/platform.h" #if defined(__linux__) # include # ifndef SO_TIMESTAMP_OLD # define SO_TIMESTAMP_OLD SO_TIMESTAMP # endif # ifndef SO_TIMESTAMPNS_OLD # define SO_TIMESTAMPNS_OLD SO_TIMESTAMP # endif #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 #ifndef IPV6_FLOWINFO # define IPV6_FLOWINFO 11 #endif #ifndef IPV6_FLOWLABEL_MGR # define IPV6_FLOWLABEL_MGR 32 #endif #ifndef IPV6_FLOWINFO_SEND # define IPV6_FLOWINFO_SEND 33 #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) { size_t buffer_size; uc_string_t *us; if (!sb || !*sb) return NULL; buffer_size = strbuf_size(*sb); us = (uc_string_t *)(*sb)->buf; 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 int64_t parse_integer(char *s, size_t len) { union { int8_t i8; int16_t i16; int32_t i32; int64_t i64; } v; memcpy(&v, s, len < sizeof(v) ? len : sizeof(v)); switch (len) { case 1: return v.i8; case 2: return v.i16; case 4: return v.i32; case 8: return v.i64; default: return 0; } } static uint64_t parse_unsigned(char *s, size_t len) { union { uint8_t u8; uint16_t u16; uint32_t u32; uint64_t u64; } v; memcpy(&v, s, len < sizeof(v) ? len : sizeof(v)); switch (len) { case 1: return v.u8; case 2: return v.u16; case 4: return v.u32; case 8: return v.u64; default: return 0; } } 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) { if (len >= sizeof(su->sun_path)) len = sizeof(su->sun_path) - 1; memcpy(su->sun_path, s, len); su->sun_path[len++] = 0; su->sun_family = AF_UNIX; *slen = sizeof(*su); 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 = sizeof(*su); 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 uc_value_t * uv_to_pollfd(uc_vm_t *vm, uc_value_t *val, struct pollfd *pfd) { uc_value_t *rv; int64_t flags; if (ucv_type(val) == UC_ARRAY) { if (!uv_to_fileno(vm, ucv_array_get(val, 0), &pfd->fd)) return NULL; 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; return ucv_get(val); } if (!uv_to_fileno(vm, val, &pfd->fd)) return NULL; pfd->events = POLLIN | POLLERR | POLLHUP; pfd->revents = 0; rv = ucv_array_new_length(vm, 2); ucv_array_set(rv, 0, ucv_get(val)); ucv_array_set(rv, 1, ucv_uint64_new(pfd->events)); return rv; } 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_IPV6ADDR, 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; typedef struct { int level; int type; struct_t *ctype; } cmsgtype_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 } } }; /* This structure is declared in kernel, but not libc headers, so redeclare it locally */ struct in6_flowlabel_req_local { struct in6_addr flr_dst; uint32_t flr_label; uint8_t flr_action; uint8_t flr_share; uint16_t flr_flags; uint16_t flr_expires; uint16_t flr_linger; }; static struct_t st_in6_flowlabel_req = { .size = sizeof(struct in6_flowlabel_req_local), .members = (member_t []){ STRUCT_MEMBER(in6_flowlabel_req_local, flr, dst, DT_IPV6ADDR), STRUCT_MEMBER(in6_flowlabel_req_local, flr, label, DT_UNSIGNED), STRUCT_MEMBER(in6_flowlabel_req_local, flr, action, DT_UNSIGNED), STRUCT_MEMBER(in6_flowlabel_req_local, flr, share, DT_UNSIGNED), STRUCT_MEMBER(in6_flowlabel_req_local, flr, flags, DT_UNSIGNED), STRUCT_MEMBER(in6_flowlabel_req_local, flr, expires, DT_UNSIGNED), STRUCT_MEMBER(in6_flowlabel_req_local, flr, linger, DT_UNSIGNED), { 0 } } }; #if defined(__linux__) static uc_value_t * in6_ifindex_to_uv(void *st) { char ifname[IF_NAMESIZE] = { 0 }; struct ipv6_mreq *mr = st; if (mr->ipv6mr_interface > 0 && if_indextoname(mr->ipv6mr_interface, ifname)) return ucv_string_new(ifname); return ucv_int64_new(mr->ipv6mr_interface); } static bool in6_ifindex_to_c(void *st, uc_value_t *uv) { struct ipv6_mreq *mr = st; if (ucv_type(uv) == UC_STRING) { mr->ipv6mr_interface = if_nametoindex(ucv_string_get(uv)); if (mr->ipv6mr_interface == 0) err_return(errno, "Unable to resolve interface %s", ucv_string_get(uv)); } else { mr->ipv6mr_interface = ucv_to_integer(uv); if (errno) err_return(errno, "Unable to convert interface to integer"); } return true; } static struct_t st_ipv6_mreq = { .size = sizeof(struct ipv6_mreq), .members = (member_t []){ STRUCT_MEMBER(ipv6_mreq, ipv6mr, multiaddr, DT_IPV6ADDR), STRUCT_MEMBER_CB(interface, in6_ifindex_to_c, in6_ifindex_to_uv), { 0 } } }; /* NB: this is the same layout as struct ipv6_mreq, so we reuse the callbacks */ static struct_t st_in6_pktinfo = { .size = sizeof(struct in6_pktinfo), .members = (member_t []){ STRUCT_MEMBER(in6_pktinfo, ipi6, addr, DT_IPV6ADDR), STRUCT_MEMBER_CB(interface, in6_ifindex_to_c, in6_ifindex_to_uv), { 0 } } }; struct ipv6_recv_error_local { struct { uint32_t ee_errno; uint8_t ee_origin; uint8_t ee_type; uint8_t ee_code; uint8_t ee_pad; uint32_t ee_info; union { uint32_t ee_data; struct { uint16_t ee_len; uint8_t ee_flags; uint8_t ee_reserved; } ee_rfc4884; } u; } ee; struct sockaddr_in6 offender; }; static uc_value_t * offender_to_uv(void *st) { struct ipv6_recv_error_local *e = st; uc_value_t *addr = ucv_object_new(NULL); if (sockaddr_to_uv((struct sockaddr_storage *)&e->offender, addr)) return addr; ucv_put(addr); return NULL; } static struct_t st_ip_recv_error = { .size = sizeof(struct ipv6_recv_error_local), .members = (member_t []){ STRUCT_MEMBER(ipv6_recv_error_local, ee.ee, errno, DT_UNSIGNED), STRUCT_MEMBER(ipv6_recv_error_local, ee.ee, origin, DT_UNSIGNED), STRUCT_MEMBER(ipv6_recv_error_local, ee.ee, type, DT_UNSIGNED), STRUCT_MEMBER(ipv6_recv_error_local, ee.ee, code, DT_UNSIGNED), STRUCT_MEMBER(ipv6_recv_error_local, ee.ee, info, DT_UNSIGNED), STRUCT_MEMBER(ipv6_recv_error_local, ee.u.ee, data, DT_UNSIGNED), STRUCT_MEMBER(ipv6_recv_error_local, ee.u.ee_rfc4884.ee, len, DT_UNSIGNED), STRUCT_MEMBER(ipv6_recv_error_local, ee.u.ee_rfc4884.ee, flags, DT_UNSIGNED), STRUCT_MEMBER_CB(offender, NULL, offender_to_uv), { 0 } } }; static uc_value_t * ip6m_addr_to_uv(void *st) { struct ip6_mtuinfo *mi = st; uc_value_t *addr = ucv_object_new(NULL); if (sockaddr_to_uv((struct sockaddr_storage *)&mi->ip6m_addr, addr)) return addr; ucv_put(addr); return NULL; } static struct_t st_ip6_mtuinfo = { .size = sizeof(struct ip6_mtuinfo), .members = (member_t []){ STRUCT_MEMBER_CB(addr, NULL, ip6m_addr_to_uv), STRUCT_MEMBER(ip6_mtuinfo, ip6m, mtu, DT_UNSIGNED), { 0 } } }; 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_tpacket_auxdata = { .size = sizeof(struct tpacket_auxdata), .members = (member_t []){ STRUCT_MEMBER(tpacket_auxdata, tp, status, DT_UNSIGNED), STRUCT_MEMBER(tpacket_auxdata, tp, len, DT_UNSIGNED), STRUCT_MEMBER(tpacket_auxdata, tp, snaplen, DT_UNSIGNED), STRUCT_MEMBER(tpacket_auxdata, tp, mac, DT_UNSIGNED), STRUCT_MEMBER(tpacket_auxdata, tp, net, DT_UNSIGNED), STRUCT_MEMBER(tpacket_auxdata, tp, vlan_tci, DT_UNSIGNED), STRUCT_MEMBER(tpacket_auxdata, tp, vlan_tpid, 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 } } }; struct timeval_old_local { long tv_sec; #if defined(__sparc__) && defined(__arch64__) int tv_usec; #else long tv_usec; #endif }; static struct_t st_timeval_old = { .size = sizeof(struct timeval_old_local), .members = (member_t []){ STRUCT_MEMBER(timeval_old_local, tv, sec, DT_SIGNED), STRUCT_MEMBER(timeval_old_local, tv, usec, DT_SIGNED), { 0 } } }; # ifdef SO_TIMESTAMP_NEW struct timeval_new_local { int64_t tv_sec; int64_t tv_usec; }; static struct_t st_timeval_new = { .size = sizeof(struct timeval_old_local), .members = (member_t []){ STRUCT_MEMBER(timeval_new_local, tv, sec, DT_SIGNED), STRUCT_MEMBER(timeval_new_local, tv, usec, DT_SIGNED), { 0 } } }; # endif struct timespec_old_local { long tv_sec; long tv_nsec; }; static struct_t st_timespec_old = { .size = sizeof(struct timespec_old_local), .members = (member_t []){ STRUCT_MEMBER(timespec_old_local, tv, sec, DT_SIGNED), STRUCT_MEMBER(timespec_old_local, tv, nsec, DT_SIGNED), { 0 } } }; # ifdef SO_TIMESTAMPNS_NEW struct timespec_new_local { long long tv_sec; long long tv_nsec; }; static struct_t st_timespec_new = { .size = sizeof(struct timespec_new_local), .members = (member_t []){ STRUCT_MEMBER(timespec_new_local, tv, sec, DT_SIGNED), STRUCT_MEMBER(timespec_new_local, tv, nsec, DT_SIGNED), { 0 } } }; # endif #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 #define SV_IFNAME (struct_t *)5 #define CV_INT (struct_t *)0 #define CV_UINT (struct_t *)1 #define CV_BE32 (struct_t *)2 #define CV_STRING (struct_t *)3 #define CV_SOCKADDR (struct_t *)4 #define CV_FDS (struct_t *)5 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_IPV6, IPV6_FLOWINFO_SEND, SV_BOOL }, { IPPROTO_IPV6, IPV6_FLOWINFO, SV_BOOL }, { IPPROTO_IPV6, IPV6_FLOWLABEL_MGR, &st_in6_flowlabel_req }, { IPPROTO_IPV6, IPV6_MULTICAST_HOPS, SV_INT }, { IPPROTO_IPV6, IPV6_MULTICAST_IF, SV_IFNAME }, { IPPROTO_IPV6, IPV6_MULTICAST_LOOP, SV_BOOL }, { IPPROTO_IPV6, IPV6_RECVTCLASS, SV_BOOL }, { IPPROTO_IPV6, IPV6_TCLASS, SV_INT }, { IPPROTO_IPV6, IPV6_UNICAST_HOPS, SV_INT }, { IPPROTO_IPV6, IPV6_V6ONLY, SV_BOOL }, #if defined(__linux__) { IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &st_ipv6_mreq }, { IPPROTO_IPV6, IPV6_ADDR_PREFERENCES, SV_INT }, { IPPROTO_IPV6, IPV6_ADDRFORM, SV_INT }, { IPPROTO_IPV6, IPV6_AUTHHDR, SV_BOOL }, { IPPROTO_IPV6, IPV6_AUTOFLOWLABEL, SV_BOOL }, { IPPROTO_IPV6, IPV6_DONTFRAG, SV_BOOL }, { IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &st_ipv6_mreq }, { IPPROTO_IPV6, IPV6_DSTOPTS, SV_STRING }, { IPPROTO_IPV6, IPV6_FREEBIND, SV_BOOL }, { IPPROTO_IPV6, IPV6_HOPLIMIT, SV_BOOL }, { IPPROTO_IPV6, IPV6_HOPOPTS, SV_STRING }, { IPPROTO_IPV6, IPV6_JOIN_ANYCAST, &st_ipv6_mreq }, { IPPROTO_IPV6, IPV6_LEAVE_ANYCAST, &st_ipv6_mreq }, { IPPROTO_IPV6, IPV6_MINHOPCOUNT, SV_INT }, { IPPROTO_IPV6, IPV6_MTU_DISCOVER, SV_INT }, { IPPROTO_IPV6, IPV6_MTU, SV_INT }, { IPPROTO_IPV6, IPV6_MULTICAST_ALL, SV_BOOL }, { IPPROTO_IPV6, IPV6_PKTINFO, &st_in6_pktinfo }, { IPPROTO_IPV6, IPV6_RECVDSTOPTS, SV_BOOL }, { IPPROTO_IPV6, IPV6_RECVERR, SV_BOOL }, { IPPROTO_IPV6, IPV6_RECVFRAGSIZE, SV_BOOL }, { IPPROTO_IPV6, IPV6_RECVHOPLIMIT, SV_BOOL }, { IPPROTO_IPV6, IPV6_RECVHOPOPTS, SV_BOOL }, { IPPROTO_IPV6, IPV6_RECVORIGDSTADDR, SV_BOOL }, { IPPROTO_IPV6, IPV6_RECVPATHMTU, SV_BOOL }, { IPPROTO_IPV6, IPV6_RECVPKTINFO, SV_BOOL }, { IPPROTO_IPV6, IPV6_RECVRTHDR, SV_BOOL }, { IPPROTO_IPV6, IPV6_ROUTER_ALERT_ISOLATE, SV_BOOL }, { IPPROTO_IPV6, IPV6_ROUTER_ALERT, SV_BOOL }, { IPPROTO_IPV6, IPV6_RTHDR, SV_STRING }, { IPPROTO_IPV6, IPV6_RTHDRDSTOPTS, SV_STRING }, { IPPROTO_IPV6, IPV6_TRANSPARENT, SV_BOOL }, { IPPROTO_IPV6, IPV6_UNICAST_IF, SV_IFNAME }, #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 cmsgtype_t cmsgtypes[] = { #if defined(__linux__) { SOL_PACKET, PACKET_AUXDATA, &st_tpacket_auxdata }, { SOL_SOCKET, SO_TIMESTAMP_OLD, &st_timeval_old }, # ifdef SO_TIMESTAMP_NEW { SOL_SOCKET, SO_TIMESTAMP_NEW, &st_timeval_new }, # endif { SOL_SOCKET, SO_TIMESTAMPNS_OLD, &st_timespec_old }, # ifdef SO_TIMESTAMPNS_NEW { SOL_SOCKET, SO_TIMESTAMPNS_NEW, &st_timespec_new }, # endif { SOL_SOCKET, SCM_CREDENTIALS, &st_ucred }, { SOL_SOCKET, SCM_RIGHTS, CV_FDS }, #endif { IPPROTO_IP, IP_RECVOPTS, SV_STRING }, { IPPROTO_IP, IP_RETOPTS, SV_STRING }, { IPPROTO_IP, IP_TOS, CV_INT }, { IPPROTO_IP, IP_TTL, CV_INT }, #if defined(__linux__) { IPPROTO_IP, IP_CHECKSUM, CV_UINT }, { IPPROTO_IP, IP_ORIGDSTADDR, CV_SOCKADDR }, { IPPROTO_IP, IP_RECVERR, &st_ip_recv_error }, { IPPROTO_IP, IP_RECVFRAGSIZE, CV_INT }, #endif { IPPROTO_IPV6, IPV6_TCLASS, CV_INT }, { IPPROTO_IPV6, IPV6_FLOWINFO, CV_BE32 }, #if defined(__linux__) { IPPROTO_IPV6, IPV6_DSTOPTS, CV_STRING }, { IPPROTO_IPV6, IPV6_HOPLIMIT, CV_INT }, { IPPROTO_IPV6, IPV6_HOPOPTS, CV_STRING }, { IPPROTO_IPV6, IPV6_ORIGDSTADDR, CV_SOCKADDR }, { IPPROTO_IPV6, IPV6_PATHMTU, &st_ip6_mtuinfo }, { IPPROTO_IPV6, IPV6_PKTINFO, &st_in6_pktinfo }, { IPPROTO_IPV6, IPV6_RECVERR, &st_ip_recv_error }, { IPPROTO_IPV6, IPV6_RECVFRAGSIZE, CV_INT }, { IPPROTO_IPV6, IPV6_RTHDR, CV_STRING }, { IPPROTO_TCP, TCP_CM_INQ, CV_INT }, { IPPROTO_UDP, UDP_GRO, CV_INT }, #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_IPV6ADDR: s = ucv_string_get(fv); if (!s || inet_pton(AF_INET6, s, st + m->u1.offset) != 1) { free(st); err_return(EINVAL, "Unable to convert field %s to IPv6 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("ffff:ffff:ffff:ffff:ffff:ffff: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_IPV6ADDR: if (inet_ntop(AF_INET6, 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; case (uintptr_t)SV_IFNAME: if (ucv_type(value) == UC_STRING) { soval = if_nametoindex(ucv_string_get(value)); if (soval <= 0) err_return(errno, "Unable to resolve interface %s", ucv_string_get(value)); } else { soval = ucv_to_integer(value); if (errno) err_return(errno, "Unable to convert value to integer"); } valptr = &soval; vallen = sizeof(int); 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; char ival[sizeof(int64_t)] = { 0 }; void *valptr = NULL, *st = NULL; int sockfd, solvl, soopt, ret; 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); 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: case (uintptr_t)SV_IFNAME: valptr = ival; vallen = sizeof(ival); 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) { char ifname[IF_NAMESIZE]; int ifidx; 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(parse_integer(ival, vallen)); break; case (uintptr_t)SV_BOOL: value = ucv_boolean_new(parse_integer(ival, vallen) != 0); break; case (uintptr_t)SV_STRING: value = strbuf_finish(&sb, vallen); break; case (uintptr_t)SV_IFNAME: ifidx = parse_integer(ival, vallen); if (if_indextoname(ifidx, ifname)) value = ucv_string_new(ifname); else value = ucv_int64_new(ifidx); 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, flags]` tuples for each socket with pending * events. When a tuple is passed as socket argument, it is included as-is into * the result tuple array, with the flags entry changed to a bitwise OR-ed value * describing the pending events for this socket. When a plain socket instance * (or another kind of handle) is passed, a new tuple array is created for this * socket within the result tuple array, containing this socket as first and the * bitwise OR-ed pending events as second element. * * 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 any other kind of handle * implementing a `fileno()` method) or a `[socket, flags]` tuple specifying the * socket and requested poll flags. If a plain socket (or other kind of handle) * instead of a tuple is provided, the requested poll flags default to * `POLLIN|POLLERR|POLLHUP` for this socket. * * @returns {module:socket.PollSpec[]} * * @example * let x = socket.connect("example.org", 80); * let y = socket.connect("example.com", 80); * * // Pass plain socket arguments * let events = socket.poll(10, x, y); * print(events); // [ [ "", 0 ], [ "", 0 ] ] * * // Passing tuples allows attaching state information and requesting * // different I/O events * let events = socket.poll(10, * [ x, socket.POLLOUT | socket.POLLHUP, "This is example.org" ], * [ y, socket.POLLOUT | socket.POLLHUP, "This is example.com" ] * ); * print(events); // [ [ "", 4, "This is example.org" ], * // [ "", 4, "This is example.com" ] ] */ 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); item = uv_to_pollfd(vm, uc_fn_arg(i), &pfds.entries[pfds.count]); if (item) 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); } static bool should_resolve(uc_value_t *host) { char *s = ucv_string_get(host); return (s != NULL && memchr(s, '/', ucv_string_length(host)) == NULL); } /** * 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 (should_resolve(host)) { 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)) { free(ai_hints); uc_vector_clear(&addresses); 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 || should_resolve(host)) { 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); } int default_socktype = SOCK_STREAM; if (ss.ss_family != AF_INET && ss.ss_family != AF_INET6) default_socktype = SOCK_DGRAM; socktype = ai_hints ? ai_hints->ai_socktype : default_socktype; 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 && errno != EOPNOTSUPP) { 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 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)); } uc_declare_vector(strbuf_array_t, uc_stringbuf_t *); #if defined(__linux__) static void optmem_max(size_t *sz) { char buf[sizeof("18446744073709551615")] = { 0 }; int fd, rv; fd = open("/proc/sys/net/core/optmem_max", O_RDONLY); if (fd >= 0) { if (read(fd, buf, sizeof(buf) - 1) > 0) { rv = strtol(buf, NULL, 10); if (rv > 0 && (size_t)rv < *sz) *sz = rv; } if (fd > 2) close(fd); } } #else # define optmem_max(x) #endif /** * Represents a single control (ancillary data) message returned * in the *ancillary* array by {@link module:socket.socket#recvmsg|`recvmsg()`}. * * @typedef {Object} module:socket.socket.ControlMessage * @property {number} level * The message socket level (`cmsg_level`), e.g. `SOL_SOCKET`. * * @property {number} type * The protocol specific message type (`cmsg_type`), e.g. `SCM_RIGHTS`. * * @property {*} data * The payload of the control message. If the control message type is known by * the socket module, it is represented as a mixed value (array, object, number, * etc.) with structure specific to the control message type. If the control * message cannot be decoded, *data* is set to a string value containing the raw * payload. */ static uc_value_t * decode_cmsg(uc_vm_t *vm, struct cmsghdr *cmsg) { char *s = (char *)CMSG_DATA(cmsg); size_t sz = cmsg->cmsg_len - sizeof(*cmsg); struct sockaddr_storage *ss; uc_value_t *fdarr; struct stat st; int *fds; for (size_t i = 0; i < ARRAY_SIZE(cmsgtypes); i++) { if (cmsgtypes[i].level != cmsg->cmsg_level) continue; if (cmsgtypes[i].type != cmsg->cmsg_type) continue; switch ((uintptr_t)cmsgtypes[i].ctype) { case (uintptr_t)CV_INT: return ucv_int64_new(parse_integer(s, sz)); case (uintptr_t)CV_UINT: case (uintptr_t)CV_BE32: return ucv_uint64_new(parse_unsigned(s, sz)); case (uintptr_t)CV_SOCKADDR: ss = (struct sockaddr_storage *)s; if ((sz >= sizeof(struct sockaddr_in) && ss->ss_family == AF_INET) || (sz >= sizeof(struct sockaddr_in6) && ss->ss_family == AF_INET6)) { uc_value_t *addr = ucv_object_new(vm); if (sockaddr_to_uv(ss, addr)) return addr; ucv_put(addr); } return NULL; case (uintptr_t)CV_FDS: fdarr = ucv_array_new_length(vm, sz / sizeof(int)); fds = (int *)s; for (size_t i = 0; i < sz / sizeof(int); i++) { if (fstat(fds[i], &st) == 0) { uc_resource_type_t *t; if (S_ISSOCK(st.st_mode)) { t = ucv_resource_type_lookup(vm, "socket"); ucv_array_push(fdarr, ucv_resource_new(t, (void *)(intptr_t)fds[i])); continue; } else if (S_ISDIR(st.st_mode)) { t = ucv_resource_type_lookup(vm, "fs.dir"); if (t) { DIR *d = fdopendir(fds[i]); if (d) { ucv_array_push(fdarr, ucv_resource_new(t, d)); continue; } } } else { t = ucv_resource_type_lookup(vm, "fs.file"); if (t) { int n = fcntl(fds[i], F_GETFL); const char *mode; if (n <= 0 || (n & O_ACCMODE) == O_RDONLY) mode = "r"; else if ((n & O_ACCMODE) == O_WRONLY) mode = (n & O_APPEND) ? "a" : "w"; else mode = (n & O_APPEND) ? "a+" : "w+"; FILE *f = fdopen(fds[i], mode); if (f) { ucv_array_push(fdarr, uc_resource_new(t, f)); continue; } } } } ucv_array_push(fdarr, ucv_int64_new(fds[i])); } return fdarr; case (uintptr_t)CV_STRING: break; default: if (sz >= cmsgtypes[i].ctype->size) return struct_to_uv(s, cmsgtypes[i].ctype); } break; } return ucv_string_new_length(s, sz); } static size_t estimate_cmsg_size(uc_value_t *uv) { int cmsg_level = ucv_to_integer(ucv_object_get(uv, "level", NULL)); int cmsg_type = ucv_to_integer(ucv_object_get(uv, "type", NULL)); uc_value_t *val = ucv_object_get(uv, "data", NULL); for (size_t i = 0; i < ARRAY_SIZE(cmsgtypes); i++) { if (cmsgtypes[i].level != cmsg_level) continue; if (cmsgtypes[i].type != cmsg_type) continue; switch ((uintptr_t)cmsgtypes[i].ctype) { case (uintptr_t)CV_INT: return sizeof(int); case (uintptr_t)CV_UINT: return sizeof(unsigned int); case (uintptr_t)CV_BE32: return sizeof(uint32_t); case (uintptr_t)CV_SOCKADDR: return sizeof(struct sockaddr_storage); case (uintptr_t)CV_FDS: return ucv_array_length(val) * sizeof(int); case (uintptr_t)CV_STRING: return ucv_string_length(val); default: return cmsgtypes[i].ctype->size; } } switch (ucv_type(val)) { case UC_BOOLEAN: return sizeof(unsigned int); case UC_INTEGER: return sizeof(int); case UC_STRING: return ucv_string_length(val); default: return 0; } } static bool encode_cmsg(uc_vm_t *vm, uc_value_t *uv, struct cmsghdr *cmsg) { struct { int *entries; size_t count; } fds = { 0 }; void *dataptr = NULL; socklen_t datasz = 0; char *st = NULL; size_t i; union { int i; unsigned int u; uint32_t u32; struct sockaddr_storage ss; } val; cmsg->cmsg_level = ucv_to_integer(ucv_object_get(uv, "level", NULL)); cmsg->cmsg_type = ucv_to_integer(ucv_object_get(uv, "type", NULL)); uc_value_t *data = ucv_object_get(uv, "data", NULL); for (i = 0; i < ARRAY_SIZE(cmsgtypes); i++) { if (cmsgtypes[i].level != cmsg->cmsg_level) continue; if (cmsgtypes[i].type != cmsg->cmsg_type) continue; switch ((uintptr_t)cmsgtypes[i].ctype) { case (uintptr_t)CV_INT: val.i = ucv_to_integer(data); datasz = sizeof(val.i); dataptr = &val; break; case (uintptr_t)CV_UINT: val.u = ucv_to_unsigned(data); datasz = sizeof(val.u); dataptr = &val; break; case (uintptr_t)CV_BE32: val.u32 = ucv_to_unsigned(data); datasz = sizeof(val.u32); dataptr = &val; break; case (uintptr_t)CV_SOCKADDR: if (uv_to_sockaddr(data, &val.ss, &datasz)) dataptr = &val; else datasz = 0, dataptr = NULL; break; case (uintptr_t)CV_FDS: if (ucv_type(data) == UC_ARRAY) { for (size_t i = 0; i < ucv_array_length(data); i++) { int fd; if (uv_to_fileno(vm, ucv_array_get(data, i), &fd)) uc_vector_push(&fds, fd); } } datasz = sizeof(fds.entries[0]) * fds.count; dataptr = fds.entries; break; case (uintptr_t)CV_STRING: datasz = ucv_string_length(data); dataptr = ucv_string_get(data); break; default: st = uv_to_struct(data, cmsgtypes[i].ctype); datasz = st ? cmsgtypes[i].ctype->size : 0; dataptr = st; break; } break; } /* we don't know this kind of control message, guess encoding */ if (i == ARRAY_SIZE(cmsgtypes)) { switch (ucv_type(data)) { /* treat boolean as int with values 1 or 0 */ case UC_BOOLEAN: val.u = ucv_boolean_get(data); dataptr = &val; datasz = sizeof(val.u); break; /* treat integers as int */ case UC_INTEGER: if (ucv_is_u64(data)) { val.u = ucv_uint64_get(data); datasz = sizeof(val.u); } else { val.i = ucv_int64_get(data); datasz = sizeof(val.i); } dataptr = &val; break; /* pass strings as-is */ case UC_STRING: dataptr = ucv_string_get(data); datasz = ucv_string_length(data); break; default: break; } } cmsg->cmsg_len = CMSG_LEN(datasz); if (dataptr) memcpy(CMSG_DATA(cmsg), dataptr, datasz); uc_vector_clear(&fds); free(st); return true; } /** * Sends a message through the socket. * * Sends a message through the socket handle, supporting complex message * structures including multiple data buffers and ancillary data. This function * allows for precise control over the message content and delivery behavior. * * Returns the number of sent bytes. * * Returns `null` if an error occurred. * * @function module:socket.socket#sendmsg * * @param {*} [data] * The data to be sent. If a string is provided, it is sent as is. If an array * is specified, each item is sent as a separate `struct iovec`. Non-string * values are implicitly converted to a string and sent. If omitted, only * ancillary data and address are considered. * * @param {module:socket.socket.ControlMessage[]|string} [ancillaryData] * Optional ancillary data to be sent. If an array is provided, each element is * converted to a control message. If a string is provided, it is sent as-is * without further interpretation. Refer to * {@link module:socket.socket#recvmsg|`recvmsg()`} and * {@link module:socket.socket.ControlMessage|ControlMessage} for details. * * @param {module:socket.socket.SocketAddress} [address] * The destination address for the message. If provided, it sets or overrides * the packet destination address. * * @param {number} [flags] * Optional flags to modify the behavior of the send operation. This should be a * bitwise OR-ed combination of `MSG_*` flag values. * * @returns {?number} * Returns the number of bytes sent on success, or `null` if an error occurred. * * @example * // Send file descriptors over domain socket * const f1 = fs.open("example.txt", "w"); * const f2 = fs.popen("date +%s", "r"); * const sk = socket.connect({ family: socket.AF_UNIX, path: "/tmp/socket" }); * sk.sendmsg("Hi there, here's some descriptors!", [ * { level: socket.SOL_SOCKET, type: socket.SCM_RIGHTS, data: [ f1, f2 ] } * ]); * * // Send multiple values in one datagram * sk.sendmsg([ "This", "is", "one", "message" ]); */ static uc_value_t * uc_socket_inst_sendmsg(uc_vm_t *vm, size_t nargs) { uc_value_t *data, *ancdata, *addr, *flags; struct sockaddr_storage ss = { 0 }; strbuf_array_t sbarr = { 0 }; struct msghdr msg = { 0 }; struct iovec vec = { 0 }; int flagval, sockfd; socklen_t slen; ssize_t ret; args_get(vm, nargs, &sockfd, "data", UC_NULL, true, &data, "ancillary data", UC_NULL, true, &ancdata, "address", UC_OBJECT, true, &addr, "flags", UC_INTEGER, true, &flags); flagval = flags ? ucv_int64_get(flags) : 0; /* treat string ancdata arguemnt as raw controldata buffer */ if (ucv_type(ancdata) == UC_STRING) { msg.msg_control = ucv_string_get(ancdata); msg.msg_controllen = ucv_string_length(ancdata); } /* encode ancdata passed as array */ else if (ucv_type(ancdata) == UC_ARRAY) { msg.msg_controllen = 0; for (size_t i = 0; i < ucv_array_length(ancdata); i++) { size_t sz = estimate_cmsg_size(ucv_array_get(ancdata, i)); if (sz > 0) msg.msg_controllen += CMSG_SPACE(sz); } if (msg.msg_controllen > 0) { msg.msg_control = xalloc(msg.msg_controllen); struct cmsghdr *cmsg = NULL; for (size_t i = 0; i < ucv_array_length(ancdata); i++) { #ifdef __clang_analyzer__ /* Clang static analyzer assumes that CMSG_*HDR() returns * allocated heap pointers and not pointers into the * msg.msg_control buffer. Nudge it. */ cmsg = (struct cmsghdr *)msg.msg_control; #else cmsg = cmsg ? CMSG_NXTHDR(&msg, cmsg) : CMSG_FIRSTHDR(&msg); #endif if (!cmsg) { free(msg.msg_control); err_return(ENOBUFS, "Not enough CMSG buffer space"); } if (!encode_cmsg(vm, ucv_array_get(ancdata, i), cmsg)) { free(msg.msg_control); return NULL; } } msg.msg_controllen = (cmsg != NULL) ? (char *)cmsg - (char *)msg.msg_control + CMSG_SPACE(cmsg->cmsg_len) : 0; } } else if (ancdata) { err_return(EINVAL, "Ancillary data must be string or array value"); } /* prepare iov array */ if (ucv_type(data) == UC_ARRAY) { msg.msg_iovlen = ucv_array_length(data); msg.msg_iov = (msg.msg_iovlen > 1) ? xalloc(sizeof(vec) * msg.msg_iovlen) : &vec; for (size_t i = 0; i < (size_t)msg.msg_iovlen; i++) { uc_value_t *item = ucv_array_get(data, i); if (ucv_type(item) == UC_STRING) { msg.msg_iov[i].iov_base = _ucv_string_get(&((uc_array_t *)data)->entries[i]); msg.msg_iov[i].iov_len = ucv_string_length(item); } else if (item) { struct printbuf *pb = xprintbuf_new(); uc_vector_push(&sbarr, pb); ucv_to_stringbuf(vm, pb, item, false); msg.msg_iov[i].iov_base = pb->buf; msg.msg_iov[i].iov_len = pb->bpos; } } } else if (ucv_type(data) == UC_STRING) { msg.msg_iovlen = 1; msg.msg_iov = &vec; vec.iov_base = ucv_string_get(data); vec.iov_len = ucv_string_length(data); } else if (data) { struct printbuf *pb = xprintbuf_new(); uc_vector_push(&sbarr, pb); ucv_to_stringbuf(vm, pb, data, false); msg.msg_iovlen = 1; msg.msg_iov = &vec; vec.iov_base = pb->buf; vec.iov_len = pb->bpos; } /* prepare address */ if (addr && uv_to_sockaddr(addr, &ss, &slen)) { msg.msg_name = &ss; msg.msg_namelen = slen; } /* now send actual data */ do { ret = sendmsg(sockfd, &msg, flagval); } while (ret == -1 && errno == EINTR); while (sbarr.count > 0) printbuf_free(sbarr.entries[--sbarr.count]); uc_vector_clear(&sbarr); if (msg.msg_iov != &vec) free(msg.msg_iov); free(msg.msg_control); if (ret == -1) err_return(errno, "sendmsg()"); ok_return(ucv_int64_new(ret)); } /** * Represents a message object returned by * {@link module:socket.socket#recvmsg|`recvmsg()`}. * * @typedef {Object} module:socket.socket.ReceivedMessage * @property {number} flags * Integer value containing bitwise OR-ed `MSG_*` result flags returned by the * underlying receive call. * * @property {number} length * Integer value containing the number of bytes returned by the `recvmsg()` * syscall, which might be larger than the received data in case `MSG_TRUNC` * was passed. * * @property {module:socket.socket.SocketAddress} address * The address from which the message was received. * * @property {string[]|string} data * An array of strings, each representing the received message data. * Each string corresponds to one buffer size specified in the *sizes* argument. * If a single receive size was passed instead of an array of sizes, *data* will * hold a string containing the received data. * * @property {module:socket.socket.ControlMessage[]} [ancillary] * An array of received control messages. Only included if a non-zero positive * *ancillarySize* was passed to `recvmsg()`. */ /** * Receives a message from the socket. * * Receives a message from the socket handle, allowing for more complex data * reception compared to `recv()`. This includes the ability to receive * ancillary data (such as file descriptors, credentials, etc.), multiple * message segments, and optional flags to modify the receive behavior. * * Returns an object containing the received message data, ancillary data, * and the sender's address. * * Returns `null` if an error occurred during the receive operation. * * @function module:socket.socket#recvmsg * * @param {number[]|number} [sizes] * Specifies the sizes of the buffers used for receiving the message. If an * array of numbers is provided, each number determines the size of an * individual buffer segment, creating multiple `struct iovec` for reception. * If a single number is provided, a single buffer of that size is used. * * @param {number} [ancillarySize] * The size allocated for the ancillary data buffer. If not provided, ancillary * data is not processed. * * @param {number} [flags] * Optional flags to modify the behavior of the receive operation. This should * be a bitwise OR-ed combination of flag values. * * @returns {?module:socket.socket.ReceivedMessage} * An object containing the received message data, ancillary data, * and the sender's address. * * @example * // Receive file descriptors over domain socket * const sk = socket.listen({ family: socket.AF_UNIX, path: "/tmp/socket" }); * sk.setopt(socket.SOL_SOCKET, socket.SO_PASSCRED, true); * * const msg = sk.recvmsg(1024, 1024); * * for (let cmsg in msg.ancillary) * if (cmsg.level == socket.SOL_SOCKET && cmsg.type == socket.SCM_RIGHTS) * print(`Got some descriptors: ${cmsg.data}!\n`); * * // Receive message in segments of 10, 128 and 512 bytes * const msg = sk.recvmsg([ 10, 128, 512 ]); * print(`Message parts: ${msg.data[0]}, ${msg.data[1]}, ${msg.data[2]}\n`); * * // Peek buffer * const msg = sk.recvmsg(0, 0, socket.MSG_PEEK|socket.MSG_TRUNC); * print(`Received ${length(msg.data)} bytes, ${msg.length} bytes available\n`); */ static uc_value_t * uc_socket_inst_recvmsg(uc_vm_t *vm, size_t nargs) { uc_value_t *length, *anclength, *flags, *rv; struct sockaddr_storage ss = { 0 }; strbuf_array_t sbarr = { 0 }; struct msghdr msg = { 0 }; struct iovec vec = { 0 }; int flagval, sockfd; ssize_t ret; args_get(vm, nargs, &sockfd, "length", UC_NULL, true, &length, "ancillary length", UC_INTEGER, true, &anclength, "flags", UC_INTEGER, true, &flags); flagval = flags ? ucv_int64_get(flags) : 0; #if defined(__linux__) flagval |= MSG_CMSG_CLOEXEC; #endif /* prepare ancillary data buffer */ if (anclength) { size_t sz = ucv_to_unsigned(anclength); if (errno != 0) err_return(errno, "Invalid ancillary data length"); optmem_max(&sz); if (sz > 0) { msg.msg_controllen = sz; msg.msg_control = xalloc(sz); } } /* prepare iov array */ if (ucv_type(length) == UC_ARRAY) { msg.msg_iovlen = ucv_array_length(length); msg.msg_iov = (msg.msg_iovlen > 1) ? xalloc(sizeof(vec) * msg.msg_iovlen) : &vec; for (size_t i = 0; i < (size_t)msg.msg_iovlen; i++) { size_t sz = ucv_to_unsigned(ucv_array_get(length, i)); if (errno != 0) { while (sbarr.count > 0) strbuf_free(sbarr.entries[--sbarr.count]); uc_vector_clear(&sbarr); if (msg.msg_iov != &vec) free(msg.msg_iov); free(msg.msg_control); err_return(errno, "Invalid length value"); } uc_vector_push(&sbarr, strbuf_alloc(sz)); msg.msg_iov[i].iov_base = strbuf_data(sbarr.entries[i]); msg.msg_iov[i].iov_len = sz; } } else { size_t sz = ucv_to_unsigned(length); if (errno != 0) { free(msg.msg_control); err_return(errno, "Invalid length value"); } uc_vector_push(&sbarr, strbuf_alloc(sz)); msg.msg_iovlen = 1; msg.msg_iov = &vec; vec.iov_base = strbuf_data(sbarr.entries[0]); vec.iov_len = sz; } /* now receive actual data */ msg.msg_name = &ss; msg.msg_namelen = sizeof(ss); do { ret = recvmsg(sockfd, &msg, flagval); } while (ret == -1 && errno == EINTR); if (ret == -1) { while (sbarr.count > 0) strbuf_free(sbarr.entries[--sbarr.count]); uc_vector_clear(&sbarr); if (msg.msg_iov != &vec) free(msg.msg_iov); free(msg.msg_control); err_return(errno, "recvmsg()"); } rv = ucv_object_new(vm); ucv_object_add(rv, "flags", ucv_int64_new(msg.msg_flags)); ucv_object_add(rv, "length", ucv_int64_new(ret)); if (msg.msg_namelen > 0) { uc_value_t *addr = ucv_object_new(vm); if (sockaddr_to_uv(&ss, addr)) ucv_object_add(rv, "address", addr); else ucv_put(addr); } if (msg.msg_controllen > 0) { uc_value_t *ancillary = ucv_array_new(vm); for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { uc_value_t *c = ucv_object_new(vm); ucv_object_add(c, "level", ucv_int64_new(cmsg->cmsg_level)); ucv_object_add(c, "type", ucv_int64_new(cmsg->cmsg_type)); ucv_object_add(c, "data", decode_cmsg(vm, cmsg)); ucv_array_push(ancillary, c); } ucv_object_add(rv, "ancillary", ancillary); } if (ret >= 0) { if (ucv_type(length) == UC_ARRAY) { uc_value_t *data = ucv_array_new_length(vm, msg.msg_iovlen); for (size_t i = 0; i < (size_t)msg.msg_iovlen; i++) { size_t sz = ret; if (sz > msg.msg_iov[i].iov_len) sz = msg.msg_iov[i].iov_len; ucv_array_push(data, strbuf_finish(&sbarr.entries[i], sz)); ret -= sz; } ucv_object_add(rv, "data", data); } else { size_t sz = ret; if (sz > msg.msg_iov[0].iov_len) sz = msg.msg_iov[0].iov_len; ucv_object_add(rv, "data", strbuf_finish(&sbarr.entries[0], sz)); } } uc_vector_clear(&sbarr); if (msg.msg_iov != &vec) free(msg.msg_iov); free(msg.msg_control); ok_return(rv); } /** * 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 }, { "sendmsg", uc_socket_inst_sendmsg }, { "recv", uc_socket_inst_recv }, { "recvmsg", uc_socket_inst_recvmsg }, { "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 {Object} IPv6 Protocol Constants * @description * The `IPPROTO_IPV6` constant specifies the IPv6 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 `IPV6_*` constants are option names recognized by * {@link module:socket.socket#getopt|getopt()} * and {@link module:socket.socket#setopt|setopt()}, in conjunction with * the `IPPROTO_IPV6` socket level. * @property {number} IPPROTO_IPV6 - The IPv6 protocol. * @property {number} IPV6_ADDRFORM - Turn an AF_INET6 socket into a socket of a different address family. Only AF_INET is supported. * @property {number} IPV6_ADDR_PREFERENCES - Specify preferences for address selection. * @property {number} IPV6_ADD_MEMBERSHIP - Add an IPv6 group membership. * @property {number} IPV6_AUTHHDR - Set delivery of the authentication header control message for incoming datagrams. * @property {number} IPV6_AUTOFLOWLABEL - Enable or disable automatic flow labels. * @property {number} IPV6_DONTFRAG - Control whether the socket allows IPv6 fragmentation. * @property {number} IPV6_DROP_MEMBERSHIP - Drop an IPv6 group membership. * @property {number} IPV6_DSTOPTS - Set delivery of the destination options control message for incoming datagrams. * @property {number} IPV6_FLOWINFO_SEND - Control whether flow information is sent. * @property {number} IPV6_FLOWINFO - Set delivery of the flow ID control message for incoming datagrams. * @property {number} IPV6_FLOWLABEL_MGR - Manage flow labels. * @property {number} IPV6_FREEBIND - Allow binding to an IP address not assigned to a network interface. * @property {number} IPV6_HOPLIMIT - Set delivery of the hop limit control message for incoming datagrams. * @property {number} IPV6_HOPOPTS - Set delivery of the hop options control message for incoming datagrams. * @property {number} IPV6_JOIN_ANYCAST - Join an anycast group. * @property {number} IPV6_LEAVE_ANYCAST - Leave an anycast group. * @property {number} IPV6_MINHOPCOUNT - Set the minimum hop count. * @property {number} IPV6_MTU - Retrieve or set the MTU to be used for the socket. * @property {number} IPV6_MTU_DISCOVER - Control path-MTU discovery on the socket. * @property {number} IPV6_MULTICAST_ALL - Control whether the socket receives all multicast packets. * @property {number} IPV6_MULTICAST_HOPS - Set the multicast hop limit for the socket. * @property {number} IPV6_MULTICAST_IF - Set the device for outgoing multicast packets on the socket. * @property {number} IPV6_MULTICAST_LOOP - Control whether the socket sees multicast packets that it has sent itself. * @property {number} IPV6_PKTINFO - Set delivery of the IPV6_PKTINFO control message on incoming datagrams. * @property {number} IPV6_RECVDSTOPTS - Control receiving of the destination options control message. * @property {number} IPV6_RECVERR - Control receiving of asynchronous error options. * @property {number} IPV6_RECVFRAGSIZE - Control receiving of fragment size. * @property {number} IPV6_RECVHOPLIMIT - Control receiving of hop limit. * @property {number} IPV6_RECVHOPOPTS - Control receiving of hop options. * @property {number} IPV6_RECVORIGDSTADDR - Control receiving of the original destination address. * @property {number} IPV6_RECVPATHMTU - Control receiving of path MTU. * @property {number} IPV6_RECVPKTINFO - Control receiving of packet information. * @property {number} IPV6_RECVRTHDR - Control receiving of routing header. * @property {number} IPV6_RECVTCLASS - Control receiving of traffic class. * @property {number} IPV6_ROUTER_ALERT_ISOLATE - Control isolation of router alert messages. * @property {number} IPV6_ROUTER_ALERT - Pass forwarded packets containing a router alert hop-by-hop option to this socket. * @property {number} IPV6_RTHDR - Set delivery of the routing header control message for incoming datagrams. * @property {number} IPV6_RTHDRDSTOPTS - Set delivery of the routing header destination options control message. * @property {number} IPV6_TCLASS - Set the traffic class. * @property {number} IPV6_TRANSPARENT - Enable transparent proxy support. * @property {number} IPV6_UNICAST_HOPS - Set the unicast hop limit for the socket. * @property {number} IPV6_UNICAST_IF - Set the interface for outgoing unicast packets. * @property {number} IPV6_V6ONLY - Restrict the socket to sending and receiving IPv6 packets only. */ ADD_CONST(IPPROTO_IPV6); ADD_CONST(IPV6_FLOWINFO_SEND); ADD_CONST(IPV6_FLOWINFO); ADD_CONST(IPV6_FLOWLABEL_MGR); ADD_CONST(IPV6_MULTICAST_HOPS); ADD_CONST(IPV6_MULTICAST_IF); ADD_CONST(IPV6_MULTICAST_LOOP); ADD_CONST(IPV6_RECVTCLASS); ADD_CONST(IPV6_TCLASS); ADD_CONST(IPV6_UNICAST_HOPS); ADD_CONST(IPV6_V6ONLY); #if defined(__linux__) ADD_CONST(IPV6_ADD_MEMBERSHIP); ADD_CONST(IPV6_ADDR_PREFERENCES); ADD_CONST(IPV6_ADDRFORM); ADD_CONST(IPV6_AUTHHDR); ADD_CONST(IPV6_AUTOFLOWLABEL); ADD_CONST(IPV6_DONTFRAG); ADD_CONST(IPV6_DROP_MEMBERSHIP); ADD_CONST(IPV6_DSTOPTS); ADD_CONST(IPV6_FREEBIND); ADD_CONST(IPV6_HOPLIMIT); ADD_CONST(IPV6_HOPOPTS); ADD_CONST(IPV6_JOIN_ANYCAST); ADD_CONST(IPV6_LEAVE_ANYCAST); ADD_CONST(IPV6_MINHOPCOUNT); ADD_CONST(IPV6_MTU_DISCOVER); ADD_CONST(IPV6_MTU); ADD_CONST(IPV6_MULTICAST_ALL); ADD_CONST(IPV6_PKTINFO); ADD_CONST(IPV6_RECVDSTOPTS); ADD_CONST(IPV6_RECVERR); ADD_CONST(IPV6_RECVFRAGSIZE); ADD_CONST(IPV6_RECVHOPLIMIT); ADD_CONST(IPV6_RECVHOPOPTS); ADD_CONST(IPV6_RECVORIGDSTADDR); ADD_CONST(IPV6_RECVPATHMTU); ADD_CONST(IPV6_RECVPKTINFO); ADD_CONST(IPV6_RECVRTHDR); ADD_CONST(IPV6_ROUTER_ALERT_ISOLATE); ADD_CONST(IPV6_ROUTER_ALERT); ADD_CONST(IPV6_RTHDR); ADD_CONST(IPV6_RTHDRDSTOPTS); ADD_CONST(IPV6_TRANSPARENT); ADD_CONST(IPV6_UNICAST_IF); #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); ADD_CONST(SCM_CREDENTIALS); ADD_CONST(SCM_RIGHTS); #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); }