]> git.plutz.net Git - quickjs_net/commitdiff
unix sockets, getters for address lookup, some code reformatting
authorPaul Hänsch <paul@plutz.net>
Wed, 11 Mar 2026 19:43:01 +0000 (20:43 +0100)
committerPaul Hänsch <paul@plutz.net>
Wed, 11 Mar 2026 19:43:01 +0000 (20:43 +0100)
socket.c

index 12ea8842a82128d957c6f7c710939058012c9f83..f774fd173f1804c46101a8fb00d3e8e6c5fe92ec 100644 (file)
--- a/socket.c
+++ b/socket.c
 #define libc_error(ctx) ( JS_ThrowInternalError(ctx, "%s", strerror(errno)) )
 
 // use struct addrinfo in socket functions
-#define ai_socket(a) ( socket( (a)->ai_family, (a)->ai_socktype, (a)->ai_protocol ) )
-#define ai_bind(s, a) (       bind((s), (a)->ai_addr, (a)->ai_addrlen) )
+#define ai_socket(a) ( socket( (a)->ai_family, \
+                               (a)->ai_socktype, \
+                               (a)->ai_protocol \
+                     ) )
+#define    ai_bind(s, a) (    bind((s), (a)->ai_addr, (a)->ai_addrlen) )
 #define ai_connect(s, a) ( connect((s), (a)->ai_addr, (a)->ai_addrlen) )
 
