feat(net): net.connect outbound TCP client (id 110)
- new builtin net.connect(host, port) -> Int: the outbound-socket gap language 38 named and jarvis surfaced; the client half of the net verbs - getaddrinfo for DNS (v4/v6, numeric or hostname), blocking connect with the same EINTR/stop handling as net.connect_unix, then O_NONBLOCK for the park plane; returns the same fd-scalar accept yields - wob.h enum + WO_B_MAX 110; types.ml registration; loader.c arity; builtin.c sysio dispatch range extended to WO_B_NET_CONNECT; sysio.c impl - verified: numeric IP + hostname (DNS) connect to a local listener, closed port traps cleanly; ASan-clean; runtime battery 0 fail - deferred (next slice): net.connect_dl deadline/park variant (no shard stall during handshake), on the accept_dl pattern (cherry picked from commit 13c6f124428243b4956fbb4eceb1e7d0206d45f2)
This commit is contained in:
parent
b932e0cb87
commit
e91a3704fe
5 changed files with 63 additions and 3 deletions
|
|
@ -364,6 +364,7 @@ let stdlib_members : stdlib_member list =
|
|||
m "net" "send_fd" 2 105 (Some (TScalar "Bool")) None;
|
||||
m "net" "recv_fd" 1 106 (Some (TNullable (TScalar "Int"))) None;
|
||||
m "net" "connect_unix" 1 107 (Some (TScalar "Int")) None;
|
||||
m "net" "connect" 2 110 (Some (TScalar "Int")) None;
|
||||
(* runtime-v2 6: resize's read twin (nil = not a tty), and a
|
||||
codepoint's terminal cell width (libc wcwidth under C.UTF-8) *)
|
||||
m "term" "size" 1 108 (Some (TNullable (TScalar termsize_record_name))) (Some termsize_record_name);
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ int wo_builtin(wo_vm *vm, uint64_t *R, uint32_t ins, const char **msg) {
|
|||
if (C == WO_B_JSON_ENCODE || C == WO_B_JSON_DECODE)
|
||||
return wo_builtin_json(vm, R, ins, msg);
|
||||
if ((C >= WO_B_SYS_FIRST && C <= WO_B_PROC_RUN) || C == WO_B_TIME_TICKS
|
||||
|| (C >= WO_B_NET_READ_DL && C <= WO_B_TERM_WIDTH))
|
||||
|| (C >= WO_B_NET_READ_DL && C <= WO_B_NET_CONNECT))
|
||||
return wo_builtin_sys(vm, R, ins, msg);
|
||||
if (C >= WO_B_SHA1 && C <= WO_B_HMAC_SHA256)
|
||||
return wo_builtin_crypto(vm, R, ins, msg);
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ static const uint8_t b_arity[WO_B_MAX + 1] = {
|
|||
[WO_B_PROC_SPAWN_PTY] = 5, [WO_B_PROC_RESIZE] = 3,
|
||||
[WO_B_SIGNAL_ON] = 3, [WO_B_TERM_RAW] = 1, [WO_B_TERM_RESTORE] = 1,
|
||||
[WO_B_NET_SEND_FD] = 2, [WO_B_NET_RECV_FD] = 1, [WO_B_NET_CONNECT_UNIX] = 1,
|
||||
[WO_B_TERM_SIZE] = 2, [WO_B_TERM_WIDTH] = 1,
|
||||
[WO_B_TERM_SIZE] = 2, [WO_B_TERM_WIDTH] = 1, [WO_B_NET_CONNECT] = 2,
|
||||
/* json (json.c): encode takes the value's static kind, decode the class
|
||||
id to build */
|
||||
[WO_B_JSON_ENCODE] = 2, [WO_B_JSON_DECODE] = 2, [WO_B_MAP_GET_OPT] = 2,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include <sys/syscall.h>
|
||||
#include <termios.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
|
|
@ -1552,6 +1553,59 @@ int wo_builtin_sys(wo_vm *vm, uint64_t *R, uint32_t ins, const char **msg) {
|
|||
R[A] = (uint64_t)fd;
|
||||
return 0;
|
||||
}
|
||||
case WO_B_NET_CONNECT: { /* (host, port) -> Int: outbound TCP client fd.
|
||||
* DNS via getaddrinfo (v4 or v6), blocking
|
||||
* connect with the same EINTR/stop handling as
|
||||
* WO_B_NET_CONNECT_UNIX, then nonblocking for the
|
||||
* plane. A _dl deadline/park variant is the next
|
||||
* slice; this one can stall the shard during the
|
||||
* handshake, tolerable while connect is rare. */
|
||||
if (cstr_of(R[B], path, sizeof path, msg)) return WO_T_BOUNDS;
|
||||
int64_t port = (int64_t)R[B + 1];
|
||||
if (port < 0 || port > 65535) {
|
||||
*msg = "port out of range";
|
||||
return WO_T_BOUNDS;
|
||||
}
|
||||
char portstr[8];
|
||||
snprintf(portstr, sizeof portstr, "%u", (unsigned)port);
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *res = NULL;
|
||||
struct addrinfo *ai;
|
||||
memset(&hints, 0, sizeof hints);
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
int grc = getaddrinfo(path, portstr, &hints, &res);
|
||||
if (grc != 0) {
|
||||
*msg = gai_strerror(grc);
|
||||
return WO_T_IO;
|
||||
}
|
||||
int fd = -1;
|
||||
for (ai = res; ai; ai = ai->ai_next) {
|
||||
fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
|
||||
if (fd < 0) continue;
|
||||
int rc;
|
||||
for (;;) {
|
||||
rc = connect(fd, ai->ai_addr, ai->ai_addrlen);
|
||||
if (rc == 0 || errno != EINTR) break;
|
||||
if (stop_pending()) {
|
||||
close(fd);
|
||||
freeaddrinfo(res);
|
||||
return WO_SYS_STOPPED;
|
||||
}
|
||||
}
|
||||
if (rc == 0) break;
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
freeaddrinfo(res);
|
||||
if (fd < 0) {
|
||||
*msg = strerror(errno);
|
||||
return WO_T_IO;
|
||||
}
|
||||
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
|
||||
R[A] = (uint64_t)fd;
|
||||
return 0;
|
||||
}
|
||||
case WO_B_NET_SEND_FD: { /* (conn, fd) -> Bool: SCM_RIGHTS, one fd */
|
||||
int conn = (int)(int64_t)R[B];
|
||||
int pass = (int)(int64_t)R[B + 1];
|
||||
|
|
|
|||
|
|
@ -547,9 +547,14 @@ enum {
|
|||
WO_B_TERM_WIDTH = 109, /* (codepoint) -> Int cell width via libc
|
||||
* wcwidth under C.UTF-8: -1 control,
|
||||
* 0 combining, 1, or 2 */
|
||||
/* ---- language 38 / jarvis: the outbound-socket client side. TCP
|
||||
* connect with DNS via getaddrinfo; blocking connect then nonblocking
|
||||
* for the plane, mirroring WO_B_NET_CONNECT_UNIX. A _dl deadline/park
|
||||
* variant is the next slice. Underneath runtime-v2 9's outbound TLS. */
|
||||
WO_B_NET_CONNECT = 110, /* (host, port) -> Int: outbound TCP client fd */
|
||||
};
|
||||
|
||||
#define WO_B_MAX 109u
|
||||
#define WO_B_MAX 110u
|
||||
/* ids at or above this one live in sysio.c, not builtin.c */
|
||||
#define WO_B_SYS_FIRST WO_B_FS_EXISTS
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue