mdns: add mdns for local network routing (#511)

This PR implements [mDNS](https://en.wikipedia.org/wiki/Multicast_DNS)
for vere, significantly improving Urbit routing inside local networks.
On startup vere will now broadcast a service with the format
`dinleb-rambep._ames._udp.local`. Vere will also listen for any services
on the local network with the signature of `_ames._udp`. Whenever new
services are found vere will send the lane to arvo.

On macOS we can use [Bonjour](https://developer.apple.com/bonjour/) and
don't need to pull down any dependencies at all. On Linux we can use
[Avahi](https://www.avahi.org/), specifically the bonjour compatibility
layer. The header we're using is
[dns_sd.h](https://github.com/lathiat/avahi/blob/master/avahi-compat-libdns_sd/dns_sd.h),
[documented
here](https://developer.apple.com/library/archive/documentation/Networking/Conceptual/dns_discovery_api/Introduction.html#//apple_ref/doc/uid/TP40002475-SW1).

Tested on macos aarch64 and linux x86_64 so far. To try it out, build
vere from this PR, start a fakezod and observe:

```
# macos
dns-sd -B _ames._udp

# linux
avahi-browse -r _ames._udp
```

You can also start another fake ship on the same network and do
`|ames-verb %rcv %odd` to see your fakeships find eachother. For this
second part you need the companion arvo PR, here's a pill that has it
already:

```
./urbit -F zod -u https://urbit-foundation.s3.us-east-2.amazonaws.com/dear.pill
```
This commit is contained in:
Pyry Kovanen 2023-12-05 14:00:16 +02:00 committed by GitHub
commit e7e06d031c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 2102 additions and 2 deletions

View File

@ -121,6 +121,15 @@ versioned_http_archive(
version = "9681279cfaa6e6399bb7ca3afbbc27fc2e19df4b", version = "9681279cfaa6e6399bb7ca3afbbc27fc2e19df4b",
) )
versioned_http_archive(
name = "avahi",
build_file = "//bazel/third_party/avahi:avahi.BUILD",
sha256 = "060309d7a333d38d951bc27598c677af1796934dbd98e1024e7ad8de798fedda",
strip_prefix = "avahi-{version}",
url = "https://github.com/lathiat/avahi/releases/download/v{version}/avahi-{version}.tar.gz",
version = "0.8",
)
versioned_http_archive( versioned_http_archive(
name = "bazel_gazelle", name = "bazel_gazelle",
sha256 = "efbbba6ac1a4fd342d5122cbdfdb82aeb2cf2862e35022c752eaddffada7c3f3", sha256 = "efbbba6ac1a4fd342d5122cbdfdb82aeb2cf2862e35022c752eaddffada7c3f3",
@ -145,6 +154,25 @@ versioned_http_archive(
version = "7.85.0", version = "7.85.0",
) )
versioned_http_archive(
name = "dbus",
build_file = "//bazel/third_party/dbus:dbus.BUILD",
sha256 = "a6bd5bac5cf19f0c3c594bdae2565a095696980a683a0ef37cb6212e093bde35",
strip_prefix = "dbus-{version}",
url = "https://dbus.freedesktop.org/releases/dbus/dbus-{version}.tar.xz",
version = "1.14.8",
)
versioned_http_archive(
name = "expat",
build_file = "//bazel/third_party/expat:expat.BUILD",
strip_prefix = "expat-{version}",
sha256 = "ef2420f0232c087801abf705e89ae65f6257df6b7931d37846a193ef2e8cdcbe",
# TODO: fix the R_2_5_0 nonsense
url = "https://github.com/libexpat/libexpat/releases/download/R_2_5_0/expat-{version}.tar.xz",
version = "2.5.0",
)
versioned_http_archive( versioned_http_archive(
name = "gmp", name = "gmp",
build_file = "//bazel/third_party/gmp:gmp.BUILD", build_file = "//bazel/third_party/gmp:gmp.BUILD",

0
bazel/third_party/avahi/BUILD.bazel vendored Normal file
View File

30
bazel/third_party/avahi/avahi.BUILD vendored Normal file
View File

@ -0,0 +1,30 @@
load("@rules_foreign_cc//foreign_cc:defs.bzl", "configure_make")
filegroup(
name = "all",
srcs = glob(["**"]),
)
cc_library(
name = "dns-sd",
hdrs = ["dns_sd.h"],
visibility = ["//visibility:public"],
)
configure_make(
name = "avahi",
args = select({
"@platforms//os:macos": ["--jobs=`sysctl -n hw.logicalcpu`"],
"//conditions:default": ["--jobs=`nproc`"],
}),
configure_options = ["--with-dbus-system-address='unix:path=/var/run/dbus/system_bus_socket' --with-xml=none --disable-libevent --disable-glib --disable-gobject --disable-gdbm --disable-qt3 --disable-qt4 --disable-qt5 --disable-gtk --disable-gtk3 --disable-mono --disable-monodoc --disable-python --disable-libdaemon --enable-compat-libdns_sd --disable-rpath"],
lib_source = ":all",
# out_include_dir = "avahi-compat-libdns_sd",
deps = ["@dbus"],
configure_in_place = True,
autogen = True,
autoconf = True,
autogen_command = "bootstrap.sh",
out_static_libs = ["libdns_sd.a", "libavahi-client.a", "libavahi-common.a"],
visibility = ["//visibility:public"],
)

0
bazel/third_party/dbus/BUILD.bazel vendored Normal file
View File

21
bazel/third_party/dbus/dbus.BUILD vendored Normal file
View File

@ -0,0 +1,21 @@
load("@rules_foreign_cc//foreign_cc:defs.bzl", "configure_make")
filegroup(
name = "all",
srcs = glob(["**"]),
)
configure_make(
name = "dbus",
lib_name = "libdbus-1",
args = select({
"@platforms//os:macos": ["--jobs=`sysctl -n hw.logicalcpu`"],
"//conditions:default": ["--jobs=`nproc`"],
}),
copts = ["-O3"],
configure_options = ["--disable-selinux --without-x --disable-tests"],
lib_source = ":all",
configure_in_place = True,
deps = ["@expat"],
visibility = ["//visibility:public"],
)

0
bazel/third_party/expat/BUILD.bazel vendored Normal file
View File

18
bazel/third_party/expat/expat.BUILD vendored Normal file
View File

@ -0,0 +1,18 @@
load("@rules_foreign_cc//foreign_cc:defs.bzl", "configure_make")
filegroup(
name = "all",
srcs = glob(["**"]),
)
configure_make(
name = "expat",
args = select({
"@platforms//os:macos": ["--jobs=`sysctl -n hw.logicalcpu`"],
"//conditions:default": ["--jobs=`nproc`"],
}),
copts = ["-O3"],
lib_source = ":all",
out_static_libs = ["libexpat.a"],
visibility = ["//visibility:public"],
)

View File

@ -308,6 +308,7 @@
# define c3__deep c3_s4('d','e','e','p') # define c3__deep c3_s4('d','e','e','p')
# define c3__defn c3_s4('d','e','f','n') # define c3__defn c3_s4('d','e','f','n')
# define c3__del c3_s3('d','e','l') # define c3__del c3_s3('d','e','l')
# define c3__dear c3_s4('d','e','a','r')
# define c3__delc c3_s4('d','e','l','c') # define c3__delc c3_s4('d','e','l','c')
# define c3__delt c3_s4('d','e','l','t') # define c3__delt c3_s4('d','e','l','t')
# define c3__dept c3_s4('d','e','p','t') # define c3__dept c3_s4('d','e','p','t')

View File

@ -119,7 +119,13 @@ vere_library(
hdrs = [ hdrs = [
"db/lmdb.h", "db/lmdb.h",
"vere.h", "vere.h",
"mdns.h",
] + select({
"@platforms//os:macos": [],
"@platforms//os:linux": [
"dns_sd.h",
], ],
}),
includes = ["."], includes = ["."],
linkstatic = True, linkstatic = True,
visibility = ["//pkg:__subpackages__"], visibility = ["//pkg:__subpackages__"],
@ -133,7 +139,12 @@ vere_library(
"@lmdb", "@lmdb",
"@openssl", "@openssl",
"@uv", "@uv",
] + select({
"@platforms//os:macos": [],
"@platforms//os:linux": [
"@avahi",
], ],
})
) )
# #

1722
pkg/vere/dns_sd.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
/// @file /// @file
#include "vere.h" #include "vere.h"
#include "mdns.h"
#include "noun.h" #include "noun.h"
#include "ur.h" #include "ur.h"
@ -2083,6 +2084,50 @@ _ames_recv_cb(uv_udp_t* wax_u,
} }
} }
static void
_mdns_dear_bail(u3_ovum* egg_u, u3_noun lud)
{
u3z(lud);
u3_ovum_free(egg_u);
}
/* _ames_put_dear(): send lane to arvo after hearing mdns response
*/
static void
_ames_put_dear(c3_c* ship, c3_w s_addr, c3_s port, void* context)
{
u3_ames* sam_u = (u3_ames*)context;
u3_lane lan;
lan.pip_w = ntohl(s_addr);
lan.por_s = ntohs(port);
u3_noun whu = u3dc("slaw", c3__p, u3i_string(ship));
if (u3_nul == whu) {
u3l_log("ames: strange ship from mdns: %s", ship);
return;
}
u3_noun our = u3i_chubs(2, sam_u->pir_u->who_d);
if (our == u3t(whu)) {
u3z(whu);
u3z(our);
return;
}
u3z(our);
u3_noun wir = u3nc(c3__ames, u3_nul);
u3_noun cad = u3nt(c3__dear, u3k(u3t(whu)), u3nc(c3n, u3_ames_encode_lane(lan)));
u3_auto_peer(
u3_auto_plan(&sam_u->car_u,
u3_ovum_init(0, c3__a, wir, cad)),
0, 0, _mdns_dear_bail);
u3z(whu);
}
/* _ames_io_start(): initialize ames I/O. /* _ames_io_start(): initialize ames I/O.
*/ */
static void static void
@ -2147,6 +2192,15 @@ _ames_io_start(u3_ames* sam_u)
u3l_log("ames: live on %d (localhost only)", sam_u->pir_u->por_s); u3l_log("ames: live on %d (localhost only)", sam_u->pir_u->por_s);
} }
{
u3_noun our = u3dc("scot", 'p', u3k(who));
char* our_s = u3r_string(our);
u3z(our);
mdns_init(por_s, our_s, _ames_put_dear, (void *)sam_u);
c3_free(our_s);
}
uv_udp_recv_start(&sam_u->wax_u, _ames_alloc, _ames_recv_cb); uv_udp_recv_start(&sam_u->wax_u, _ames_alloc, _ames_recv_cb);
sam_u->car_u.liv_o = c3y; sam_u->car_u.liv_o = c3y;

210
pkg/vere/mdns.c Normal file
View File

@ -0,0 +1,210 @@
/// @file
#include "vere.h"
#include "mdns.h"
#include "dns_sd.h"
typedef struct _mdns_payload {
mdns_cb* cb;
DNSServiceRef sref;
char who[58];
uint16_t port;
uv_poll_t poll;
void* context;
} mdns_payload;
static void close_cb(uv_handle_t* poll) {
mdns_payload* payload = (mdns_payload*)poll->data;
DNSServiceRefDeallocate(payload->sref);
c3_free(payload);
}
static void getaddrinfo_cb(uv_getaddrinfo_t* req, int status, struct addrinfo* res) {
mdns_payload* payload = (mdns_payload*)req->data;
if (status < 0) {
u3l_log("mdns: getaddrinfo error: %s", uv_strerror(status));
} else {
struct sockaddr_in* addr = (struct sockaddr_in*)res->ai_addr;
payload->cb(payload->who, addr->sin_addr.s_addr, payload->port, payload->context);
}
payload->poll.data = payload;
uv_close((uv_handle_t*)&payload->poll, close_cb);
c3_free(req);
uv_freeaddrinfo(res);
}
static void resolve_cb(DNSServiceRef sref,
DNSServiceFlags f,
uint32_t interface,
DNSServiceErrorType err,
const char *name,
const char *host,
uint16_t port,
uint16_t tl,
const unsigned char *t,
void *context)
{
mdns_payload* payload = (mdns_payload*)context;
uv_poll_stop(&payload->poll);
if (err != kDNSServiceErr_NoError) {
u3l_log("mdns: dns resolve error %d", err);
payload->poll.data = payload;
uv_close((uv_handle_t*)&payload->poll, close_cb);
return;
}
payload->sref = sref;
payload->port = port;
int i;
payload->who[0] = '~';
for (i = 0; name[i] != '\0' && name[i] != '.' && i < 58; ++i)
{
payload->who[i+1] = name[i];
}
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET; // Request only IPv4 addresses
hints.ai_socktype = SOCK_STREAM; // TCP socket
uv_getaddrinfo_t* req = (uv_getaddrinfo_t*)c3_malloc(sizeof(uv_getaddrinfo_t));
req->data = (void*)payload;
uv_loop_t* loop = uv_default_loop();
int error = uv_getaddrinfo(loop, req, getaddrinfo_cb, host, NULL, &hints);
if (error < 0) {
u3l_log("mdns: getaddrinfo error: %s\n", uv_strerror(error));
payload->poll.data = payload;
uv_close((uv_handle_t*)&payload->poll, close_cb);
c3_free(req);
}
}
static void poll_cb(uv_poll_t* handle, int status, int events) {
DNSServiceRef sref = (DNSServiceRef) handle->data;
int err = DNSServiceProcessResult(sref);
}
static void init_sref_poll(DNSServiceRef sref, mdns_payload* payload) {
int fd = DNSServiceRefSockFD(sref);
uv_loop_t* loop = uv_default_loop();
payload->poll.data = (void*)sref;
uv_poll_init(loop, &payload->poll, fd);
uv_poll_start(&payload->poll, UV_READABLE, poll_cb);
}
static void browse_cb(DNSServiceRef s,
DNSServiceFlags f,
uint32_t interface,
DNSServiceErrorType err,
const char* name,
const char* type,
const char* domain,
void* context)
{
if (err != kDNSServiceErr_NoError) {
u3l_log("mdns: service browse error %i", err);
return;
}
if (f & kDNSServiceFlagsAdd) { // Add
// we are leaking payload because we don't know when we are done
// browsing, luckily we only browse once
mdns_payload* payload = (mdns_payload*)context;
mdns_payload* payload_copy = c3_malloc(sizeof *payload_copy);
// copy to prevent asynchronous thrashing of payload
memcpy(payload_copy, payload, sizeof(mdns_payload));
DNSServiceErrorType err =
DNSServiceResolve(&payload_copy->sref, 0, interface,
name, type, domain, resolve_cb,
(void*)payload_copy);
init_sref_poll(payload_copy->sref, payload_copy);
if (err != kDNSServiceErr_NoError) {
u3l_log("mdns: dns service resolve error %i", err);
payload_copy->poll.data = payload_copy;
uv_close((uv_handle_t*)&payload_copy->poll, close_cb);
}
}
}
static void register_close_cb(uv_handle_t* poll) {
// not freeing sref with DNSServiceRefDeallocate since that
// deregisters us from mdns
mdns_payload* payload = (mdns_payload*)poll->data;
c3_free(payload);
}
static void register_cb(DNSServiceRef sref,
DNSServiceFlags f,
DNSServiceErrorType err,
const char* name,
const char* type,
const char* domain,
void* context)
{
mdns_payload* payload = (mdns_payload*)context;
if (err != kDNSServiceErr_NoError) {
u3l_log("mdns: service register error %i", err);
} else {
u3l_log("mdns: %s registered on all interfaces", name);
}
uv_poll_stop(&payload->poll);
uv_close((uv_handle_t*)&payload->poll, register_close_cb);
}
void mdns_init(uint16_t port, char* our, mdns_cb* cb, void* context)
{
#if defined(U3_OS_linux)
setenv("AVAHI_COMPAT_NOWARN", "1", 0);
setenv("DBUS_SYSTEM_BUS_ADDRESS", "unix:path=/var/run/dbus/system_bus_socket", 0);
# endif
mdns_payload* register_payload = (mdns_payload*)c3_malloc(sizeof(mdns_payload));
DNSServiceRef sref;
DNSServiceErrorType err;
char* our_no_sig = our + 1; // certain url parsers don't like the ~
err = DNSServiceRegister(&sref, 0, 0, our_no_sig, "_ames._udp",
NULL, NULL, htons(port), 0, NULL, register_cb, (void*)register_payload);
if (err != kDNSServiceErr_NoError) {
u3l_log("mdns: service register error %i", err);
DNSServiceRefDeallocate(sref);
return;
}
init_sref_poll(sref, register_payload);
mdns_payload* browse_payload = (mdns_payload*)c3_malloc(sizeof(mdns_payload));
browse_payload->cb = cb;
browse_payload->context = context;
DNSServiceErrorType dnserr;
DNSServiceRef sref2;
dnserr = DNSServiceBrowse(&sref2, 0, 0, "_ames._udp", NULL, browse_cb, (void *)browse_payload);
if (dnserr != kDNSServiceErr_NoError) {
u3l_log("mdns: service browse error %i", dnserr);
DNSServiceRefDeallocate(sref2);
return;
}
init_sref_poll(sref2, browse_payload);
}

5
pkg/vere/mdns.h Normal file
View File

@ -0,0 +1,5 @@
#include "noun.h"
typedef void mdns_cb(c3_c* ship, c3_w s_addr, c3_s port, void* context);
void mdns_init(uint16_t port, char* our, mdns_cb* cb, void* context);