-static JSValue js_os_socket( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
+static JSValue js_os_socket(
+  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv
+) {
   int family, type, protocol, fd;
 
   if ( argc >= 3
@@ -32,7 +37,9 @@ static JSValue js_os_socket( JSContext *ctx, JSValueConst this, int argc, JSValu
   }
 }
 
-static JSValue js_os_bind(   JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int con) {
+static JSValue js_os_bind(
+  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int con
+) {
   struct addrinfo *addr, *waddr;
   struct sockaddr_un addr_un = { .sun_family = AF_UNIX };
   int fd, b = -1; const char *host, *port, *path;
@@ -68,7 +75,9 @@ static JSValue js_os_bind(   JSContext *ctx, JSValueConst this, int argc, JSValu
   }
 }
 
-static JSValue js_os_listen( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
+static JSValue js_os_listen(
+  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv
+) {
   int fd, backlog = 1;
 
   if (argc < 1 ) return JS_EXCEPTION;
@@ -81,7 +90,9 @@ static JSValue js_os_listen( JSContext *ctx, JSValueConst this, int argc, JSValu
   else return libc_error(ctx);
 }
 
-static JSValue js_os_accept( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
+static JSValue js_os_accept(
+  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv
+) {
   struct sockaddr * peer; socklen_t ps = sizeof(struct sockaddr);
   char host[40], port[6];
   int fd, new;
@@ -127,7 +138,9 @@ typedef struct {
   struct sockaddr peer;
 } SocketData;
 
-static JSValue sock_accept(JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
+static JSValue sock_accept(
+  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv
+) {
   SocketData * data = JS_GetOpaque2(ctx, this, socket_cid);
   SocketData * newdata = js_mallocz(ctx, sizeof(SocketData));
   JSValue new = JS_NewObjectClass(ctx, socket_cid);
@@ -146,10 +159,13 @@ static JSValue sock_accept(JSContext *ctx, JSValueConst this, int argc, JSValueC
 
   return new;
 }
-static JSValue sock_send(  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
+static JSValue sock_send(
+  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv
+) {
   SocketData * data = JS_GetOpaque2(ctx, this, socket_cid);
   uint8_t *buf; size_t len;
 
+  // FIXME: how portable is MSG_NOSIGNAL ? (POSIX.1-2008)
   if ( (buf = JS_GetArrayBuffer(ctx, &len, argv[0])) ) {
     len = send(data->fd, buf, len, MSG_NOSIGNAL);
   } else if ( (buf = (uint8_t*) JS_ToCStringLen(ctx, &len, argv[0])) ) {
@@ -163,7 +179,9 @@ static JSValue sock_send(  JSContext *ctx, JSValueConst this, int argc, JSValueC
   else return JS_NewInt32(ctx, len);
 }
 
-static JSValue sock_recv(  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int str) {
+static JSValue sock_recv(
+  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int str
+) {
   SocketData * data = JS_GetOpaque2(ctx, this, socket_cid);
   uint8_t *buf; int len; JSValue jbuf;
 
@@ -189,7 +207,37 @@ static JSValue sock_recv(  JSContext *ctx, JSValueConst this, int argc, JSValueC
     return libc_error(ctx);
   }
 }
-static JSValue sock_close( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
+
+static JSValue sock_get_addr( JSContext *ctx, JSValueConst this, int magic ) {
+  SocketData * data = JS_GetOpaque2(ctx, this, socket_cid);
+  struct sockaddr * addr = (magic&2) ? &(data->peer) : &(data->bind);
+  char host[40];
+
+  if ( addr == NULL) return JS_UNDEFINED;
+
+  if (magic&1){
+    if      (addr->sa_family == AF_INET)
+      return JS_NewInt32(ctx, ntohs(((struct sockaddr_in *) addr)->sin_port));
+    else if (addr->sa_family == AF_INET6)
+      return JS_NewInt32(ctx, ntohs(((struct sockaddr_in6 *) addr)->sin6_port));
+    else return JS_UNDEFINED;
+  }
+
+  if (addr->sa_family == AF_UNIX) {
+    return JS_NewString(ctx, ((struct sockaddr_un *) addr)->sun_path);
+
+  } else if (addr->sa_family == AF_INET || addr->sa_family == AF_INET6) {
+    if (!getnameinfo(addr, sizeof(struct sockaddr),
+                     host, 40, NULL, 0,
+                     NI_NUMERICHOST | NI_NUMERICSERV)
+    ) { return JS_NewString(ctx, host);
+    } else return libc_error(ctx);
+  } else return JS_UNDEFINED;
+}
+
+static JSValue sock_close(
+  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv
+) {
   SocketData *data = JS_GetOpaque2(ctx, this, socket_cid);
 
   if ( close(data->fd) ) return libc_error(ctx);
@@ -207,13 +255,19 @@ static const JSCFunctionListEntry socket_ptype[] = {
   JS_CFUNC_DEF("close",  0, sock_close),
   JS_CFUNC_MAGIC_DEF("recv",       0, sock_recv, 0),
   JS_CFUNC_MAGIC_DEF("recvString", 0, sock_recv, 1),
+  JS_CGETSET_MAGIC_DEF("localName", sock_get_addr, NULL, 0),
+  JS_CGETSET_MAGIC_DEF("localPort", sock_get_addr, NULL, 1),
+  JS_CGETSET_MAGIC_DEF("peerName",  sock_get_addr, NULL, 2),
+  JS_CGETSET_MAGIC_DEF("peerPort",  sock_get_addr, NULL, 3),
 };
 
 static JSClassDef socket_class = {
   "Socket", .finalizer = sock_destroy
 };
 
-static JSValue ip_listen( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int magic) {
+static JSValue ip_listen(
+  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int magic
+) {
   struct addrinfo hints = {
     .ai_family = (magic&2) ? AF_INET6 : AF_INET,
     .ai_socktype = (magic&1) ? SOCK_STREAM : SOCK_DGRAM,
@@ -235,45 +289,109 @@ static JSValue ip_listen( JSContext *ctx, JSValueConst this, int argc, JSValueCo
     memcpy(&(data->bind), self->ai_addr, sizeof(struct sockaddr));
     JS_SetOpaque(new, data);
   } else {
-    JS_FreeValue(ctx, new);
-    if (data->fd >= 0) close(data->fd);
-    js_free(ctx, data);
-    if (!gai_err) new = libc_error(ctx);
+    if (data && data->fd >= 0) close(data->fd);
+    if (!data) new = JS_EXCEPTION;
+    else if (!gai_err) new = libc_error(ctx);
     else new = JS_ThrowInternalError(ctx, "%s", gai_strerror(gai_err));
+    js_free(ctx, data);
+    JS_FreeValue(ctx, new);
   }
   freeaddrinfo(self);
   JS_FreeCString(ctx, host); JS_FreeCString(ctx, port);
 
   return new;
 }
-static JSValue unix_listen(JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
-  return JS_UNDEFINED;
-}
 
-static JSValue udp_connect( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
-  return JS_UNDEFINED;
-}
-static JSValue tcp_connect( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
-  return JS_UNDEFINED;
+static JSValue ip_connect(
+  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int magic
+) {
+  struct addrinfo hints = {
+    .ai_family = (magic&2) ? AF_INET6 : AF_INET,
+    .ai_socktype = (magic&1) ? SOCK_STREAM : SOCK_DGRAM,
+  };
+  struct addrinfo * peer = NULL;
+  const char *host, *port; int gai_err;
+  SocketData *data = js_mallocz(ctx, sizeof(*data));
+  JSValue new = JS_NewObjectClass(ctx, socket_cid);
+
+  host = JS_ToCString(ctx, argv[0]);
+  port = JS_ToCString(ctx, argv[1]);
+
+  if ( data && (data->fd = -1)
+    && !(gai_err = getaddrinfo( host, port, &hints, &peer))
+    && (data->fd = ai_socket(peer)) >= 0
+    && !ai_connect(data->fd, peer)
+  ) {
+    memcpy(&(data->peer), peer->ai_addr, sizeof(struct sockaddr));
+    JS_SetOpaque(new, data);
+  } else {
+    if (data && data->fd >= 0) close(data->fd);
+    if (!data) new = JS_EXCEPTION;
+    else if (!gai_err) new = libc_error(ctx);
+    else new = JS_ThrowInternalError(ctx, "%s", gai_strerror(gai_err));
+    js_free(ctx, data);
+    JS_FreeValue(ctx, new);
+  }
+  freeaddrinfo(peer);
+  JS_FreeCString(ctx, host); JS_FreeCString(ctx, port);
+
+  return new;
 }
-static JSValue unix_connect(JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv) {
-  return JS_UNDEFINED;
+
+static JSValue unix_bind(
+  JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int c
+) {
+  SocketData *data = js_mallocz(ctx, sizeof(*data));
+  JSValue new = JS_NewObjectClass(ctx, socket_cid);
+  const char *path; size_t plen;
+  struct sockaddr_un * addr;
+
+  path = JS_ToCStringLen(ctx, &plen, argv[0]);
+
+  if ( data && (data->fd = -1)
+    && (addr = (struct sockaddr_un *) (c ? &(data->peer) : &(data->bind)))
+    && (addr->sun_family = AF_UNIX)
+    && (plen < sizeof(addr->sun_path))
+    && strncpy(addr->sun_path, path, plen)
+    && (data->fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0
+    && !( c ? connect(data->fd, &(data->peer), sizeof(*addr))
+            : bind(data->fd, &(data->bind), sizeof(*addr))
+    ) && (c || !listen(data->fd, 1))
+  ) {
+    JS_SetOpaque(new, data);
+  } else {
+    if (data && data->fd >= 0) close(data->fd);
+    if (!data) {
+      new = JS_EXCEPTION;
+    } else if (plen < sizeof(addr->sun_path)) {
+      new = libc_error(ctx);
+    } else new = JS_ThrowRangeError(
+      ctx, "pathname too long (>= %lu bytes)", sizeof(addr->sun_path)
+    );
+    js_free(ctx, data);
+    JS_FreeValue(ctx, new);
+  }
+  JS_FreeCString(ctx, path);
+
+  return new;
 }
 
 static const JSCFunctionListEntry net_funcs[] = {
-  JS_PROP_INT32_DEF("AF_UNIX",       AF_UNIX,       JS_PROP_CONFIGURABLE),
-  JS_PROP_INT32_DEF("AF_INET",       AF_INET,       JS_PROP_CONFIGURABLE),
-  JS_PROP_INT32_DEF("AF_INET6",      AF_INET6,      JS_PROP_CONFIGURABLE),
-  JS_PROP_INT32_DEF("SOCK_STREAM",   SOCK_STREAM,   JS_PROP_CONFIGURABLE),
-  JS_PROP_INT32_DEF("SOCK_DGRAM",    SOCK_DGRAM,    JS_PROP_CONFIGURABLE),
-  JS_CFUNC_MAGIC_DEF("udpListen",  2, ip_listen, 0),
-  JS_CFUNC_MAGIC_DEF("tcpListen",  2, ip_listen, 1),
-  JS_CFUNC_MAGIC_DEF("udp6Listen", 2, ip_listen, 2),
-  JS_CFUNC_MAGIC_DEF("tcp6Listen", 2, ip_listen, 3),
-  JS_CFUNC_DEF("unixListen",  1, unix_listen),
-  JS_CFUNC_DEF("udpConnect",  2, udp_connect),
-  JS_CFUNC_DEF("tcpConnect",  2, tcp_connect),
-  JS_CFUNC_DEF("unixConnect", 1, unix_connect),
+  JS_PROP_INT32_DEF("AF_UNIX",     AF_UNIX,     JS_PROP_CONFIGURABLE),
+  JS_PROP_INT32_DEF("AF_INET",     AF_INET,     JS_PROP_CONFIGURABLE),
+  JS_PROP_INT32_DEF("AF_INET6",    AF_INET6,    JS_PROP_CONFIGURABLE),
+  JS_PROP_INT32_DEF("SOCK_STREAM", SOCK_STREAM, JS_PROP_CONFIGURABLE),
+  JS_PROP_INT32_DEF("SOCK_DGRAM",  SOCK_DGRAM,  JS_PROP_CONFIGURABLE),
+  JS_CFUNC_MAGIC_DEF("udpListen",   2, ip_listen, 0),
+  JS_CFUNC_MAGIC_DEF("tcpListen",   2, ip_listen, 1),
+  JS_CFUNC_MAGIC_DEF("udp6Listen",  2, ip_listen, 2),
+  JS_CFUNC_MAGIC_DEF("tcp6Listen",  2, ip_listen, 3),
+  JS_CFUNC_MAGIC_DEF("udpConnect",  2, ip_connect, 0),
+  JS_CFUNC_MAGIC_DEF("tcpConnect",  2, ip_connect, 1),
+  JS_CFUNC_MAGIC_DEF("udp6Connect", 2, ip_connect, 2),
+  JS_CFUNC_MAGIC_DEF("tcp6Connect", 2, ip_connect, 3),
+  JS_CFUNC_MAGIC_DEF("unixListen",  1, unix_bind, 0),
+  JS_CFUNC_MAGIC_DEF("unixConnect", 1, unix_bind, 1),
 };
 
 // static const JSCFunctionListEntry net_obj[] = {
@@ -294,15 +412,16 @@ static int sock_modinit(JSContext *ctx, JSModuleDef *mod ) {
 }
 
 JSModuleDef *js_init_module(JSContext *ctx, const char *name) {
+  JSModuleDef *mod = JS_NewCModule(ctx, name, sock_modinit);
   JSValue global = JS_GetGlobalObject(ctx);
   JSValue os = JS_GetPropertyStr(ctx, global, "os");
-  JSModuleDef *mod = JS_NewCModule(ctx, name, sock_modinit);
 
   if (mod) {
     JS_AddModuleExportList(ctx, mod, net_funcs, countof(net_funcs));
     // JS_SetPropertyFunctionList(ctx, global, net_obj, countof(net_obj));
     if ( !JS_IsUndefined(os) )
-      JS_SetPropertyFunctionList(ctx, os, os_socket_funcs, countof(os_socket_funcs));
+      JS_SetPropertyFunctionList(ctx, os, os_socket_funcs,
+                                  countof(os_socket_funcs));
   }
   JS_FreeValue(ctx, global); JS_FreeValue(ctx, os);