summaryrefslogtreecommitdiffhomepage
path: root/lib
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2024-05-21 10:40:09 +0200
committerGitHub <noreply@github.com>2024-05-21 10:40:09 +0200
commit2a0240f349d2808a01ec3e006b6f0da9107b7a27 (patch)
tree370e0a4cf78ed98a6a15656335777aa164bc1428 /lib
parent9cd88751a4c6e29afcbaba82a72f775e57638def (diff)
parent7611487b9a051f17e1cf9d21cc10b78071695b93 (diff)
Merge pull request #202 from jow-/socket-improvements
Socket improvements
Diffstat (limited to 'lib')
-rw-r--r--lib/socket.c1396
1 files changed, 1339 insertions, 57 deletions
diff --git a/lib/socket.c b/lib/socket.c
index 226ba28..63ca24f 100644
--- a/lib/socket.c
+++ b/lib/socket.c
@@ -58,6 +58,7 @@
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
+#include <sys/stat.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
@@ -69,12 +70,22 @@
#include <netdb.h>
#include <poll.h>
#include <limits.h>
+#include <dirent.h>
+#include <assert.h>
#include "ucode/module.h"
#include "ucode/platform.h"
#if defined(__linux__)
# include <linux/if_packet.h>
+
+# 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__)
@@ -96,6 +107,18 @@
# 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)
@@ -240,8 +263,14 @@ strbuf_size(uc_stringbuf_t *sb)
static uc_value_t *
strbuf_finish(uc_stringbuf_t **sb, size_t final_size)
{
- uc_string_t *us = (uc_string_t *)(*sb)->buf;
- size_t buffer_size = strbuf_size(*sb);
+ 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;
@@ -417,6 +446,38 @@ sockaddr_to_uv(struct sockaddr_storage *ss, uc_value_t *addrobj)
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)
{
@@ -481,14 +542,13 @@ uv_to_sockaddr(uc_value_t *addr, struct sockaddr_storage *ss, socklen_t *slen)
len = ucv_string_length(addr);
if (memchr(s, '/', len) != NULL) {
- *slen = ucv_string_length(addr);
-
- if (*slen >= sizeof(su->sun_path))
- *slen = sizeof(su->sun_path) - 1;
+ if (len >= sizeof(su->sun_path))
+ len = sizeof(su->sun_path) - 1;
- memcpy(su->sun_path, s, *slen);
- su->sun_path[(*slen)++] = 0;
+ memcpy(su->sun_path, s, len);
+ su->sun_path[len++] = 0;
su->sun_family = AF_UNIX;
+ *slen = sizeof(*su);
ok_return(true);
}
@@ -678,7 +738,7 @@ uv_to_sockaddr(uc_value_t *addr, struct sockaddr_storage *ss, socklen_t *slen)
memcpy(su->sun_path, ucv_string_get(item), len);
su->sun_path[len++] = 0;
su->sun_family = AF_UNIX;
- *slen = len;
+ *slen = sizeof(*su);
ok_return(true);
@@ -855,7 +915,7 @@ xclose(int *fdptr)
typedef struct {
const char *name;
- enum { DT_SIGNED, DT_UNSIGNED, DT_IPV4ADDR, DT_CALLBACK } type;
+ enum { DT_SIGNED, DT_UNSIGNED, DT_IPV4ADDR, DT_IPV6ADDR, DT_CALLBACK } type;
union {
size_t offset;
bool (*to_c)(void *, uc_value_t *);
@@ -877,6 +937,12 @@ typedef struct {
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) }, \
@@ -940,7 +1006,159 @@ static struct_t st_ip_mreq_source = {
}
};
+/* 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 []){
@@ -1164,6 +1382,20 @@ static struct_t st_tpacket_stats = {
}
};
+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 []){
@@ -1173,13 +1405,73 @@ static struct_t st_fanout_args = {
{ 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_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 },
@@ -1260,6 +1552,52 @@ static sockopt_t sockopts[] = {
{ 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 },
@@ -1299,6 +1637,52 @@ static sockopt_t sockopts[] = {
#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)
@@ -1382,6 +1766,17 @@ uv_to_struct(uc_value_t *uv, struct_t *spec)
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);
@@ -1398,7 +1793,7 @@ uv_to_struct(uc_value_t *uv, struct_t *spec)
static uc_value_t *
struct_to_uv(char *st, struct_t *spec)
{
- char s[sizeof("255.255.255.255")];
+ char s[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
uc_value_t *uv, *fv;
member_t *m;
@@ -1457,6 +1852,12 @@ struct_to_uv(char *st, struct_t *spec)
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;
@@ -1552,6 +1953,25 @@ uc_socket_inst_setopt(uc_vm_t *vm, size_t nargs)
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;
@@ -1604,8 +2024,9 @@ static uc_value_t *
uc_socket_inst_getopt(uc_vm_t *vm, size_t nargs)
{
uc_value_t *level, *option, *value = NULL;
- int sockfd, solvl, soopt, soval, ret;
+ 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;
@@ -1616,7 +2037,6 @@ uc_socket_inst_getopt(uc_vm_t *vm, size_t nargs)
solvl = ucv_int64_get(level);
soopt = ucv_int64_get(option);
- soval = 0;
for (i = 0; i < ARRAY_SIZE(sockopts); i++) {
if (sockopts[i].level != solvl || sockopts[i].option != soopt)
@@ -1629,8 +2049,9 @@ uc_socket_inst_getopt(uc_vm_t *vm, size_t nargs)
case (uintptr_t)SV_INT:
case (uintptr_t)SV_INT_RO:
case (uintptr_t)SV_BOOL:
- valptr = &soval;
- vallen = sizeof(int);
+ case (uintptr_t)SV_IFNAME:
+ valptr = ival;
+ vallen = sizeof(ival);
break;
case (uintptr_t)SV_STRING:
@@ -1670,23 +2091,34 @@ uc_socket_inst_getopt(uc_vm_t *vm, size_t nargs)
}
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(soval);
+ value = ucv_int64_new(parse_integer(ival, vallen));
break;
case (uintptr_t)SV_BOOL:
- value = ucv_boolean_new(soval);
+ 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;
@@ -2140,6 +2572,14 @@ uc_socket_poll(uc_vm_t *vm, size_t nargs)
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.
*
@@ -2223,7 +2663,7 @@ uc_socket_connect(uc_vm_t *vm, size_t nargs)
ai_hints = hints
? (struct addrinfo *)uv_to_struct(hints, &st_addrinfo) : NULL;
- if (ucv_type(host) == UC_STRING) {
+ if (should_resolve(host)) {
char *servstr = (ucv_type(serv) != UC_STRING)
? ucv_to_string(vm, serv) : NULL;
@@ -2437,7 +2877,7 @@ uc_socket_listen(uc_vm_t *vm, size_t nargs)
ai_hints = hints
? (struct addrinfo *)uv_to_struct(hints, &st_addrinfo) : NULL;
- if (host == NULL || ucv_type(host) == UC_STRING) {
+ if (host == NULL || should_resolve(host)) {
char *servstr = (ucv_type(serv) != UC_STRING)
? ucv_to_string(vm, serv) : NULL;
@@ -2501,7 +2941,12 @@ uc_socket_listen(uc_vm_t *vm, size_t nargs)
((struct sockaddr_in *)&ss)->sin_port = htons(port);
}
- socktype = ai_hints ? ai_hints->ai_socktype : SOCK_STREAM;
+ 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;
}
@@ -2524,7 +2969,7 @@ uc_socket_listen(uc_vm_t *vm, size_t nargs)
ret = listen(fd, backlog ? ucv_to_unsigned(backlog) : 128);
- if (ret == -1) {
+ if (ret == -1 && errno != EOPNOTSUPP) {
close(fd);
err_return(errno, "listen()");
}
@@ -2569,37 +3014,6 @@ uc_socket_listen(uc_vm_t *vm, size_t nargs)
*/
/**
- * Creates a socket and returns its hand
- *
- * The handle will be connected to the process stdin or stdout, depending on the
- * value of the mode argument.
- *
- * The mode argument may be either "r" to open the process for reading (connect
- * to its stdin) or "w" to open the process for writing (connect to its stdout).
- *
- * The mode character "r" or "w" may be optionally followed by "e" to apply the
- * FD_CLOEXEC flag onto the open descriptor.
- *
- * Returns a process handle referring to the executed process.
- *
- * Returns `null` if an error occurred.
- *
- * @function module:fs#popen
- *
- * @param {string} command
- * The command to be executed.
- *
- * @param {string} [mode="r"]
- * The open mode of the process handle.
- *
- * @returns {?module:fs.proc}
- *
- * @example
- * // Open a process
- * const process = popen('command', 'r');
- */
-
-/**
* Creates a network socket instance.
*
* This function creates a new network socket with the specified domain and
@@ -2890,6 +3304,765 @@ uc_socket_inst_recv(uc_vm_t *vm, size_t nargs)
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.
*
@@ -3416,7 +4589,9 @@ static const uc_function_list_t socket_fns[] = {
{ "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 },
@@ -3605,6 +4780,110 @@ void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
#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
@@ -3704,6 +4983,9 @@ void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
ADD_CONST(SO_RXQ_OVFL);
ADD_CONST(SO_SNDBUFFORCE);
ADD_CONST(SO_TIMESTAMPNS);
+
+ ADD_CONST(SCM_CREDENTIALS);
+ ADD_CONST(SCM_RIGHTS);
#endif
/**