diff --git a/changes/udp-open-local.md b/changes/udp-open-local.md new file mode 100644 index 00000000..3bb1ec48 --- /dev/null +++ b/changes/udp-open-local.md @@ -0,0 +1,5 @@ +bump: minor +type: feature +**`Udp.open_local(port)`: a socket only this machine reaches.** Bound to 127.0.0.1 alone (udp.ll, +udp_win.ll), where `Udp.open` binds every interface - for a port only local tools should talk to, such +as a development link. diff --git a/docs/language/udp/udp-open_local.md b/docs/language/udp/udp-open_local.md new file mode 100644 index 00000000..10e6d068 --- /dev/null +++ b/docs/language/udp/udp-open_local.md @@ -0,0 +1,23 @@ +--- +id: udp-open_local +name: Udp.open_local +category: udp +kind: namespace-method +tokens: Udp.open_local +sig: Udp.open_local(port) -> int +tip: Open a socket only this machine can reach. +order: 0 +ns: Udp +member: open_local +--- + +Opens a non-blocking IPv4 socket like Udp.open, but bound to 127.0.0.1 alone: programs on the same machine reach it, and nothing on the network can. Use it for a port only local tools should talk to, such as a development link. Returns the handle, or 0 when it could not. + +```ludic +program Demo { + entry { + let s = Udp.open_local(port: 0) + print(s > 0) + } +} +``` diff --git a/runtime/native/namespaces.ludic b/runtime/native/namespaces.ludic index 300ee640..77a5c491 100644 --- a/runtime/native/namespaces.ludic +++ b/runtime/native/namespaces.ludic @@ -205,6 +205,7 @@ namespace Http { } namespace Udp { alias open(port) = udp_open + alias open_local(port) = udp_open_local alias port(socket) = udp_port alias send(socket, ip, port, bytes, len) = udp_send alias recv(socket, bytes, cap) = udp_recv diff --git a/runtime/native/udp.ll b/runtime/native/udp.ll index 7f6c5b94..60c60040 100644 --- a/runtime/native/udp.ll +++ b/runtime/native/udp.ll @@ -71,14 +71,29 @@ none: ret i32 -1 } +; open: every interface (a party's port, reached from the LAN and beyond) define i32 @lu_udp_open(i32 %port) { +entry: + %h = call i32 @lu_udp_open_at(i32 0, i32 %port) + ret i32 %h +} + +; open_local: 127.0.0.1 only - a port for this machine's own tools, never reachable from the LAN +define i32 @lu_udp_open_local(i32 %port) { +entry: + %h = call i32 @lu_udp_open_at(i32 2130706433, i32 %port) + ret i32 %h +} + +; bound to one address (host order: 0 any, 0x7f000001 loopback), non-blocking, in a free slot +define internal i32 @lu_udp_open_at(i32 %ip, i32 %port) { entry: %sa = alloca [16 x i8] %fd = call i32 @socket(i32 2, i32 2, i32 17) %bad = icmp slt i32 %fd, 0 br i1 %bad, label %fail, label %bindit bindit: - call void @lu_addr(ptr %sa, i32 0, i32 %port) + call void @lu_addr(ptr %sa, i32 %ip, i32 %port) %b = call i32 @bind(i32 %fd, ptr %sa, i32 16) %bb = icmp ne i32 %b, 0 br i1 %bb, label %closefail, label %nonblock diff --git a/runtime/native/udp.ludic b/runtime/native/udp.ludic index d7f23b01..6f862eaf 100644 --- a/runtime/native/udp.ludic +++ b/runtime/native/udp.ludic @@ -12,6 +12,8 @@ # Like Http.* and Time.now it is OUT-OF-BAND: it depends on the network and the wall clock # and must never feed a deterministic lockstep/replay simulation directly. # +# Udp.open_local(port) binds 127.0.0.1 alone, for a port only this machine's tools should reach. +# # An address is an int: a.b.c.d is (a << 24) | (b << 16) | (c << 8) | d. Udp.ip("10.0.0.2") # and Udp.ip_text(ip) convert, Udp.resolve("example.com") looks a host name up (it blocks # for as long as the system's resolver takes), and Udp.local_ip() is the address this @@ -22,6 +24,7 @@ # ============================================================================ extern function lu_udp_open(port: int) -> int = "lu_udp_open" +extern function lu_udp_open_local(port: int) -> int = "lu_udp_open_local" extern function lu_udp_port(h: int) -> int = "lu_udp_port" extern function lu_udp_send(h: int, ip: int, port: int, buf: pointer, n: int) -> int = "lu_udp_send" extern function lu_udp_recv(h: int, buf: pointer, cap: int, ipport: pointer) -> int = "lu_udp_recv" @@ -54,6 +57,13 @@ function udp_open(rt_udp_st: mut RtUdpState, port: int) -> int { if (port < 0) or (port > 65535) { return 0 } return lu_udp_open(port) } +# the same bound to 127.0.0.1 only: a port this machine's own tools reach (a dev link) and the +# LAN never can +function udp_open_local(rt_udp_st: mut RtUdpState, port: int) -> int { + udp_init(rt_udp_st) + if (port < 0) or (port > 65535) { return 0 } + return lu_udp_open_local(port) +} # the port a socket is bound to (the one the system picked, for port 0) function udp_port(h: int) -> int { return lu_udp_port(h) } # one datagram of `n` bytes from `buf` to ip:port: the bytes sent, or -1 diff --git a/runtime/native/udp_win.ll b/runtime/native/udp_win.ll index 5791e865..0d597a66 100644 --- a/runtime/native/udp_win.ll +++ b/runtime/native/udp_win.ll @@ -71,7 +71,22 @@ none: ret i64 -1 } +; open: every interface (a party's port, reached from the LAN and beyond) define i32 @lu_udp_open(i32 %port) { +entry: + %h = call i32 @lu_udp_open_at(i32 0, i32 %port) + ret i32 %h +} + +; open_local: 127.0.0.1 only - a port for this machine's own tools, never reachable from the LAN +define i32 @lu_udp_open_local(i32 %port) { +entry: + %h = call i32 @lu_udp_open_at(i32 2130706433, i32 %port) + ret i32 %h +} + +; bound to one address (host order: 0 any, 0x7f000001 loopback), non-blocking, in a free slot +define internal i32 @lu_udp_open_at(i32 %ip, i32 %port) { entry: call void @lu_start() %sa = alloca [16 x i8] @@ -80,7 +95,7 @@ entry: %bad = icmp eq i64 %s, -1 br i1 %bad, label %fail, label %bindit bindit: - call void @lu_addr(ptr %sa, i32 0, i32 %port) + call void @lu_addr(ptr %sa, i32 %ip, i32 %port) %b = call i32 @bind(i64 %s, ptr %sa, i32 16) %bb = icmp ne i32 %b, 0 br i1 %bb, label %closefail, label %nonblock