start implementing mDNS service discovery for multiplayer
parent
0ab23322ea
commit
a6fe88b65e
12
build.zig
12
build.zig
|
@ -20,6 +20,16 @@ pub fn build(b: *std.Build) void {
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mdns = b.dependency("mdns", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const enet = b.dependency("enet", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "seizer-rummy",
|
.name = "seizer-rummy",
|
||||||
.root_source_file = .{ .path = "src/main.zig" },
|
.root_source_file = .{ .path = "src/main.zig" },
|
||||||
|
@ -27,6 +37,8 @@ pub fn build(b: *std.Build) void {
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
exe.root_module.addImport("seizer", seizer.module("seizer"));
|
exe.root_module.addImport("seizer", seizer.module("seizer"));
|
||||||
|
exe.linkLibrary(mdns.artifact("mdns"));
|
||||||
|
exe.linkLibrary(enet.artifact("enet"));
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
|
|
||||||
const run_cmd = b.addRunArtifact(exe);
|
const run_cmd = b.addRunArtifact(exe);
|
||||||
|
|
|
@ -19,6 +19,14 @@
|
||||||
.url = "https://github.com/leroycep/seizer/archive/8fdc6335641614c1cd844d1ecd5ad937584a443e.tar.gz",
|
.url = "https://github.com/leroycep/seizer/archive/8fdc6335641614c1cd844d1ecd5ad937584a443e.tar.gz",
|
||||||
.hash = "1220b6f9d0aba788b55a3e26a34ca9793495fbdb062c463cef1433ff937b96aa77d6",
|
.hash = "1220b6f9d0aba788b55a3e26a34ca9793495fbdb062c463cef1433ff937b96aa77d6",
|
||||||
},
|
},
|
||||||
|
.mdns = .{
|
||||||
|
.url = "https://github.com/leroycep/mdns/archive/ab55ee5a1e3f72d872521b89fd28a771a1271e2e.tar.gz",
|
||||||
|
.hash = "12209523b5d87d2cb4168950c68e7a8f14b1c74001b6d9a79d35286f3631ef5ea210",
|
||||||
|
},
|
||||||
|
.enet = .{
|
||||||
|
.url = "https://github.com/leroycep/enet/archive/624ca683074acead6f96785d9133e074aae59574.tar.gz",
|
||||||
|
.hash = "1220f45c8055a48c87a17b16adb28531072ebf336756b8847d489076ffb55b08e3f0",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
.paths = .{
|
.paths = .{
|
||||||
// This makes *all* files, recursively, included in this package. It is generally
|
// This makes *all* files, recursively, included in this package. It is generally
|
||||||
|
|
513
src/LocalUI.zig
513
src/LocalUI.zig
|
@ -12,7 +12,7 @@ fn main_menu_handler(_: ?*anyopaque, request: Request) Handler.Error!Response {
|
||||||
var join_multiplayer_link = try Element.Link.create(arena.allocator(), join_multiplayer_text.element(), .{ .handler = join_multiplayer_game });
|
var join_multiplayer_link = try Element.Link.create(arena.allocator(), join_multiplayer_text.element(), .{ .handler = join_multiplayer_game });
|
||||||
|
|
||||||
var host_multiplayer_text = try Element.Text.create(arena.allocator(), "Host Multiplayer Game");
|
var host_multiplayer_text = try Element.Text.create(arena.allocator(), "Host Multiplayer Game");
|
||||||
var host_multiplayer_link = try Element.Link.create(arena.allocator(), host_multiplayer_text.element(), .{ .handler = host_multiplayer_game });
|
var host_multiplayer_link = try Element.Link.create(arena.allocator(), host_multiplayer_text.element(), .{ .handler = HostMultiplayerGame.INTERFACE });
|
||||||
|
|
||||||
var play_game_hbox = try Element.HBox.create(arena.allocator());
|
var play_game_hbox = try Element.HBox.create(arena.allocator());
|
||||||
try play_game_hbox.addElement(join_multiplayer_link.element());
|
try play_game_hbox.addElement(join_multiplayer_link.element());
|
||||||
|
@ -42,28 +42,523 @@ fn join_multiplayer_game_handler(_: ?*anyopaque, request: Request) Handler.Error
|
||||||
return Response{ .arena = arena, .body = .{ .element = page.element() } };
|
return Response{ .arena = arena, .body = .{ .element = page.element() } };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const host_multiplayer_game = &Handler.Interface{
|
const HostMultiplayerGame = struct {
|
||||||
.create = null,
|
allocator: std.mem.Allocator,
|
||||||
.handle = &host_multiplayer_game_handler,
|
mdns_addr4_string: []u8,
|
||||||
.deinit = null,
|
mdns_addr6_string: []u8,
|
||||||
|
|
||||||
|
mdns_socket4: std.os.socket_t,
|
||||||
|
mdns_socket6: std.os.socket_t,
|
||||||
|
|
||||||
|
service_string: []const u8,
|
||||||
|
service_instance_string: []const u8,
|
||||||
|
qualified_hostname: []const u8,
|
||||||
|
mdns_records: []c.mdns_record_t,
|
||||||
|
mdns_thread: std.Thread,
|
||||||
|
mdns_thread_should_stop: std.atomic.Value(bool),
|
||||||
|
|
||||||
|
address: c.ENetAddress,
|
||||||
|
server: *c.ENetHost,
|
||||||
|
|
||||||
|
pub const INTERFACE = &Handler.Interface{
|
||||||
|
.create = &create,
|
||||||
|
.handle = &handler,
|
||||||
|
.deinit = &deinit,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn host_multiplayer_game_handler(_: ?*anyopaque, request: Request) Handler.Error!Response {
|
fn create(allocator: std.mem.Allocator) Handler.Error!?*anyopaque {
|
||||||
|
const this = try allocator.create(@This());
|
||||||
|
errdefer allocator.destroy(this);
|
||||||
|
|
||||||
|
// setup ENet game server
|
||||||
|
const address = c.ENetAddress{
|
||||||
|
.host = c.ENET_HOST_ANY,
|
||||||
|
.port = 5711,
|
||||||
|
};
|
||||||
|
|
||||||
|
const server = c.enet_host_create(&address, 4, 2, 0, 0);
|
||||||
|
if (server == null) {
|
||||||
|
return error.OutOfMemory; // TODO: add better error message
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup mDNS discovery service
|
||||||
|
|
||||||
|
const mdns_socket = std.os.socket(std.os.AF.INET, std.os.SOCK.DGRAM, std.os.IPPROTO.UDP) catch |e| {
|
||||||
|
std.log.scoped(.mdns).err("failed to open mdns socket = {}", .{e});
|
||||||
|
return error.OutOfMemory;
|
||||||
|
};
|
||||||
|
|
||||||
|
std.os.setsockopt(
|
||||||
|
mdns_socket,
|
||||||
|
std.os.SOL.SOCKET,
|
||||||
|
std.os.SO.REUSEADDR,
|
||||||
|
&std.mem.toBytes(@as(c_int, 1)),
|
||||||
|
) catch unreachable;
|
||||||
|
std.os.setsockopt(
|
||||||
|
mdns_socket,
|
||||||
|
std.os.SOL.SOCKET,
|
||||||
|
std.os.SO.REUSEPORT,
|
||||||
|
&std.mem.toBytes(@as(c_int, 1)),
|
||||||
|
) catch unreachable;
|
||||||
|
std.os.setsockopt(
|
||||||
|
mdns_socket,
|
||||||
|
std.os.IPPROTO.IP,
|
||||||
|
std.os.linux.IP.MULTICAST_TTL,
|
||||||
|
&std.mem.toBytes(@as(c_int, 1)),
|
||||||
|
) catch unreachable;
|
||||||
|
std.os.setsockopt(
|
||||||
|
mdns_socket,
|
||||||
|
std.os.IPPROTO.IP,
|
||||||
|
std.os.linux.IP.MULTICAST_LOOP,
|
||||||
|
&std.mem.toBytes(@as(c_int, 1)),
|
||||||
|
) catch unreachable;
|
||||||
|
|
||||||
|
const ip_mreq_t = extern struct {
|
||||||
|
multicast_address: [4]u8,
|
||||||
|
address: [4]u8,
|
||||||
|
};
|
||||||
|
std.os.setsockopt(
|
||||||
|
mdns_socket,
|
||||||
|
std.os.IPPROTO.IP,
|
||||||
|
std.os.linux.IP.ADD_MEMBERSHIP,
|
||||||
|
&std.mem.toBytes(ip_mreq_t{
|
||||||
|
.multicast_address = .{ 224, 0, 0, 251 },
|
||||||
|
.address = .{ 0, 0, 0, 0 },
|
||||||
|
}),
|
||||||
|
) catch unreachable;
|
||||||
|
|
||||||
|
const mdns_sock_addr = std.net.Address.parseIp4("0.0.0.0", c.MDNS_PORT) catch unreachable;
|
||||||
|
std.os.bind(mdns_socket, @ptrCast(&mdns_sock_addr), mdns_sock_addr.getOsSockLen()) catch |e| {
|
||||||
|
std.log.scoped(.mdns).err("bind failed = {}", .{e});
|
||||||
|
return error.OutOfMemory;
|
||||||
|
};
|
||||||
|
|
||||||
|
std.os.setsockopt(
|
||||||
|
mdns_socket,
|
||||||
|
std.os.IPPROTO.IP,
|
||||||
|
std.os.linux.IP.MULTICAST_IF,
|
||||||
|
&std.mem.toBytes(mdns_sock_addr.any),
|
||||||
|
) catch unreachable;
|
||||||
|
|
||||||
|
// ipv6 socket
|
||||||
|
const mdns_socket6 = std.os.socket(std.os.AF.INET6, std.os.SOCK.DGRAM, std.os.IPPROTO.UDP) catch |e| {
|
||||||
|
std.log.scoped(.mdns).err("failed to open mdns socket = {}", .{e});
|
||||||
|
return error.OutOfMemory;
|
||||||
|
};
|
||||||
|
|
||||||
|
std.os.setsockopt(
|
||||||
|
mdns_socket6,
|
||||||
|
std.os.SOL.SOCKET,
|
||||||
|
std.os.SO.REUSEADDR,
|
||||||
|
&std.mem.toBytes(@as(c_int, 1)),
|
||||||
|
) catch unreachable;
|
||||||
|
std.os.setsockopt(
|
||||||
|
mdns_socket6,
|
||||||
|
std.os.SOL.SOCKET,
|
||||||
|
std.os.SO.REUSEPORT,
|
||||||
|
&std.mem.toBytes(@as(c_int, 1)),
|
||||||
|
) catch unreachable;
|
||||||
|
std.os.setsockopt(
|
||||||
|
mdns_socket6,
|
||||||
|
std.os.IPPROTO.IPV6,
|
||||||
|
std.os.linux.IPV6.MULTICAST_HOPS,
|
||||||
|
&std.mem.toBytes(@as(c_int, 1)),
|
||||||
|
) catch unreachable;
|
||||||
|
std.os.setsockopt(
|
||||||
|
mdns_socket6,
|
||||||
|
std.os.IPPROTO.IPV6,
|
||||||
|
std.os.linux.IPV6.MULTICAST_LOOP,
|
||||||
|
&std.mem.toBytes(@as(c_int, 1)),
|
||||||
|
) catch unreachable;
|
||||||
|
std.os.setsockopt(
|
||||||
|
mdns_socket6,
|
||||||
|
std.os.IPPROTO.IPV6,
|
||||||
|
std.os.linux.IPV6.MULTICAST_IF,
|
||||||
|
&std.mem.toBytes(@as(c_int, 0)),
|
||||||
|
) catch unreachable;
|
||||||
|
|
||||||
|
const ipv6_mreq_t = extern struct {
|
||||||
|
addr: [16]u8,
|
||||||
|
interface: c_int,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: get ipv4 and ipv6 adress of server to send out over mDNS
|
||||||
|
// const SIOCGIFADDR = 0x8915;
|
||||||
|
// var ifr: std.os.ifreq = undefined;
|
||||||
|
// ifr.ifru.addr.family = std.os.AF.INET;
|
||||||
|
// const interface_name = "wlp170s0";
|
||||||
|
// @memset(&ifr.ifrn.name, 0);
|
||||||
|
// @memcpy(ifr.ifrn.name[0..interface_name.len], interface_name);
|
||||||
|
|
||||||
|
// const ifr_ret_value = std.os.linux.getErrno(std.os.linux.ioctl(mdns_socket6, SIOCGIFADDR, @intFromPtr(&ifr)));
|
||||||
|
// std.log.scoped(.mdns).debug("ioctl({}, SIOCGIFADDR, {*}) = {}", .{ mdns_socket6, &ifr, ifr_ret_value });
|
||||||
|
|
||||||
|
// std.os.ioctl_SIOCGIFINDEX(mdns_socket6, &ifr) catch unreachable;
|
||||||
|
// const interface = ifr.ifru.ivalue;
|
||||||
|
|
||||||
|
// const mdns_ipv6_multicast_group = std.net.Address.parseIp6("ff02::fb", c.MDNS_PORT) catch unreachable;
|
||||||
|
// var socklen = mdns_sock_addr6.getOsSockLen();
|
||||||
|
std.os.setsockopt(
|
||||||
|
mdns_socket6,
|
||||||
|
std.os.IPPROTO.IPV6,
|
||||||
|
std.os.linux.IPV6.ADD_MEMBERSHIP,
|
||||||
|
&std.mem.toBytes(ipv6_mreq_t{
|
||||||
|
.addr = .{ 0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFB },
|
||||||
|
.interface = 0,
|
||||||
|
}),
|
||||||
|
) catch unreachable;
|
||||||
|
|
||||||
|
const mdns_sock_addr6 = std.net.Address.parseIp6("::", c.MDNS_PORT) catch unreachable;
|
||||||
|
var socklen = mdns_sock_addr6.getOsSockLen();
|
||||||
|
std.os.bind(mdns_socket6, &mdns_sock_addr6.any, socklen) catch unreachable;
|
||||||
|
// std.os.listen(mdns_socket6, self.kernel_backlog) catch unreachable;
|
||||||
|
|
||||||
|
var mdns_listen_addr: std.net.Address = undefined;
|
||||||
|
std.os.getsockname(mdns_socket, &mdns_listen_addr.any, &socklen) catch unreachable;
|
||||||
|
const ipv4_sockaddr_string = try std.fmt.allocPrint(allocator, "{}", .{mdns_listen_addr});
|
||||||
|
|
||||||
|
socklen = mdns_sock_addr6.getOsSockLen();
|
||||||
|
var mdns_listen_addr6: std.net.Address = undefined;
|
||||||
|
std.os.getsockname(mdns_socket6, &mdns_listen_addr6.any, &socklen) catch unreachable;
|
||||||
|
const ipv6_sockaddr_string = try std.fmt.allocPrint(allocator, "{}", .{mdns_listen_addr6});
|
||||||
|
|
||||||
|
// // const SIOCGIFNAME = 0x8910;
|
||||||
|
|
||||||
|
// const ipv4_sockaddr: std.os.sockaddr.in = @bitCast(ifr.ifru.addr);
|
||||||
|
// // const ipv4_bytes: [4]u8 = @bitCast(ipv4_sockaddr.addr);
|
||||||
|
|
||||||
|
const service_string = try allocator.dupe(u8, "rummy._udp.local.");
|
||||||
|
|
||||||
|
var hostname_buffer: [std.os.HOST_NAME_MAX]u8 = undefined;
|
||||||
|
const hostname = std.os.gethostname(&hostname_buffer) catch unreachable;
|
||||||
|
|
||||||
|
// "<hostname>.<_service-name>._udp.local." string
|
||||||
|
const service_instance_string = try std.fmt.allocPrint(allocator, "{s}.{s}", .{
|
||||||
|
hostname,
|
||||||
|
service_string,
|
||||||
|
});
|
||||||
|
errdefer allocator.free(service_string);
|
||||||
|
|
||||||
|
const qualified_hostname = try std.fmt.allocPrint(allocator, "{s}.local.", .{hostname});
|
||||||
|
errdefer allocator.free(qualified_hostname);
|
||||||
|
std.log.scoped(.mdns).info("qualified hostname = \"{}\"", .{std.zig.fmtEscapes(qualified_hostname)});
|
||||||
|
|
||||||
|
// Create DNS records
|
||||||
|
|
||||||
|
var mdns_records = std.ArrayList(c.mdns_record_t).init(allocator);
|
||||||
|
defer mdns_records.deinit();
|
||||||
|
|
||||||
|
// DNS PTR that maps "<service_string>._udp.local." to "<hostname>.<service_string>._udp.local."
|
||||||
|
try mdns_records.append(c.mdns_record_t{
|
||||||
|
.name = .{ .str = service_string.ptr, .length = service_string.len },
|
||||||
|
.type = c.MDNS_RECORDTYPE_PTR,
|
||||||
|
.data = .{ .ptr = .{ .name = .{ .str = service_instance_string.ptr, .length = service_instance_string.len } } },
|
||||||
|
.rclass = 0,
|
||||||
|
.ttl = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// DNS SRV that maps "<hostname>.<service_string>._udp.local." to "<hostname>.local.:<PORT>"
|
||||||
|
try mdns_records.append(c.mdns_record_t{
|
||||||
|
.name = .{ .str = service_instance_string.ptr, .length = service_instance_string.len },
|
||||||
|
.type = c.MDNS_RECORDTYPE_SRV,
|
||||||
|
.data = .{ .srv = .{
|
||||||
|
.name = .{ .str = qualified_hostname.ptr, .length = qualified_hostname.len },
|
||||||
|
.port = address.port,
|
||||||
|
.priority = 0,
|
||||||
|
.weight = 0,
|
||||||
|
} },
|
||||||
|
.rclass = 0,
|
||||||
|
.ttl = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Add AAAA and A records to mdns service
|
||||||
|
// // DNS A that maps "<hostname>.local." to the an ip address
|
||||||
|
// const mdns_a_record = c.mdns_record_t{
|
||||||
|
// .name = .{ .str = qualified_hostname.ptr, .length = qualified_hostname.len },
|
||||||
|
// .type = c.MDNS_RECORDTYPE_A,
|
||||||
|
// .data = .{ .a = .{
|
||||||
|
// .addr = @bitCast(ipv4_sockaddr),
|
||||||
|
// } },
|
||||||
|
// .rclass = 0,
|
||||||
|
// .ttl = 0,
|
||||||
|
// };
|
||||||
|
// try mdns_records.append(mdns_a_record);
|
||||||
|
|
||||||
|
const mdns_record_slice = try mdns_records.toOwnedSlice();
|
||||||
|
errdefer allocator.free(mdns_record_slice);
|
||||||
|
|
||||||
|
this.* = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.mdns_addr4_string = ipv4_sockaddr_string,
|
||||||
|
.mdns_addr6_string = ipv6_sockaddr_string,
|
||||||
|
.mdns_socket4 = mdns_socket,
|
||||||
|
.mdns_socket6 = mdns_socket6,
|
||||||
|
.service_string = service_string,
|
||||||
|
.service_instance_string = service_instance_string,
|
||||||
|
.qualified_hostname = qualified_hostname,
|
||||||
|
.mdns_records = mdns_record_slice,
|
||||||
|
.mdns_thread = undefined,
|
||||||
|
.mdns_thread_should_stop = std.atomic.Value(bool).init(false),
|
||||||
|
|
||||||
|
.address = address,
|
||||||
|
.server = server,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.mdns_thread = std.Thread.spawn(.{}, mdns_thread_main, .{this}) catch return error.OutOfMemory;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handler(pointer: ?*anyopaque, request: Request) Handler.Error!Response {
|
||||||
|
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(request.allocator);
|
var arena = std.heap.ArenaAllocator.init(request.allocator);
|
||||||
errdefer arena.deinit();
|
errdefer arena.deinit();
|
||||||
|
|
||||||
var text = try Element.Text.create(arena.allocator(), "Hosting Multiplayer Game");
|
// var text = try Element.Text.create(arena.allocator(), try std.fmt.allocPrint(arena.allocator(), "Hosting Multiplayer Game at {}:{}", .{ this.address.host, this.address.port }));
|
||||||
|
var mdns_addr4_string_text = try Element.Text.create(request.allocator, this.mdns_addr4_string);
|
||||||
|
var mdns_addr6_string_text = try Element.Text.create(request.allocator, this.mdns_addr6_string);
|
||||||
|
var service_instance_string_text = try Element.Text.create(request.allocator, this.service_instance_string);
|
||||||
|
var qualified_hostname_text = try Element.Text.create(request.allocator, this.qualified_hostname);
|
||||||
|
|
||||||
var page = try Element.Page.create(arena.allocator());
|
var page = try Element.Page.create(request.allocator);
|
||||||
try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, text.element());
|
// try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, text.element());
|
||||||
|
try page.addElement(.{ 0.3, 0.6 }, .{ 0.5, 0.5 }, mdns_addr4_string_text.element());
|
||||||
|
try page.addElement(.{ 0.7, 0.6 }, .{ 0.5, 0.5 }, mdns_addr6_string_text.element());
|
||||||
|
try page.addElement(.{ 0.5, 0.7 }, .{ 0.5, 0.5 }, service_instance_string_text.element());
|
||||||
|
try page.addElement(.{ 0.5, 0.9 }, .{ 0.5, 0.5 }, qualified_hostname_text.element());
|
||||||
|
|
||||||
return Response{ .arena = arena, .body = .{ .element = page.element() } };
|
return Response{ .arena = arena, .body = .{ .element = page.element() } };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn deinit(pointer: ?*anyopaque) void {
|
||||||
|
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||||
|
|
||||||
|
c.enet_host_destroy(this.server);
|
||||||
|
|
||||||
|
this.mdns_thread_should_stop.store(true, .Monotonic);
|
||||||
|
this.mdns_thread.join();
|
||||||
|
|
||||||
|
this.allocator.free(this.mdns_records);
|
||||||
|
this.allocator.free(this.mdns_addr4_string);
|
||||||
|
this.allocator.free(this.mdns_addr6_string);
|
||||||
|
this.allocator.free(this.service_string);
|
||||||
|
this.allocator.free(this.service_instance_string);
|
||||||
|
this.allocator.free(this.qualified_hostname);
|
||||||
|
|
||||||
|
this.allocator.destroy(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mdns_thread_main(this: *@This()) !void {
|
||||||
|
const buffer = try this.allocator.alloc(u8, 2048);
|
||||||
|
defer this.allocator.free(buffer);
|
||||||
|
|
||||||
|
const log = std.log.scoped(.mdns);
|
||||||
|
log.info("mdns service started", .{});
|
||||||
|
|
||||||
|
_ = c.mdns_announce_multicast(
|
||||||
|
this.mdns_socket4,
|
||||||
|
buffer.ptr,
|
||||||
|
buffer.len,
|
||||||
|
this.mdns_records[0],
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
this.mdns_records[1..].ptr,
|
||||||
|
this.mdns_records[1..].len,
|
||||||
|
);
|
||||||
|
_ = c.mdns_announce_multicast(
|
||||||
|
this.mdns_socket6,
|
||||||
|
buffer.ptr,
|
||||||
|
buffer.len,
|
||||||
|
this.mdns_records[0],
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
this.mdns_records[1..].ptr,
|
||||||
|
this.mdns_records[1..].len,
|
||||||
|
);
|
||||||
|
|
||||||
|
while (!this.mdns_thread_should_stop.load(.Monotonic)) {
|
||||||
|
var pollfds = [_]std.os.pollfd{
|
||||||
|
.{ .fd = this.mdns_socket4, .events = std.os.POLL.IN, .revents = undefined },
|
||||||
|
.{ .fd = this.mdns_socket6, .events = std.os.POLL.IN, .revents = undefined },
|
||||||
|
};
|
||||||
|
_ = std.os.poll(&pollfds, 1000) catch break;
|
||||||
|
for (pollfds) |pollfd| {
|
||||||
|
if (pollfd.revents & std.os.POLL.IN == 0) continue;
|
||||||
|
_ = c.mdns_socket_listen(pollfd.fd, buffer.ptr, buffer.len, &mdns_thread_service_callback, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("mdns service stopping", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mdns_thread_service_callback(
|
||||||
|
sock: c_int,
|
||||||
|
from: ?*const c.sockaddr,
|
||||||
|
addrlen: usize,
|
||||||
|
entry: c.mdns_entry_type_t,
|
||||||
|
query_id: u16,
|
||||||
|
rtype: u16,
|
||||||
|
rclass: u16,
|
||||||
|
ttl: u32,
|
||||||
|
data_ptr_opaque: ?*const anyopaque,
|
||||||
|
data_len: usize,
|
||||||
|
name_offset: usize,
|
||||||
|
name_length: usize,
|
||||||
|
record_offset: usize,
|
||||||
|
record_length: usize,
|
||||||
|
userdata: ?*anyopaque,
|
||||||
|
) callconv(.C) c_int {
|
||||||
|
const log = std.log.scoped(.mdns);
|
||||||
|
if (entry != c.MDNS_ENTRYTYPE_QUESTION) return 0;
|
||||||
|
|
||||||
|
const DNS_SD = "_services._dns-sd._udp.local.";
|
||||||
|
const this: *@This() = @ptrCast(@alignCast(userdata));
|
||||||
|
_ = ttl;
|
||||||
|
_ = record_offset;
|
||||||
|
_ = record_length;
|
||||||
|
_ = name_length;
|
||||||
|
|
||||||
|
// const data_ptr: [*]const u8 = @ptrCast(data_ptr_opaque.?);
|
||||||
|
// const data = data_ptr[0..data_len];
|
||||||
|
|
||||||
|
var name_buffer: [128]u8 = undefined;
|
||||||
|
var offset = name_offset;
|
||||||
|
const name_mdns_str = c.mdns_string_extract(data_ptr_opaque, data_len, &offset, &name_buffer, name_buffer.len);
|
||||||
|
const name = name_mdns_str.str[0..name_mdns_str.length];
|
||||||
|
|
||||||
|
const std_from: ?std.net.Address = if (from) |f| std.net.Address{ .any = @bitCast(f.*) } else null;
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, name, DNS_SD)) {
|
||||||
|
const answer = c.mdns_record_t{
|
||||||
|
.name = .{ .str = name.ptr, .length = name.len },
|
||||||
|
.type = c.MDNS_RECORDTYPE_PTR,
|
||||||
|
.data = .{ .ptr = .{ .name = .{ .str = DNS_SD, .length = DNS_SD.len } } },
|
||||||
|
};
|
||||||
|
|
||||||
|
var sendbuffer: [1024]u8 = undefined;
|
||||||
|
if (rclass & c.MDNS_UNICAST_RESPONSE != 0) {
|
||||||
|
const ret_val = c.mdns_query_answer_unicast(
|
||||||
|
sock,
|
||||||
|
from,
|
||||||
|
addrlen,
|
||||||
|
&sendbuffer,
|
||||||
|
sendbuffer.len,
|
||||||
|
query_id,
|
||||||
|
rtype,
|
||||||
|
name.ptr,
|
||||||
|
name.len,
|
||||||
|
answer,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
if (ret_val < 0) {
|
||||||
|
log.warn("{s}:{} failed to answer query, error code = {}", .{ @src().file, @src().line, ret_val });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const ret_val = c.mdns_query_answer_multicast(
|
||||||
|
sock,
|
||||||
|
&sendbuffer,
|
||||||
|
sendbuffer.len,
|
||||||
|
answer,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
if (ret_val < 0) {
|
||||||
|
log.warn("{s}:{} failed to answer query, error code = {}", .{ @src().file, @src().line, ret_val });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (std.mem.eql(u8, name, this.service_string)) {
|
||||||
|
const answer = this.mdns_records[0];
|
||||||
|
const additional = this.mdns_records[1..];
|
||||||
|
|
||||||
|
log.debug("heard service name \"{}\", sending answer {} and {} additional records", .{ std.zig.fmtEscapes(name), answer, additional.len });
|
||||||
|
|
||||||
|
var sendbuffer: [1024]u8 = undefined;
|
||||||
|
if (rclass & c.MDNS_UNICAST_RESPONSE != 0) {
|
||||||
|
log.debug("{s}:{} unicast response to {?}", .{ @src().file, @src().line, std_from });
|
||||||
|
const ret_val = c.mdns_query_answer_unicast(
|
||||||
|
sock,
|
||||||
|
from,
|
||||||
|
addrlen,
|
||||||
|
&sendbuffer,
|
||||||
|
sendbuffer.len,
|
||||||
|
query_id,
|
||||||
|
rtype,
|
||||||
|
name.ptr,
|
||||||
|
name.len,
|
||||||
|
answer,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
additional.ptr,
|
||||||
|
additional.len,
|
||||||
|
);
|
||||||
|
if (ret_val < 0) {
|
||||||
|
log.warn("{s}:{} failed to answer query with unicast, error code = {}", .{ @src().file, @src().line, ret_val });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.debug("{s}:{} multicast response", .{ @src().file, @src().line });
|
||||||
|
const ret_val = c.mdns_query_answer_multicast(
|
||||||
|
sock,
|
||||||
|
&sendbuffer,
|
||||||
|
sendbuffer.len,
|
||||||
|
answer,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
additional.ptr,
|
||||||
|
additional.len,
|
||||||
|
);
|
||||||
|
if (ret_val < 0) {
|
||||||
|
log.warn("{s}:{} failed to answer query with multicast, error code = {}", .{ @src().file, @src().line, ret_val });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (std.mem.eql(u8, name, this.service_instance_string)) {
|
||||||
|
const answer = this.mdns_records[1];
|
||||||
|
|
||||||
|
var sendbuffer: [1024]u8 = undefined;
|
||||||
|
if (rclass & c.MDNS_UNICAST_RESPONSE != 0) {
|
||||||
|
_ = c.mdns_query_answer_unicast(
|
||||||
|
sock,
|
||||||
|
from,
|
||||||
|
addrlen,
|
||||||
|
&sendbuffer,
|
||||||
|
sendbuffer.len,
|
||||||
|
query_id,
|
||||||
|
rtype,
|
||||||
|
name.ptr,
|
||||||
|
name.len,
|
||||||
|
answer,
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_ = c.mdns_query_answer_multicast(
|
||||||
|
sock,
|
||||||
|
&sendbuffer,
|
||||||
|
sendbuffer.len,
|
||||||
|
answer,
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const Handler = protocol.Handler;
|
const Handler = protocol.Handler;
|
||||||
const Request = protocol.Request;
|
const Request = protocol.Request;
|
||||||
const Response = protocol.Response;
|
const Response = protocol.Response;
|
||||||
|
|
||||||
|
const c = @import("./c.zig");
|
||||||
const protocol = @import("./protocol.zig");
|
const protocol = @import("./protocol.zig");
|
||||||
const Element = @import("./Element.zig");
|
const Element = @import("./Element.zig");
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue