From 45a78e9d94318fffd0e282e00a8fced6681a9185 Mon Sep 17 00:00:00 2001 From: zfl9 Date: Tue, 16 Apr 2024 17:55:53 +0800 Subject: [PATCH 01/32] tcp pipelining queries --- src/Upstream.zig | 456 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 345 insertions(+), 111 deletions(-) diff --git a/src/Upstream.zig b/src/Upstream.zig index 3a3a7b3..a9a01cb 100644 --- a/src/Upstream.zig +++ b/src/Upstream.zig @@ -28,7 +28,7 @@ comptime { const Upstream = @This(); // runtime info -fdobj: ?*EvLoop.Fd = null, // udp +ctx: ?*anyopaque = null, // config info host: ?cc.ConstStr, // DoT SNI @@ -69,7 +69,7 @@ fn init(tag: Tag, proto: Proto, addr: *const cc.SockAddr, host: []const u8, ip: } fn deinit(self: *const Upstream) void { - assert(self.fdobj == null); + assert(self.ctx == null); if (self.host) |host| g.allocator.free(cc.strslice_c(host)); @@ -87,122 +87,33 @@ fn eql(self: *const Upstream, proto: Proto, addr: *const cc.SockAddr, host: []co // zig fmt: on } -// ====================================================== - -/// for udp upstream -fn on_eol(self: *Upstream) void { - assert(self.proto == .udpi or self.proto == .udp); - - const fdobj = self.fdobj orelse return; - self.fdobj = null; // set to null - - assert(fdobj.write_frame == null); - - // log.debug( - // @src(), - // "udp upstream socket(fd:%d, url:'%s', group:%s) is end-of-life ...", - // .{ fdobj.fd, self.url, @tagName(self.group.tag).ptr }, - // ); - - if (fdobj.read_frame) |frame| { - co.do_resume(frame); - } else { - // this coroutine may be sending a response to the tcp client (suspended) - } -} - -/// for udp upstream -fn is_eol(self: *const Upstream, in_fdobj: *EvLoop.Fd) bool { - return self.fdobj != in_fdobj; -} - -// ====================================================== - /// [nosuspend] send query to upstream fn send(self: *Upstream, qmsg: *RcMsg) void { switch (self.proto) { - .tcpi, .tcp => self.send_tcp(qmsg), - .udpi, .udp => self.send_udp(qmsg), - .tls => self.send_tls(qmsg), + .tcpi, .tcp => self.tcp_send(qmsg), + .udpi, .udp => self.udp_send(qmsg), + .tls => self.tls_send(qmsg), else => unreachable, } } // ====================================================== -fn send_tcp(self: *Upstream, qmsg: *RcMsg) void { - // TODO: pipeline && retry the unanswered query - return co.create(do_send_tcp, .{ self, qmsg }); +fn udp_get_fdobj(self: *const Upstream) ?*EvLoop.Fd { + assert(self.proto == .udpi or self.proto == .udp); + return cc.ptrcast(?*EvLoop.Fd, self.ctx); } -fn do_send_tcp(self: *Upstream, qmsg: *RcMsg) void { - defer co.terminate(@frame(), @frameSize(do_send_tcp)); - - const fd = net.new_tcp_conn_sock(self.addr.family()) orelse return; - - const fdobj = EvLoop.Fd.new(fd); - defer fdobj.free(); - - // must be exec before the suspend point - _ = qmsg.ref(); - defer qmsg.unref(); - - const e: struct { op: cc.ConstStr, msg: ?cc.ConstStr = null } = e: { - g.evloop.connect(fdobj, &self.addr) orelse break :e .{ .op = "connect" }; - - var iov = [_]cc.iovec_t{ - .{ - .iov_base = std.mem.asBytes(&cc.htons(qmsg.len)), - .iov_len = @sizeOf(u16), - }, - .{ - .iov_base = qmsg.msg().ptr, - .iov_len = qmsg.len, - }, - }; - const msg = cc.msghdr_t{ - .msg_iov = &iov, - .msg_iovlen = iov.len, - }; - g.evloop.sendmsg(fdobj, &msg, 0) orelse break :e .{ .op = "send_query" }; - - // read the len - var rlen: u16 = undefined; - g.evloop.recv_exactly(fdobj, std.mem.asBytes(&rlen), 0) orelse - break :e .{ .op = "read_len", .msg = if (cc.errno() == 0) "connection closed" else null }; - - rlen = cc.ntohs(rlen); - if (rlen == 0) - break :e .{ .op = "read_len", .msg = "length field is 0" }; - - const rmsg = RcMsg.new(rlen); - defer rmsg.free(); - - // read the msg - rmsg.len = rlen; - g.evloop.recv_exactly(fdobj, rmsg.msg(), 0) orelse - break :e .{ .op = "read_msg", .msg = if (cc.errno() == 0) "connection closed" else null }; - - // send to requester - server.on_reply(rmsg, self); - - return; - }; - - const src = @src(); - if (e.msg) |msg| - log.err(src, "%s(%d, '%s') failed: %s", .{ e.op, fd, self.url, msg }) - else - log.err(src, "%s(%d, '%s') failed: (%d) %m", .{ e.op, fd, self.url, cc.errno() }); +fn udp_set_fdobj(self: *Upstream, fdobj: ?*EvLoop.Fd) void { + assert(self.proto == .udpi or self.proto == .udp); + self.ctx = fdobj; } -// ====================================================== - -fn send_udp(self: *Upstream, qmsg: *RcMsg) void { - const fd = if (self.fdobj) |fdobj| fdobj.fd else b: { +fn udp_send(self: *Upstream, qmsg: *RcMsg) void { + const fd = if (self.udp_get_fdobj()) |fdobj| fdobj.fd else b: { const fd = net.new_sock(self.addr.family(), .udp) orelse return; - co.create(recv_udp, .{ self, fd }); - assert(self.fdobj != null); + co.create(udp_recv, .{ self, fd }); + assert(self.udp_get_fdobj() != null); break :b fd; }; @@ -239,18 +150,18 @@ fn send_udp(self: *Upstream, qmsg: *RcMsg) void { log.err(@src(), "send_query(%d, '%s') failed: (%d) %m", .{ fd, self.url, cc.errno() }); } -fn recv_udp(self: *Upstream, fd: c_int) void { - defer co.terminate(@frame(), @frameSize(recv_udp)); +fn udp_recv(self: *Upstream, fd: c_int) void { + defer co.terminate(@frame(), @frameSize(udp_recv)); const fdobj = EvLoop.Fd.new(fd); defer fdobj.free(); - self.fdobj = fdobj; + self.udp_set_fdobj(fdobj); var free_rmsg: ?*RcMsg = null; defer if (free_rmsg) |rmsg| rmsg.free(); - while (!self.is_eol(fdobj)) { + while (!self.udp_is_eol(fdobj)) { const rmsg = free_rmsg orelse RcMsg.new(c.DNS_EDNS_MAXSIZE); free_rmsg = null; @@ -261,7 +172,7 @@ fn recv_udp(self: *Upstream, fd: c_int) void { rmsg.unref(); } - const rlen = while (!self.is_eol(fdobj)) { + const rlen = while (!self.udp_is_eol(fdobj)) { break cc.recv(fd, rmsg.buf(), 0) orelse { if (cc.errno() != c.EAGAIN) { log.err(@src(), "recv(%d, '%s') failed: (%d) %m", .{ fd, self.url, cc.errno() }); @@ -278,9 +189,332 @@ fn recv_udp(self: *Upstream, fd: c_int) void { } } +/// for udp upstream +fn udp_is_eol(self: *const Upstream, in_fdobj: *EvLoop.Fd) bool { + return self.udp_get_fdobj() != in_fdobj; +} + +/// for udp upstream +fn udp_on_eol(self: *Upstream) void { + const fdobj = self.udp_get_fdobj() orelse return; + self.udp_set_fdobj(null); // set to null + + assert(fdobj.write_frame == null); + + // log.debug( + // @src(), + // "udp upstream socket(fd:%d, url:'%s', group:%s) is end-of-life ...", + // .{ fdobj.fd, self.url, @tagName(self.group.tag).ptr }, + // ); + + if (fdobj.read_frame) |frame| { + co.do_resume(frame); + } else { + // this coroutine may be sending a response to the tcp client (suspended) + } +} + +// ====================================================== + +const TcpCtx = struct { + upstream: *const Upstream, + fdobj: ?*EvLoop.Fd = null, + send_queue: MsgQueue = .{}, + ack_list: std.AutoHashMapUnmanaged(u16, *RcMsg) = .{}, + + const MsgQueue = struct { + head: ?*Node = null, + tail: ?*Node = null, + waiter: ?anyframe = null, + + const Node = struct { + msg: *RcMsg, + next: *Node, + }; + + /// `null`: cancel wait + var _pushed_msg: ?*RcMsg = null; + + fn do_push(self: *MsgQueue, msg: *RcMsg, pos: enum { front, back }) void { + if (self.waiter) |waiter| { + assert(self.is_empty()); + _pushed_msg = msg; + co.do_resume(waiter); + return; + } + + const node = g.allocator.create(Node) catch unreachable; + node.* = .{ + .msg = msg, + .next = undefined, + }; + + if (self.is_empty()) { + self.head = node; + self.tail = node; + } else switch (pos) { + .front => { + node.next = self.head.?; + self.head = node; + }, + .back => { + self.tail.?.next = node; + self.tail = node; + }, + } + } + + pub fn push(self: *MsgQueue, msg: *RcMsg) void { + return self.do_push(msg, .back); + } + + pub fn push_front(self: *MsgQueue, msg: *RcMsg) void { + return self.do_push(msg, .front); + } + + /// `null`: cancel wait + pub fn pop(self: *MsgQueue, blocking: bool) ?*RcMsg { + if (self.head) |node| { + defer g.allocator.destroy(node); + if (node == self.tail) { + self.head = null; + self.tail = null; + } else { + self.head = node.next; + assert(self.tail != null); + } + return node.msg; + } else { + if (!blocking) + return null; + self.waiter = @frame(); + suspend {} + self.waiter = null; + return _pushed_msg; + } + } + + pub fn cancel_wait(self: *MsgQueue) void { + if (self.waiter) |waiter| { + assert(self.is_empty()); + _pushed_msg = null; + co.do_resume(waiter); + } + } + + pub fn is_empty(self: *const MsgQueue) bool { + return self.head == null; + } + + /// clear && msg.unref() + pub fn clear(self: *MsgQueue) void { + while (self.pop(false)) |msg| + msg.unref(); + } + }; + + pub fn new(upstream: *const Upstream) *TcpCtx { + const self = g.allocator.create(TcpCtx) catch unreachable; + self.* = .{ + .upstream = upstream, + }; + return self; + } + + /// [tcp_send] add to send queue, `qmsg.ref++` + pub fn push_qmsg(self: *TcpCtx, qmsg: *RcMsg) void { + self.send_queue.push(qmsg.ref()); + + if (self.fdobj == null) + self.start(); + } + + /// [async] used to send qmsg to upstream + /// pop from send_queue && add to ack_list + fn pop_qmsg(self: *TcpCtx, in_fdobj: *EvLoop.Fd) ?*RcMsg { + if (self.restarted(in_fdobj)) return null; + const qmsg = self.send_queue.pop(true) orelse return null; + self.on_sending(qmsg); + return qmsg; + } + + fn clear_ack_list(self: *TcpCtx, op: enum { resend, unref }) void { + var it = self.ack_list.valueIterator(); + while (it.next()) |value_ptr| { + const qmsg = value_ptr.*; + switch (op) { + .resend => self.send_queue.push_front(qmsg), + .unref => qmsg.unref(), + } + } + self.ack_list.clearRetainingCapacity(); + } + + /// add qmsg to ack_list + fn on_sending(self: *TcpCtx, qmsg: *RcMsg) void { + const qid = dns.get_id(qmsg.msg()); + self.ack_list.putNoClobber(g.allocator, qid, qmsg) catch unreachable; + } + + /// remove qmsg from ack_list && qmsg.unref() + fn on_reply(self: *TcpCtx, rmsg: *const RcMsg) void { + const qid = dns.get_id(rmsg.msg()); + if (self.ack_list.fetchRemove(qid)) |kv| { + const qmsg = kv.value; + qmsg.unref(); + } else { + log.warn(@src(), "unexpected msg_id:%u from %s", .{ cc.to_uint(qid), self.upstream.url }); + } + } + + /// [fatal error] clear all qmsg and unref them + fn on_fatal(self: *TcpCtx) void { + self.clear_ack_list(.unref); + self.send_queue.clear(); + } + + fn start(self: *TcpCtx) void { + assert(self.fdobj == null); + + co.create(TcpCtx.send, .{self}); + } + + fn restart(self: *TcpCtx) void { + self.fdobj = null; + self.send_queue.cancel_wait(); + + // ack_list => send_queue + self.clear_ack_list(.resend); + + if (!self.send_queue.is_empty()) + self.start(); + } + + fn restarted(self: *const TcpCtx, in_fdobj: *const EvLoop.Fd) bool { + return self.fdobj != in_fdobj; + } + + fn send(self: *TcpCtx) void { + defer co.terminate(@frame(), @frameSize(TcpCtx.send)); + + // nosuspend + const fd = net.new_tcp_conn_sock(self.upstream.addr.family()) orelse return self.on_fatal(); + + const fdobj = EvLoop.Fd.new(fd); + self.fdobj = fdobj; + + defer { + if (fdobj == self.fdobj) + self.restart(); + fdobj.free(); + } + + // async + const err: cc.ConstStr = e: { + g.evloop.connect(fdobj, &self.upstream.addr) orelse { + self.on_fatal(); + break :e "connect"; + }; + + co.create(recv, .{self}); + + while (self.pop_qmsg(fdobj)) |qmsg| { + var iov = [_]cc.iovec_t{ + .{ + .iov_base = std.mem.asBytes(&cc.htons(qmsg.len)), + .iov_len = @sizeOf(u16), + }, + .{ + .iov_base = qmsg.msg().ptr, + .iov_len = qmsg.len, + }, + }; + const msg = cc.msghdr_t{ + .msg_iov = &iov, + .msg_iovlen = iov.len, + }; + g.evloop.sendmsg(fdobj, &msg, 0) orelse break :e "send_query"; + } + + return; + }; + + log.err(@src(), "%s(%d, '%s') failed: (%d) %m", .{ err, fdobj.fd, self.upstream.url, cc.errno() }); + } + + fn recv(self: *TcpCtx) void { + defer co.terminate(@frame(), @frameSize(recv)); + + // nosuspend + const fdobj = self.fdobj.?.ref(); + + defer { + if (fdobj == self.fdobj) + self.restart(); + fdobj.unref(); + } + + const src = @src(); + + // async + const err: struct { op: cc.ConstStr, msg: ?cc.ConstStr = null } = e: { + var free_rmsg: ?*RcMsg = null; + defer if (free_rmsg) |rmsg| rmsg.free(); + + while (!self.restarted(fdobj)) { + // read the len + var rlen: u16 = undefined; + g.evloop.recv_exactly(fdobj, std.mem.asBytes(&rlen), 0) orelse + if (cc.errno() == 0) return else break :e .{ .op = "read_len" }; + + rlen = cc.ntohs(rlen); + if (rlen < c.DNS_MSG_MINSIZE) + break :e .{ .op = "read_len", .msg = "invalid msg len" }; + + const rmsg: *RcMsg = if (free_rmsg) |rmsg| rmsg.realloc(rlen) else RcMsg.new(rlen); + free_rmsg = null; + + defer { + if (rmsg.is_unique()) + free_rmsg = rmsg + else + rmsg.unref(); + } + + // read the msg + rmsg.len = rlen; + g.evloop.recv_exactly(fdobj, rmsg.msg(), 0) orelse + break :e .{ .op = "read_msg", .msg = if (cc.errno() == 0) "connection closed" else null }; + + self.on_reply(rmsg); + + server.on_reply(rmsg, self.upstream); + } + + return; + }; + + if (err.msg) |msg| + log.err(src, "%s(%d, '%s') failed: %s", .{ err.op, fdobj.fd, self.upstream.url, msg }) + else + log.err(src, "%s(%d, '%s') failed: (%d) %m", .{ err.op, fdobj.fd, self.upstream.url, cc.errno() }); + } +}; + +fn tcp_ctx(self: *Upstream) *TcpCtx { + assert(self.proto == .tcpi or self.proto == .tcp); + if (self.ctx == null) + self.ctx = TcpCtx.new(self); + return cc.ptrcast(*TcpCtx, self.ctx.?); +} + +fn tcp_send(self: *Upstream, qmsg: *RcMsg) void { + self.tcp_ctx().push_qmsg(qmsg); +} + // ====================================================== -fn send_tls(self: *Upstream, qmsg: *RcMsg) void { +fn tls_send(self: *Upstream, qmsg: *RcMsg) void { _ = qmsg; // TODO @@ -535,7 +769,7 @@ pub const Group = struct { }; if (eol) - upstream.on_eol(); + upstream.udp_on_eol(); } } From 7f8dfcb58f60501ac6e2300c000321baa4483f64 Mon Sep 17 00:00:00 2001 From: zfl9 Date: Wed, 17 Apr 2024 14:34:17 +0800 Subject: [PATCH 02/32] tcp pipelining queries --- src/Upstream.zig | 125 ++++++++++++++++++++++++++--------------------- src/server.zig | 2 +- 2 files changed, 69 insertions(+), 58 deletions(-) diff --git a/src/Upstream.zig b/src/Upstream.zig index a9a01cb..5ee8b36 100644 --- a/src/Upstream.zig +++ b/src/Upstream.zig @@ -189,12 +189,10 @@ fn udp_recv(self: *Upstream, fd: c_int) void { } } -/// for udp upstream fn udp_is_eol(self: *const Upstream, in_fdobj: *EvLoop.Fd) bool { return self.udp_get_fdobj() != in_fdobj; } -/// for udp upstream fn udp_on_eol(self: *Upstream) void { const fdobj = self.udp_get_fdobj() orelse return; self.udp_set_fdobj(null); // set to null @@ -219,8 +217,13 @@ fn udp_on_eol(self: *Upstream) void { const TcpCtx = struct { upstream: *const Upstream, fdobj: ?*EvLoop.Fd = null, - send_queue: MsgQueue = .{}, - ack_list: std.AutoHashMapUnmanaged(u16, *RcMsg) = .{}, + send_list: MsgQueue = .{}, // qmsg to be sent + ack_list: std.AutoHashMapUnmanaged(u16, *RcMsg) = .{}, // qmsg to be ack + pending_n: u16 = 0, // outstanding queries: send_list + ack_list + healthy: bool = false, // current connection processed at least one query ? + + /// must <= u16_max + const PENDING_MAX = 1000; const MsgQueue = struct { head: ?*Node = null, @@ -323,33 +326,27 @@ const TcpCtx = struct { /// [tcp_send] add to send queue, `qmsg.ref++` pub fn push_qmsg(self: *TcpCtx, qmsg: *RcMsg) void { - self.send_queue.push(qmsg.ref()); + if (self.pending_n >= PENDING_MAX) { + log.warn(@src(), "too many pending queries: %u", .{cc.to_uint(self.pending_n)}); + return; + } + + self.pending_n += 1; + self.send_list.push(qmsg.ref()); if (self.fdobj == null) self.start(); } /// [async] used to send qmsg to upstream - /// pop from send_queue && add to ack_list + /// pop from send_list && add to ack_list fn pop_qmsg(self: *TcpCtx, in_fdobj: *EvLoop.Fd) ?*RcMsg { - if (self.restarted(in_fdobj)) return null; - const qmsg = self.send_queue.pop(true) orelse return null; + if (!self.fdobj_ok(in_fdobj)) return null; + const qmsg = self.send_list.pop(true) orelse return null; self.on_sending(qmsg); return qmsg; } - fn clear_ack_list(self: *TcpCtx, op: enum { resend, unref }) void { - var it = self.ack_list.valueIterator(); - while (it.next()) |value_ptr| { - const qmsg = value_ptr.*; - switch (op) { - .resend => self.send_queue.push_front(qmsg), - .unref => qmsg.unref(), - } - } - self.ack_list.clearRetainingCapacity(); - } - /// add qmsg to ack_list fn on_sending(self: *TcpCtx, qmsg: *RcMsg) void { const qid = dns.get_id(qmsg.msg()); @@ -360,6 +357,8 @@ const TcpCtx = struct { fn on_reply(self: *TcpCtx, rmsg: *const RcMsg) void { const qid = dns.get_id(rmsg.msg()); if (self.ack_list.fetchRemove(qid)) |kv| { + self.healthy = true; + self.pending_n -= 1; const qmsg = kv.value; qmsg.unref(); } else { @@ -367,54 +366,66 @@ const TcpCtx = struct { } } - /// [fatal error] clear all qmsg and unref them - fn on_fatal(self: *TcpCtx) void { - self.clear_ack_list(.unref); - self.send_queue.clear(); - } - - fn start(self: *TcpCtx) void { - assert(self.fdobj == null); + /// network/socket error + fn on_failed(self: *TcpCtx) void { + self.fdobj = null; + self.send_list.cancel_wait(); - co.create(TcpCtx.send, .{self}); + if (self.healthy) { + self.clear_ack_list(.resend); + if (!self.send_list.is_empty()) self.start(); + } else { + self.clear_ack_list(.unref); + self.send_list.clear(); + self.pending_n = 0; + } } - fn restart(self: *TcpCtx) void { - self.fdobj = null; - self.send_queue.cancel_wait(); - - // ack_list => send_queue - self.clear_ack_list(.resend); + fn clear_ack_list(self: *TcpCtx, op: enum { resend, unref }) void { + var it = self.ack_list.valueIterator(); + while (it.next()) |value_ptr| { + const qmsg = value_ptr.*; + switch (op) { + .resend => self.send_list.push_front(qmsg), + .unref => qmsg.unref(), + } + } + self.ack_list.clearRetainingCapacity(); + } - if (!self.send_queue.is_empty()) - self.start(); + /// check if disconnected or reconnected + fn fdobj_ok(self: *const TcpCtx, in_fdobj: *const EvLoop.Fd) bool { + return in_fdobj == self.fdobj; } - fn restarted(self: *const TcpCtx, in_fdobj: *const EvLoop.Fd) bool { - return self.fdobj != in_fdobj; + fn start(self: *TcpCtx) void { + assert(self.fdobj == null); + assert(self.pending_n > 0); + assert(!self.send_list.is_empty()); + assert(self.ack_list.count() == 0); + + self.healthy = false; + co.create(TcpCtx.send, .{self}); } fn send(self: *TcpCtx) void { defer co.terminate(@frame(), @frameSize(TcpCtx.send)); // nosuspend - const fd = net.new_tcp_conn_sock(self.upstream.addr.family()) orelse return self.on_fatal(); + const fd = net.new_tcp_conn_sock(self.upstream.addr.family()) orelse return self.on_failed(); const fdobj = EvLoop.Fd.new(fd); self.fdobj = fdobj; defer { - if (fdobj == self.fdobj) - self.restart(); + if (self.fdobj_ok(fdobj)) + self.on_failed(); fdobj.free(); } // async const err: cc.ConstStr = e: { - g.evloop.connect(fdobj, &self.upstream.addr) orelse { - self.on_fatal(); - break :e "connect"; - }; + g.evloop.connect(fdobj, &self.upstream.addr) orelse break :e "connect"; co.create(recv, .{self}); @@ -449,19 +460,17 @@ const TcpCtx = struct { const fdobj = self.fdobj.?.ref(); defer { - if (fdobj == self.fdobj) - self.restart(); + if (self.fdobj_ok(fdobj)) + self.on_failed(); fdobj.unref(); } - const src = @src(); - // async const err: struct { op: cc.ConstStr, msg: ?cc.ConstStr = null } = e: { var free_rmsg: ?*RcMsg = null; defer if (free_rmsg) |rmsg| rmsg.free(); - while (!self.restarted(fdobj)) { + while (self.fdobj_ok(fdobj)) { // read the len var rlen: u16 = undefined; g.evloop.recv_exactly(fdobj, std.mem.asBytes(&rlen), 0) orelse @@ -486,7 +495,8 @@ const TcpCtx = struct { g.evloop.recv_exactly(fdobj, rmsg.msg(), 0) orelse break :e .{ .op = "read_msg", .msg = if (cc.errno() == 0) "connection closed" else null }; - self.on_reply(rmsg); + if (self.fdobj_ok(fdobj)) + self.on_reply(rmsg); server.on_reply(rmsg, self.upstream); } @@ -494,6 +504,7 @@ const TcpCtx = struct { return; }; + const src = @src(); if (err.msg) |msg| log.err(src, "%s(%d, '%s') failed: %s", .{ err.op, fdobj.fd, self.upstream.url, msg }) else @@ -577,7 +588,7 @@ pub const Proto = enum { // ====================================================== /// for udp upstream -const Life = struct { +const UdpLife = struct { create_time: c.time_t = 0, query_count: u8 = 0, @@ -585,7 +596,7 @@ const Life = struct { const QUERY_MAX = 10; /// called before the first query - pub fn check_eol(self: *Life, now_time: c.time_t) bool { + pub fn check_eol(self: *UdpLife, now_time: c.time_t) bool { // zig fmt: off const eol = self.query_count >= QUERY_MAX or now_time < self.create_time @@ -598,7 +609,7 @@ const Life = struct { return eol; } - pub fn on_query(self: *Life, add_count: u8) void { + pub fn on_query(self: *UdpLife, add_count: u8) void { self.query_count +|= add_count; } }; @@ -607,8 +618,8 @@ const Life = struct { pub const Group = struct { list: std.ArrayListUnmanaged(Upstream) = .{}, - udpi_life: Life = .{}, - udp_life: Life = .{}, + udpi_life: UdpLife = .{}, + udp_life: UdpLife = .{}, pub inline fn items(self: *const Group) []Upstream { return self.list.items; diff --git a/src/server.zig b/src/server.zig index 899c252..a2da031 100644 --- a/src/server.zig +++ b/src/server.zig @@ -854,7 +854,7 @@ noinline fn do_start(ip: cc.ConstStr, socktype: net.SockType) void { cc.bind(fd, &addr) orelse break :e "bind"; switch (socktype) { .tcp => { - cc.listen(fd, 256) orelse break :e "listen"; + cc.listen(fd, 1024) orelse break :e "listen"; co.create(listen_tcp, .{ fd, ip }); }, .udp => { From 7251033938e997bb1d83669bd0523b410ff2514f Mon Sep 17 00:00:00 2001 From: zfl9 Date: Thu, 18 Apr 2024 14:17:37 +0800 Subject: [PATCH 03/32] gitignore: dep/wolfssl-* --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index cf1c043..c3498a2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ zig-out/ zig-cache/ dep/openssl-* dep/mimalloc-* +dep/wolfssl-* From 52288187f29188de30540c0d1e4954ad8b37c334 Mon Sep 17 00:00:00 2001 From: zfl9 Date: Thu, 18 Apr 2024 18:07:10 +0800 Subject: [PATCH 04/32] vscode --- .vscode/c_cpp_properties.json | 1 + .vscode/settings.json | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index b148e7d..4c15289 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -5,6 +5,7 @@ "intelliSenseMode": "linux-clang-x64", "compilerPath": "/usr/bin/clang", "compilerArgs": [ + "-I${workspaceFolder}/dep/wolfssl-5.7.0", "-I${workspaceFolder}/dep/openssl-3.2.0@x86_64-linux-musl@x86_64+native@fast+lto/include", "-I${workspaceFolder}/dep/mimalloc-2.1.2/include" ], diff --git a/.vscode/settings.json b/.vscode/settings.json index faaae35..f42a4de 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,8 @@ { "files.associations": { "*.h": "c", + "*.i": "c", + "*.in": "c", + "*.S": "c", }, } From 71836e694b981de810ade1d6f355f50bea76d75a Mon Sep 17 00:00:00 2001 From: zfl9 Date: Fri, 19 Apr 2024 21:58:46 +0800 Subject: [PATCH 05/32] DoT support --- .vscode/c_cpp_properties.json | 7 +- .vscode/settings.json | 7 ++ build.zig | 139 +++++++++++---------------- src/Upstream.zig | 170 ++++++++++++++++++++++++++++++---- src/c.zig | 8 ++ src/misc.h | 2 +- src/opt.zig | 4 +- 7 files changed, 229 insertions(+), 108 deletions(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 4c15289..14b5b35 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -5,13 +5,16 @@ "intelliSenseMode": "linux-clang-x64", "compilerPath": "/usr/bin/clang", "compilerArgs": [ + "-I${workspaceFolder}/src/wolfssl", "-I${workspaceFolder}/dep/wolfssl-5.7.0", "-I${workspaceFolder}/dep/openssl-3.2.0@x86_64-linux-musl@x86_64+native@fast+lto/include", "-I${workspaceFolder}/dep/mimalloc-2.1.2/include" ], "defines": [ - // "MUSL", - // "TEST", + "MUSL", + "TEST", + "BUILDING_WOLFSSL", + "WOLFSSL_USER_SETTINGS", ], "cStandard": "c11", "cppStandard": "c++17" diff --git a/.vscode/settings.json b/.vscode/settings.json index f42a4de..7c603eb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,15 @@ { "files.associations": { + "*.config": "lua", "*.h": "c", "*.i": "c", "*.in": "c", "*.S": "c", + "*.inc": "c", + "chrono": "c", + "typeinfo": "c", + "complex": "c", + "format": "c", + "random": "c" }, } diff --git a/build.zig b/build.zig index 5d59294..0128114 100644 --- a/build.zig +++ b/build.zig @@ -18,7 +18,7 @@ var _mode: BuildMode = undefined; var _lto: bool = undefined; var _strip: bool = undefined; var _exe_name: []const u8 = undefined; -var _enable_openssl: bool = undefined; +var _enable_wolfssl: bool = undefined; var _enable_mimalloc: bool = undefined; // conditional compilation for zig source files @@ -35,14 +35,14 @@ const DependLib = struct { lib_dir: []const u8, }; -var _dep_openssl: DependLib = b: { - const version = "3.2.0"; - const src_dir = "dep/openssl-" ++ version; +var _dep_wolfssl: DependLib = b: { + const version = "5.7.0"; + const src_dir = "dep/wolfssl-" ++ version; break :b .{ - .url = "https://www.openssl.org/source/openssl-" ++ version ++ ".tar.gz", + .url = "https://github.com/wolfSSL/wolfssl/archive/refs/tags/v" ++ version ++ "-stable.tar.gz", .version = version, .tarball = src_dir ++ ".tar.gz", - .src_dir = src_dir, + .src_dir = src_dir ++ "-stable", .src_dir_always_clean = false, .base_dir = undefined, // set by init() .include_dir = undefined, // set by init() @@ -84,20 +84,20 @@ fn init(b: *Builder) void { option_lto(); option_strip(); option_name(); - option_openssl(); + option_wolfssl(); option_mimalloc(); - _dep_openssl.base_dir = with_target_desc(_dep_openssl.src_dir, .ReleaseFast); // dependency lib always ReleaseFast - _dep_openssl.include_dir = fmt("{s}/include", .{_dep_openssl.base_dir}); - _dep_openssl.lib_dir = fmt("{s}/lib", .{_dep_openssl.base_dir}); + _dep_wolfssl.base_dir = with_target_desc(_dep_wolfssl.src_dir, .ReleaseFast); // dependency lib always ReleaseFast + _dep_wolfssl.include_dir = fmt("{s}/include", .{_dep_wolfssl.base_dir}); + _dep_wolfssl.lib_dir = fmt("{s}/lib", .{_dep_wolfssl.base_dir}); // conditional compilation for zig source files _build_opts = _b.addOptions(); _build_opts.addOption(bool, "is_test", _test); - _build_opts.addOption(bool, "enable_openssl", _enable_openssl); + _build_opts.addOption(bool, "enable_wolfssl", _enable_wolfssl); _build_opts.addOption(bool, "enable_mimalloc", _enable_mimalloc); _build_opts.addOption([]const u8, "version", chinadns_version); - _build_opts.addOption([]const u8, "openssl_version", _dep_openssl.version); + _build_opts.addOption([]const u8, "wolfssl_version", _dep_wolfssl.version); _build_opts.addOption([]const u8, "mimalloc_version", _dep_mimalloc.version); _build_opts.addOption([]const u8, "target", desc_target()); _build_opts.addOption([]const u8, "cpu", desc_cpu()); @@ -164,8 +164,8 @@ fn option_name() void { } } -fn option_openssl() void { - _enable_openssl = _b.option(bool, "openssl", "enable openssl to support DoH protocol, default: false") orelse false; +fn option_wolfssl() void { + _enable_wolfssl = _b.option(bool, "wolfssl", "enable wolfssl to support DoT protocol, default: false") orelse false; } fn option_mimalloc() void { @@ -428,7 +428,7 @@ fn with_target_desc(name: []const u8, in_mode: ?BuildMode) []const u8 { return fmt("{s}@{s}@{s}@{s}", .{ name, target, cpu, mode }); } -/// for zig cc (build openssl) +/// for zig cc (build wolfssl) fn get_target_mcpu() []const u8 { const target = get_optval_target() orelse "native"; return if (get_optval_cpu()) |cpu| @@ -496,57 +496,22 @@ fn gen_modules_zig() void { // ========================================================================= -fn get_openssl_target() []const u8 { - return switch (_target.getCpuArch()) { - .arm => "linux-armv4", - .aarch64 => "linux-aarch64", - .i386 => "linux-x86-clang", - .x86_64 => "linux-x86_64-clang", - .mips, .mipsel => b: { - // [zig] https://github.com/ziglang/zig/issues/11829 - // [zig] https://github.com/ziglang/zig/commit/e1cc70ba11735b678430ffde348527a16287a744 - // [zig] I ported it to version 0.10.1 via a hack: https://www.zfl9.com/zig-mips.html - // [openssl] Configure script adds minimally required -march for assembly support, if no -march/-mips* was specified at command line. - const target = "linux-mips32"; - const march = _target.getCpuModel().llvm_name.?; - break :b fmt("{s} -{s}", .{ target, march }); - }, - // .mips64, .mips64el => b: { - // // [zig] https://github.com/ziglang/zig/issues/8020 - // // [openssl] Configure script adds minimally required -march for assembly support, if no -march/-mips* was specified at command line. - // const target = "linux64-mips64"; - // const march = _target.getCpuModel().llvm_name.?; - // break :b fmt("{s} -{s}", .{ target, march }); - // }, - else => b: { - err_invalid( - "'{s}' is not supported, currently supports ['arm', 'aarch64', 'i386', 'x86_64', 'mips', 'mipsel']", - .{@tagName(_target.getCpuArch())}, - ); - break :b ""; - }, - }; -} - -/// openssl dependency lib -fn build_openssl() *Step { - const openssl = add_step("openssl"); +/// wolfssl dependency lib +fn build_wolfssl() *Step { + const wolfssl = add_step("wolfssl"); // already installed ? - if (path_exists(_dep_openssl.base_dir)) - return openssl; - - init_dep(openssl, _dep_openssl); + if (path_exists(_dep_wolfssl.base_dir)) + return wolfssl; - const openssl_target = get_openssl_target(); - openssl.dependOn(add_log("[openssl] ./Configure {s}", .{openssl_target})); + init_dep(wolfssl, _dep_wolfssl); const cmd_ = \\ install_dir='{s}' \\ src_dir='{s}' \\ zig_exe='{s}' \\ target_mcpu='{s}' - \\ openssl_target='{s}' + \\ target_triple='{s}' \\ zig_cache_dir='{s}' \\ is_musl='{s}' \\ lto='{s}' @@ -562,31 +527,32 @@ fn build_openssl() *Step { \\ export AR="$zig_exe ar" \\ export RANLIB="$zig_exe ranlib" \\ - \\ ./Configure $openssl_target --prefix="$install_dir" --libdir=lib --openssldir=/etc/ssl \ - \\ enable-ktls no-deprecated no-async no-comp no-dgram no-legacy no-pic no-psk \ - \\ no-dso no-dynamic-engine no-shared no-srp no-srtp no-ssl-trace no-tests no-apps no-threads + \\ [ "$target_triple" ] && host="--host=$target_triple" || host="" \\ - \\ make -j$(nproc) build_sw - \\ make install_sw + \\ ./autogen.sh + \\ ./configure $host --prefix="$install_dir" --enable-opensslall --enable-static --disable-shared \ + \\ --enable-staticmemory --disable-crypttests --disable-benchmark --disable-examples \ + \\ --enable-singlethreaded + \\ make install ; const str_musl: [:0]const u8 = if (is_musl()) "1" else "0"; const str_lto: [:0]const u8 = if (_lto) "-flto" else ""; const cmd = fmt(cmd_, .{ - _b.pathFromRoot(_dep_openssl.base_dir), - _dep_openssl.src_dir, + _b.pathFromRoot(_dep_wolfssl.base_dir), + _dep_wolfssl.src_dir, _b.zig_exe, get_target_mcpu(), - openssl_target, + get_optval_target() orelse "", _b.pathFromRoot(_b.cache_root), str_musl, str_lto, }); - openssl.dependOn(add_sh_cmd_x(cmd)); + wolfssl.dependOn(add_sh_cmd_x(cmd)); - return openssl; + return wolfssl; } fn setup_libexeobj_step(step: *LibExeObjStep) void { @@ -717,9 +683,9 @@ fn link_obj_chinadns(exe: *LibExeObjStep) void { if (is_musl()) obj.defineCMacroRaw("MUSL"); - // openssl lib - if (_enable_openssl) - obj.addIncludePath(_dep_openssl.include_dir); + // wolfssl lib + if (_enable_wolfssl) + obj.addIncludePath(_dep_wolfssl.include_dir); // for log.h obj.defineCMacroRaw(fmt("LOG_FILENAME=\"{s}\"", .{file.name})); @@ -738,8 +704,8 @@ fn configure() void { setup_libexeobj_step(exe); // build the dependency library first - if (_enable_openssl) - exe.step.dependOn(build_openssl()); + if (_enable_wolfssl) + exe.step.dependOn(build_wolfssl()); // to ensure that the standard malloc interface resolves to the mimalloc library, link it as the first object file if (_enable_mimalloc) @@ -747,11 +713,10 @@ fn configure() void { link_obj_chinadns(exe); - // link openssl library - if (_enable_openssl) { - exe.addLibraryPath(_dep_openssl.lib_dir); - exe.linkSystemLibrary("ssl"); - exe.linkSystemLibrary("crypto"); + // link wolfssl library + if (_enable_wolfssl) { + exe.addLibraryPath(_dep_wolfssl.lib_dir); + exe.linkSystemLibrary("wolfssl"); } // install to dest dir @@ -767,30 +732,30 @@ fn configure() void { run.dependOn(&run_exe.step); const rm_cache = add_rm(_b.cache_root); - const rm_openssl = add_rm(_dep_openssl.base_dir); // current target - const rm_openssl_all = add_sh_cmd(fmt("rm -fr {s}@*", .{_dep_openssl.src_dir})); // all targets + const rm_wolfssl = add_rm(_dep_wolfssl.base_dir); // current target + const rm_wolfssl_all = add_sh_cmd(fmt("rm -fr {s}@*", .{_dep_wolfssl.src_dir})); // all targets // zig build clean-cache const clean_cache = _b.step("clean-cache", fmt("clean zig build cache: '{s}'", .{_b.cache_root})); clean_cache.dependOn(rm_cache); - // zig build clean-openssl - const clean_openssl = _b.step("clean-openssl", fmt("clean openssl build cache: '{s}'", .{_dep_openssl.base_dir})); - clean_openssl.dependOn(rm_openssl); + // zig build clean-wolfssl + const clean_wolfssl = _b.step("clean-wolfssl", fmt("clean wolfssl build cache: '{s}'", .{_dep_wolfssl.base_dir})); + clean_wolfssl.dependOn(rm_wolfssl); - // zig build clean-openssl-all - const clean_openssl_all = _b.step("clean-openssl-all", fmt("clean openssl build caches: '{s}@*'", .{_dep_openssl.src_dir})); - clean_openssl_all.dependOn(rm_openssl_all); + // zig build clean-wolfssl-all + const clean_wolfssl_all = _b.step("clean-wolfssl-all", fmt("clean wolfssl build caches: '{s}@*'", .{_dep_wolfssl.src_dir})); + clean_wolfssl_all.dependOn(rm_wolfssl_all); // zig build clean const clean = _b.step("clean", fmt("clean all build caches", .{})); clean.dependOn(clean_cache); - clean.dependOn(clean_openssl); + clean.dependOn(clean_wolfssl); // zig build clean-all const clean_all = _b.step("clean-all", fmt("clean all build caches (*)", .{})); clean_all.dependOn(clean_cache); - clean_all.dependOn(clean_openssl_all); + clean_all.dependOn(clean_wolfssl_all); } /// build.zig just generates the build steps (and the dependency graph), the real running is done by build_runner.zig diff --git a/src/Upstream.zig b/src/Upstream.zig index 5ee8b36..0cc1bfc 100644 --- a/src/Upstream.zig +++ b/src/Upstream.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const build_opts = @import("build_opts"); const g = @import("g.zig"); const c = @import("c.zig"); const cc = @import("cc.zig"); @@ -214,6 +215,11 @@ fn udp_on_eol(self: *Upstream) void { // ====================================================== +const has_ssl = build_opts.enable_wolfssl; + +const SSL = if (has_ssl) ?*c.SSL else void; +const SSL_null: SSL = if (has_ssl) null else {}; + const TcpCtx = struct { upstream: *const Upstream, fdobj: ?*EvLoop.Fd = null, @@ -221,6 +227,7 @@ const TcpCtx = struct { ack_list: std.AutoHashMapUnmanaged(u16, *RcMsg) = .{}, // qmsg to be ack pending_n: u16 = 0, // outstanding queries: send_list + ack_list healthy: bool = false, // current connection processed at least one query ? + ssl: SSL = SSL_null, /// must <= u16_max const PENDING_MAX = 1000; @@ -366,8 +373,13 @@ const TcpCtx = struct { } } - /// network/socket error - fn on_failed(self: *TcpCtx) void { + /// connection closed + fn on_close(self: *TcpCtx) void { + if (has_ssl) if (self.ssl) |ssl| { + c.SSL_free(ssl); + self.ssl = null; + }; + self.fdobj = null; self.send_list.cancel_wait(); @@ -412,20 +424,21 @@ const TcpCtx = struct { defer co.terminate(@frame(), @frameSize(TcpCtx.send)); // nosuspend - const fd = net.new_tcp_conn_sock(self.upstream.addr.family()) orelse return self.on_failed(); + const fd = net.new_tcp_conn_sock(self.upstream.addr.family()) orelse return self.on_close(); const fdobj = EvLoop.Fd.new(fd); self.fdobj = fdobj; defer { if (self.fdobj_ok(fdobj)) - self.on_failed(); + self.on_close(); fdobj.free(); } // async const err: cc.ConstStr = e: { - g.evloop.connect(fdobj, &self.upstream.addr) orelse break :e "connect"; + self.do_connect() orelse break :e "connect"; + // g.evloop.connect(fdobj, &self.upstream.addr) orelse break :e "connect"; co.create(recv, .{self}); @@ -444,7 +457,8 @@ const TcpCtx = struct { .msg_iov = &iov, .msg_iovlen = iov.len, }; - g.evloop.sendmsg(fdobj, &msg, 0) orelse break :e "send_query"; + // g.evloop.sendmsg(fdobj, &msg, 0) orelse break :e "send_query"; + self.do_sendmsg(&msg, 0) orelse break :e "send_query"; } return; @@ -461,7 +475,7 @@ const TcpCtx = struct { defer { if (self.fdobj_ok(fdobj)) - self.on_failed(); + self.on_close(); fdobj.unref(); } @@ -473,7 +487,8 @@ const TcpCtx = struct { while (self.fdobj_ok(fdobj)) { // read the len var rlen: u16 = undefined; - g.evloop.recv_exactly(fdobj, std.mem.asBytes(&rlen), 0) orelse + // g.evloop.recv_exactly(fdobj, std.mem.asBytes(&rlen), 0) orelse + self.recv_exactly(std.mem.asBytes(&rlen), 0) orelse if (cc.errno() == 0) return else break :e .{ .op = "read_len" }; rlen = cc.ntohs(rlen); @@ -492,7 +507,8 @@ const TcpCtx = struct { // read the msg rmsg.len = rlen; - g.evloop.recv_exactly(fdobj, rmsg.msg(), 0) orelse + // g.evloop.recv_exactly(fdobj, rmsg.msg(), 0) orelse + self.recv_exactly(rmsg.msg(), 0) orelse break :e .{ .op = "read_msg", .msg = if (cc.errno() == 0) "connection closed" else null }; if (self.fdobj_ok(fdobj)) @@ -510,10 +526,131 @@ const TcpCtx = struct { else log.err(src, "%s(%d, '%s') failed: (%d) %m", .{ err.op, fdobj.fd, self.upstream.url, cc.errno() }); } + + fn do_connect(self: *TcpCtx) ?void { + g.evloop.connect(self.fdobj.?, &self.upstream.addr) orelse return null; + + if (has_ssl and self.upstream.proto == .tls) { + const static = struct { + var ssl_ctx: ?*c.SSL_CTX = null; + + fn get_ssl_ctx() *c.SSL_CTX { + if (ssl_ctx == null) { + ssl_ctx = c.wolfSSL_CTX_new(c.TLS_client_method()); + assert(ssl_ctx != null); + load_ca_certs(); + } + return ssl_ctx.?; + } + + fn load_ca_certs() void { + const file_list = [_][*:0]const u8{ + "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc. + "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6 + "/etc/ssl/ca-bundle.pem", // OpenSUSE + "/etc/pki/tls/cacert.pem", // OpenELEC + "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7 + "/etc/ssl/cert.pem", // Alpine Linux + }; + const dir_list = [_][*:0]const u8{ + "/etc/ssl/certs", // SLES10/SLES11, https://golang.org/issue/12139 + "/etc/pki/tls/certs", // Fedora/RHEL + }; + for (file_list) |path| { + if (c.SSL_CTX_load_verify_locations(ssl_ctx, path, null) == 1) { + if (g.verbose()) + log.info(@src(), "CA certs: %s", .{path}); + return; + } + } + for (dir_list) |path| { + if (c.SSL_CTX_load_verify_locations(ssl_ctx, null, path) == 1) { + if (g.verbose()) + log.info(@src(), "CA certs: %s", .{path}); + return; + } + } + log.err(@src(), "can't load CA certs, TODO: add option", .{}); + cc.exit(1); + } + }; + + self.ssl = c.SSL_new(static.get_ssl_ctx()); + assert(c.SSL_set_fd(self.ssl, self.fdobj.?.fd) == 1); + assert(c.SSL_set_tlsext_host_name(self.ssl, self.upstream.host) == 1); // SNI (client hello) + assert(c.SSL_set1_host(self.ssl, self.upstream.host) == 1); // host check (server hello) + + while (true) { + const res = c.SSL_connect(self.ssl); + if (res == 1) break; + self.on_ssl_error(res) orelse return null; + } + } + } + + fn do_sendmsg(self: *TcpCtx, msg: *const cc.msghdr_t, flags: c_int) ?void { + if (!has_ssl or self.upstream.proto != .tls) + return g.evloop.sendmsg(self.fdobj.?, msg, flags); + + for (msg.iov_items()) |iov| { + while (true) { + const res = c.SSL_write(self.ssl, iov.iov_base, cc.to_int(iov.iov_len)); + if (res > 0) break; + self.on_ssl_error(res) orelse return null; + } + } + } + + fn recv_exactly(self: *TcpCtx, buf: []u8, flags: c_int) ?void { + if (!has_ssl or self.upstream.proto != .tls) + return g.evloop.recv_exactly(self.fdobj.?, buf, flags); + + var nread: usize = 0; + while (nread < buf.len) { + const res = c.SSL_read(self.ssl, buf.ptr + nread, cc.to_int(buf.len - nread)); + if (res > 0) { + nread += cc.to_usize(res); + } else { + self.on_ssl_error(res) orelse return null; + } + } + } + + /// null && errno=0 => EOF + fn on_ssl_error(self: *TcpCtx, res: c_int) ?void { + const err = c.SSL_get_error(self.ssl, res); + switch (err) { + c.SSL_ERROR_ZERO_RETURN => { + cc.set_errno(0); + return null; + }, + c.SSL_ERROR_WANT_READ => { + g.evloop.wait_readable(self.fdobj.?); + }, + c.SSL_ERROR_WANT_WRITE => { + g.evloop.wait_writable(self.fdobj.?); + }, + else => { + log.err(@src(), "ssl error: %s", .{get_ssl_errstr(@bitCast(c_ulong, @as(c_long, err)))}); + return null; + }, + } + } + + /// static buffer + fn get_ssl_errstr(err: c_ulong) cc.ConstStr { + const static = struct { + var buf: [50]u8 = undefined; + }; + return cc.ptrcast( + cc.ConstStr, + c.ERR_error_string(err, &static.buf), + ); + } }; fn tcp_ctx(self: *Upstream) *TcpCtx { - assert(self.proto == .tcpi or self.proto == .tcp); + assert(self.proto == .tcpi or self.proto == .tcp or self.proto == .tls); if (self.ctx == null) self.ctx = TcpCtx.new(self); return cc.ptrcast(*TcpCtx, self.ctx.?); @@ -526,10 +663,7 @@ fn tcp_send(self: *Upstream, qmsg: *RcMsg) void { // ====================================================== fn tls_send(self: *Upstream, qmsg: *RcMsg) void { - _ = qmsg; - - // TODO - log.warn(@src(), "currently tls upstream is not supported: %s", .{self.url}); + self.tcp_ctx().push_qmsg(qmsg); } // ====================================================== @@ -545,10 +679,13 @@ pub const Proto = enum { /// "tcp://" pub fn from_str(str: []const u8) ?Proto { - const map = .{ + const map = if (has_ssl) .{ .{ .str = "tcp://", .proto = .tcp }, .{ .str = "udp://", .proto = .udp }, .{ .str = "tls://", .proto = .tls }, + } else .{ + .{ .str = "tcp://", .proto = .tcp }, + .{ .str = "udp://", .proto = .udp }, }; inline for (map) |v| { if (std.mem.eql(u8, str, v.str)) @@ -668,7 +805,8 @@ pub const Group = struct { if (!proto.require_host()) return parse_failed("no host required", host); break :b host; - } + } else if (proto.require_host()) + return parse_failed("host required", in_value); break :b ""; }; diff --git a/src/c.zig b/src/c.zig index 98d51c3..376fbe5 100644 --- a/src/c.zig +++ b/src/c.zig @@ -2,12 +2,20 @@ // because it saves the compiler from invoking clang multiple times, // and prevents inline functions from being duplicated. +const build_opts = @import("build_opts"); + // import into the current namespace (c.zig) // mainly used to access C constants, C typedefs // please give priority to using functions in the `cc` namespace pub usingnamespace @cImport({ @cDefine("_GNU_SOURCE", {}); + if (build_opts.enable_wolfssl) { + @cInclude("wolfssl/options.h"); + @cInclude("wolfssl/openssl/ssl.h"); + @cInclude("wolfssl/openssl/err.h"); + } + @cInclude("stdio.h"); @cInclude("stdlib.h"); @cInclude("stdint.h"); diff --git a/src/misc.h b/src/misc.h index 37ba66d..74a3d59 100644 --- a/src/misc.h +++ b/src/misc.h @@ -48,7 +48,7 @@ typedef int64_t s64; #define S32C INT32_C #define S64C INT64_C -typedef signed char byte; /* >= 8 bits */ +// typedef signed char byte; /* >= 8 bits */ typedef unsigned char ubyte; /* >= 8 bits */ typedef unsigned short ushort; /* >= 16 bits */ typedef unsigned int uint; /* >= 16 bits */ diff --git a/src/opt.zig b/src/opt.zig index 8e3ad67..cd90b43 100644 --- a/src/opt.zig +++ b/src/opt.zig @@ -73,8 +73,8 @@ const version: cc.ConstStr = b: { var prefix: [:0]const u8 = "ChinaDNS-NG " ++ build_opts.version; - if (build_opts.enable_openssl) - prefix = prefix ++ " | openssl-" ++ build_opts.openssl_version; + if (build_opts.enable_wolfssl) + prefix = prefix ++ " | wolfssl-" ++ build_opts.wolfssl_version; if (build_opts.enable_mimalloc) prefix = prefix ++ " | mimalloc-" ++ build_opts.mimalloc_version; From 45c9ebdd5e4bf8f27b343afe67b330c955b064ae Mon Sep 17 00:00:00 2001 From: zfl9 Date: Fri, 19 Apr 2024 23:39:54 +0800 Subject: [PATCH 06/32] bugfix: nodata cache --- src/dns.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dns.c b/src/dns.c index d87a278..2eb7f97 100644 --- a/src/dns.c +++ b/src/dns.c @@ -454,14 +454,14 @@ u16 dns_minimise(void *noalias msg, ssize_t len, int qnamelen) { void *start = msg; - int count = get_answer_count(msg); + int answer_count = get_answer_count(msg); + int authority_count = get_authority_count(msg); move_to_records(msg, len, qnamelen); - unlikely_if (!skip_record(&msg, &len, count)) + unlikely_if (!skip_record(&msg, &len, answer_count + authority_count)) return 0; struct dns_header *h = start; - h->authority_count = 0; h->additional_count = 0; return msg - start; From 654cc6b8af120f23d3884a321ccbe86a165d049a Mon Sep 17 00:00:00 2001 From: zfl9 Date: Sat, 20 Apr 2024 10:14:27 +0800 Subject: [PATCH 07/32] keep response msg consistent, whether caching is enabled or not --- src/cache.zig | 7 +------ src/dns.c | 55 +++++++++++++++++++++++++++++--------------------- src/dns.h | 5 +---- src/dns.zig | 12 +++-------- src/server.zig | 12 +++++++---- 5 files changed, 45 insertions(+), 46 deletions(-) diff --git a/src/cache.zig b/src/cache.zig index 71489a7..a307cdf 100644 --- a/src/cache.zig +++ b/src/cache.zig @@ -62,9 +62,7 @@ pub fn unref(msg: []const u8) void { } /// `in_msg` will be modified and copied -pub fn add(in_msg: []u8, qnamelen: c_int, p_ttl: *i32, p_sz: *usize) bool { - var msg = in_msg; - +pub fn add(msg: []const u8, qnamelen: c_int, p_ttl: *i32) bool { if (!enabled()) return false; @@ -77,9 +75,6 @@ pub fn add(in_msg: []u8, qnamelen: c_int, p_ttl: *i32, p_sz: *usize) bool { const ttl = dns.get_ttl(msg, qnamelen, g.cache_nodata_ttl) orelse return false; p_ttl.* = ttl; - msg = dns.minimise(msg, qnamelen) orelse return false; - p_sz.* = msg.len; - const res = _map.getOrPut(g.allocator, dns.question(msg, qnamelen)) catch unreachable; if (res.found_existing) { // check ttl, avoid duplicate add diff --git a/src/dns.c b/src/dns.c index 2eb7f97..c1f389d 100644 --- a/src/dns.c +++ b/src/dns.c @@ -140,8 +140,7 @@ static bool check_msg(bool is_query, unlikely_if (!decode_name(ascii_name, msg, qnamelen)) return false; } - if (p_qnamelen) - *p_qnamelen = qnamelen; + *p_qnamelen = qnamelen; /* move to `struct dns_question` */ msg += qnamelen; @@ -360,12 +359,41 @@ u16 dns_empty_reply(void *noalias msg, int qnamelen) { return msg_minlen(qnamelen); } +// return newlen (0 if failed) +static u16 rm_additional(void *noalias msg, ssize_t len, int qnamelen) { + if (!is_normal_msg(msg)) + return len; + + void *start = msg; + + int answer_count = get_answer_count(msg); + int authority_count = get_authority_count(msg); + move_to_records(msg, len, qnamelen); + + unlikely_if (!skip_record(&msg, &len, answer_count + authority_count)) + return 0; + + struct dns_header *h = start; + h->additional_count = 0; + + return msg - start; +} + bool dns_check_query(const void *noalias msg, ssize_t len, char *noalias ascii_name, int *noalias p_qnamelen) { return check_msg(true, msg, len, ascii_name, p_qnamelen); } -bool dns_check_reply(const void *noalias msg, ssize_t len, char *noalias ascii_name, int *noalias p_qnamelen) { - return check_msg(false, msg, len, ascii_name, p_qnamelen); +bool dns_check_reply(void *noalias msg, ssize_t len, char *noalias ascii_name, int *noalias p_qnamelen, u16 *noalias p_newlen) { + unlikely_if (!check_msg(false, msg, len, ascii_name, p_qnamelen)) + return false; + + unlikely_if ((*p_newlen = rm_additional(msg, len, *p_qnamelen)) == 0) + return false; + + struct dns_header *h = msg; + h->ra = 1; + + return true; } static bool check_ip_datalen(u16 rtype, const struct dns_record *noalias record) { @@ -448,25 +476,6 @@ void dns_add_ip(const void *noalias msg, ssize_t len, int qnamelen, struct ipset ipset_end_add_ip(ctx); } -u16 dns_minimise(void *noalias msg, ssize_t len, int qnamelen) { - if (!is_normal_msg(msg)) - return 0; - - void *start = msg; - - int answer_count = get_answer_count(msg); - int authority_count = get_authority_count(msg); - move_to_records(msg, len, qnamelen); - - unlikely_if (!skip_record(&msg, &len, answer_count + authority_count)) - return 0; - - struct dns_header *h = start; - h->additional_count = 0; - - return msg - start; -} - static bool get_ttl(struct dns_record *noalias record, int rnamelen, void *ud, bool *noalias is_break) { (void)rnamelen; (void)is_break; diff --git a/src/dns.h b/src/dns.h index 34deacc..7aa020f 100644 --- a/src/dns.h +++ b/src/dns.h @@ -74,7 +74,7 @@ u16 dns_empty_reply(void *noalias msg, int qnamelen); bool dns_check_query(const void *noalias msg, ssize_t len, char *noalias ascii_name, int *noalias p_qnamelen); /* check reply msg, `ascii_name` used to get domain name */ -bool dns_check_reply(const void *noalias msg, ssize_t len, char *noalias ascii_name, int *noalias p_qnamelen); +bool dns_check_reply(void *noalias msg, ssize_t len, char *noalias ascii_name, int *noalias p_qnamelen, u16 *noalias p_newlen); /* result of dns_test_ip() */ #define DNS_TEST_IP_IS_CHINA_IP 0 @@ -88,9 +88,6 @@ int dns_test_ip(const void *noalias msg, ssize_t len, int qnamelen, const struct /* add the answer ip to ipset/nftset (tag:chn, tag:gfw) */ void dns_add_ip(const void *noalias msg, ssize_t len, int qnamelen, struct ipset_addctx *noalias ctx); -/* return the updated length of the msg (0 means error) */ -u16 dns_minimise(void *noalias msg, ssize_t len, int qnamelen); - /* return -1 if failed */ s32 dns_get_ttl(const void *noalias msg, ssize_t len, int qnamelen, s32 nodata_ttl); diff --git a/src/dns.zig b/src/dns.zig index 60e585e..d5f0115 100644 --- a/src/dns.zig +++ b/src/dns.zig @@ -65,15 +65,15 @@ pub inline fn empty_reply(msg: []u8, qnamelen: c_int) []u8 { /// check if the query msg is valid /// `ascii_name`: the buffer used to get the domain-name (ASCII-format) /// `p_qnamelen`: used to get the length of the domain-name (wire-format) -pub inline fn check_query(msg: []const u8, ascii_name: ?[*]u8, p_qnamelen: ?*c_int) bool { +pub inline fn check_query(msg: []const u8, ascii_name: ?[*]u8, p_qnamelen: *c_int) bool { return c.dns_check_query(msg.ptr, cc.to_isize(msg.len), ascii_name, p_qnamelen); } /// check if the reply msg is valid /// `ascii_name`: the buffer used to get the domain-name (ASCII-format) /// `p_qnamelen`: used to get the length of the domain-name (wire-format) -pub inline fn check_reply(msg: []const u8, ascii_name: ?[*]u8, p_qnamelen: ?*c_int) bool { - return c.dns_check_reply(msg.ptr, cc.to_isize(msg.len), ascii_name, p_qnamelen); +pub inline fn check_reply(msg: []u8, ascii_name: ?[*]u8, p_qnamelen: *c_int, p_newlen: *u16) bool { + return c.dns_check_reply(msg.ptr, cc.to_isize(msg.len), ascii_name, p_qnamelen, p_newlen); } pub const TestIpResult = enum(c_int) { @@ -97,12 +97,6 @@ pub inline fn add_ip(msg: []const u8, qnamelen: c_int, addctx: *ipset.addctx_t) return c.dns_add_ip(msg.ptr, cc.to_isize(msg.len), qnamelen, addctx); } -/// [dns cache] -pub inline fn minimise(msg: []u8, qnamelen: c_int) ?[]u8 { - const len = c.dns_minimise(msg.ptr, cc.to_isize(msg.len), qnamelen); - return if (len > 0) msg[0..len] else null; -} - /// return `null` if there is no effective TTL pub inline fn get_ttl(msg: []const u8, qnamelen: c_int, nodata_ttl: i32) ?i32 { const ttl = c.dns_get_ttl(msg.ptr, cc.to_isize(msg.len), qnamelen, nodata_ttl); diff --git a/src/server.zig b/src/server.zig index a2da031..e8e7141 100644 --- a/src/server.zig +++ b/src/server.zig @@ -625,11 +625,16 @@ pub fn on_reply(rmsg: *RcMsg, upstream: *const Upstream) void { const p_ascii_namebuf: ?[*]u8 = if (g.verbose()) &ascii_namebuf else null; var qnamelen: c_int = undefined; - if (!dns.check_reply(msg, p_ascii_namebuf, &qnamelen)) { + var newlen: u16 = undefined; + + if (!dns.check_reply(msg, p_ascii_namebuf, &qnamelen, &newlen)) { log.err(@src(), "dns.check_reply(upstream:%s) failed: invalid reply msg", .{upstream.url}); return; } + rmsg.len = newlen; + msg = rmsg.msg(); + const qtype = dns.get_qtype(msg, qnamelen); const is_qtype_A_AAAA = qtype == c.DNS_TYPE_A or qtype == c.DNS_TYPE_AAAA; @@ -733,9 +738,8 @@ pub fn on_reply(rmsg: *RcMsg, upstream: *const Upstream) void { // add to cache (may modify the msg) // must come after the `send_reply()` var ttl: i32 = undefined; - var sz: usize = undefined; - if (cache.add(msg, qnamelen, &ttl, &sz)) - if (g.verbose()) rlog.cache(ttl, sz); + if (cache.add(msg, qnamelen, &ttl)) + if (g.verbose()) rlog.cache(ttl, msg.len); // must be at the end qctx.free(); From 1606eb2f81396a1bc99a9b4396393b9c10bf955f Mon Sep 17 00:00:00 2001 From: zfl9 Date: Sat, 20 Apr 2024 10:21:52 +0800 Subject: [PATCH 08/32] bump version --- build.zig | 2 +- src/cache.zig | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/build.zig b/build.zig index 0128114..2f99419 100644 --- a/build.zig +++ b/build.zig @@ -7,7 +7,7 @@ const Step = std.build.Step; const LibExeObjStep = std.build.LibExeObjStep; const OptionsStep = std.build.OptionsStep; -const chinadns_version = "2024.04.13"; +const chinadns_version = "2024.04.20"; var _b: *Builder = undefined; diff --git a/src/cache.zig b/src/cache.zig index a307cdf..a2b73c5 100644 --- a/src/cache.zig +++ b/src/cache.zig @@ -61,7 +61,6 @@ pub fn unref(msg: []const u8) void { return CacheMsg.from_msg(msg).unref(); } -/// `in_msg` will be modified and copied pub fn add(msg: []const u8, qnamelen: c_int, p_ttl: *i32) bool { if (!enabled()) return false; From 7741e0ef0a8e366aaa813f1446e356682042373c Mon Sep 17 00:00:00 2001 From: zfl9 Date: Sat, 20 Apr 2024 17:54:41 +0800 Subject: [PATCH 09/32] update cache.zig --- src/CacheMsg.zig | 15 +++-- src/cache.zig | 172 +++++++++++++++++++++++++++++++++++++---------- src/cc.zig | 8 +++ src/dnl.c | 6 -- src/misc.c | 7 ++ src/misc.h | 2 + 6 files changed, 164 insertions(+), 46 deletions(-) diff --git a/src/CacheMsg.zig b/src/CacheMsg.zig index 56c670d..516916e 100644 --- a/src/CacheMsg.zig +++ b/src/CacheMsg.zig @@ -11,8 +11,10 @@ const Bytes = cc.Bytes; const CacheMsg = @This(); +next: ?*CacheMsg = null, // for hashmap list_node: ListNode = undefined, update_time: c.time_t, +hashv: c_uint, ttl: i32, ttl_r: i32, // refresh if ttl <= ttl_r rc: Rc = .{}, @@ -25,8 +27,9 @@ qnamelen: u8, const metadata_len = @sizeOf(CacheMsg); const alignment = @alignOf(CacheMsg); -fn init(self: *CacheMsg, in_msg: []const u8, qnamelen: c_int, ttl: i32) *CacheMsg { +fn init(self: *CacheMsg, in_msg: []const u8, qnamelen: c_int, ttl: i32, hashv: c_uint) *CacheMsg { self.* = .{ + .hashv = hashv, .update_time = cc.time(), .ttl = ttl, .ttl_r = @divTrunc(ttl * g.cache_refresh, 100), @@ -38,20 +41,20 @@ fn init(self: *CacheMsg, in_msg: []const u8, qnamelen: c_int, ttl: i32) *CacheMs } /// the `in_msg` will be copied -pub fn new(in_msg: []const u8, qnamelen: c_int, ttl: i32) *CacheMsg { +pub fn new(in_msg: []const u8, qnamelen: c_int, ttl: i32, hashv: c_uint) *CacheMsg { const bytes = g.allocator.alignedAlloc(u8, alignment, metadata_len + in_msg.len) catch unreachable; const self = std.mem.bytesAsValue(CacheMsg, bytes[0..metadata_len]); - return self.init(in_msg, qnamelen, ttl); + return self.init(in_msg, qnamelen, ttl, hashv); } /// the `in_msg` will be copied /// if reuse fail, `self` will be freed -pub fn reuse_or_new(self: *CacheMsg, in_msg: []const u8, qnamelen: c_int, ttl: i32) *CacheMsg { +pub fn reuse_or_new(self: *CacheMsg, in_msg: []const u8, qnamelen: c_int, ttl: i32, hashv: c_uint) *CacheMsg { if (self.rc.ref_count == 1 and g.allocator.resize(self.mem(), metadata_len + in_msg.len) != null) { - return self.init(in_msg, qnamelen, ttl); + return self.init(in_msg, qnamelen, ttl, hashv); } else { self.free(); // free the old cache - return new(in_msg, qnamelen, ttl); + return new(in_msg, qnamelen, ttl, hashv); } } diff --git a/src/cache.zig b/src/cache.zig index a2b73c5..d46cb7a 100644 --- a/src/cache.zig +++ b/src/cache.zig @@ -6,28 +6,146 @@ const dns = @import("dns.zig"); const ListNode = @import("ListNode.zig"); const CacheMsg = @import("CacheMsg.zig"); const cache_ignore = @import("cache_ignore.zig"); +const log = @import("log.zig"); const assert = std.debug.assert; const Bytes = cc.Bytes; -/// question => CacheMsg -var _map: std.StringHashMapUnmanaged(*CacheMsg) = .{}; +/// LRU var _list: ListNode = undefined; pub fn module_init() void { _list.init(); } +const map = opaque { + var _buckets: []?*CacheMsg = &.{}; + var _nitems: usize = 0; + + fn calc_hashv(question: []const u8) c_uint { + return cc.calc_hashv(question); + } + + fn calc_idx(hashv: c_uint) usize { + return hashv & (_buckets.len - 1); + } + + fn get(question: []const u8, hashv: c_uint) ?*CacheMsg { + if (_buckets.len == 0) + return null; + + const idx = calc_idx(hashv); + + var prev: ?*CacheMsg = null; + var current = _buckets[idx]; + while (current) |cur| { + if (cur.hashv == hashv and cc.memeql(cur.question(), question)) { + if (prev) |p| { + // move to head (easy to del it) + p.next = cur.next; + cur.next = _buckets[idx]; + _buckets[idx] = cur; + } + return cur; + } + prev = cur; + current = cur.next; + } + + return null; + } + + fn del(cmsg: *CacheMsg) void { + if (_buckets.len == 0) + return; + + const idx = calc_idx(cmsg.hashv); + + var prev: ?*CacheMsg = null; + var current = _buckets[idx]; + while (current) |cur| { + if (cmsg == cur) { + if (prev) |p| + p.next = cur.next + else + _buckets[idx] = cur.next; + _nitems -= 1; + return; + } + prev = cur; + current = cur.next; + } + } + + /// assume not exists + fn add(cmsg: *CacheMsg) void { + try_resize(); + + const idx = calc_idx(cmsg.hashv); + + cmsg.next = _buckets[idx]; + _buckets[idx] = cmsg; + + _nitems += 1; + } + + const load_factor = 75; + + /// call before add() + fn try_resize() void { + const max_nitems = @divTrunc(_buckets.len * load_factor, 100); + if (_nitems < max_nitems) + return; + + const old_len = _buckets.len; + const new_len = std.math.max(old_len << 1, 1 << 4); + _buckets = g.allocator.realloc(_buckets, new_len) catch unreachable; + + // init + const part2 = std.mem.sliceAsBytes(_buckets.ptr[0..new_len][old_len..]); + @memset(part2.ptr, 0, part2.len); + + var idx: usize = 0; + while (idx < old_len) : (idx += 1) { + var prev: ?*CacheMsg = null; + var current = _buckets[idx]; + while (current) |cur| { + current = cur.next; + const new_idx = calc_idx(cur.hashv); + if (new_idx != idx) { + assert(new_idx >= old_len); + // remove from part 1 + if (prev) |p| + p.next = cur.next + else + _buckets[idx] = cur.next; + // add to part 2 + cur.next = _buckets[new_idx]; + _buckets[new_idx] = cur; + } else { + prev = cur; + } + } + } + } +}; + fn enabled() bool { return g.cache_size > 0; } +fn del_nofree(cache_msg: *CacheMsg) void { + map.del(cache_msg); + cache_msg.list_node.unlink(); +} + /// return the cached reply msg pub fn get(qmsg: []const u8, qnamelen: c_int, p_ttl: *i32, p_ttl_r: *i32) ?[]const u8 { if (!enabled()) return null; - const entry = _map.getEntry(dns.question(qmsg, qnamelen)) orelse return null; - const cache_msg = entry.value_ptr.*; + const question = dns.question(qmsg, qnamelen); + const hashv = map.calc_hashv(question); + const cache_msg = map.get(question, hashv) orelse return null; // update ttl const ttl = cache_msg.update_ttl(); @@ -40,17 +158,12 @@ pub fn get(qmsg: []const u8, qnamelen: c_int, p_ttl: *i32, p_ttl_r: *i32) ?[]con return cache_msg.msg(); } else { // expired - on_expired(entry.key_ptr, cache_msg); + del_nofree(cache_msg); + cache_msg.free(); return null; } } -fn on_expired(key_ptr: *[]const u8, cache_msg: *CacheMsg) void { - _map.removeByPtr(key_ptr); - cache_msg.list_node.unlink(); - cache_msg.free(); -} - /// call before using the cache msg pub fn ref(msg: []const u8) void { return CacheMsg.from_msg(msg).ref(); @@ -74,35 +187,26 @@ pub fn add(msg: []const u8, qnamelen: c_int, p_ttl: *i32) bool { const ttl = dns.get_ttl(msg, qnamelen, g.cache_nodata_ttl) orelse return false; p_ttl.* = ttl; - const res = _map.getOrPut(g.allocator, dns.question(msg, qnamelen)) catch unreachable; - if (res.found_existing) { - // check ttl, avoid duplicate add - const old_ttl = res.value_ptr.*.get_ttl(); - if (std.math.absCast(ttl - old_ttl) <= 2) return false; - } - - // create cache msg const cache_msg = b: { - if (res.found_existing) { - const old = res.value_ptr.*; - old.list_node.unlink(); // unlink from list - break :b old.reuse_or_new(msg, qnamelen, ttl); - } else if (_map.count() <= g.cache_size) { - break :b CacheMsg.new(msg, qnamelen, ttl); + const question = dns.question(msg, qnamelen); + const hashv = map.calc_hashv(question); + const old_msg = map.get(question, hashv); + if (old_msg) |old| { + // avoid duplicate add + const old_ttl = old.get_ttl(); + if (std.math.absCast(ttl - old_ttl) <= 2) return false; + del_nofree(old); + break :b old.reuse_or_new(msg, qnamelen, ttl, hashv); + } else if (map._nitems < g.cache_size) { + break :b CacheMsg.new(msg, qnamelen, ttl, hashv); } else { const old = CacheMsg.from_list_node(_list.tail()); - assert(_map.remove(old.question())); // remove from map - assert(_map.count() == g.cache_size); - old.list_node.unlink(); // unlink from list - break :b old.reuse_or_new(msg, qnamelen, ttl); + del_nofree(old); + break :b old.reuse_or_new(msg, qnamelen, ttl, hashv); } }; - // update key/value - res.key_ptr.* = cache_msg.question(); // ptr to `cache_msg` - res.value_ptr.* = cache_msg; - - // link to list + map.add(cache_msg); _list.link_to_head(&cache_msg.list_node); return true; diff --git a/src/cc.zig b/src/cc.zig index b83f438..e5995b2 100644 --- a/src/cc.zig +++ b/src/cc.zig @@ -124,6 +124,14 @@ pub const to_u64 = IntCast(u64).cast; // ============================================================== +pub inline fn calc_hashv(mem: []const u8) c_uint { + return c.calc_hashv(mem.ptr, mem.len); +} + +pub inline fn memeql(a: []const u8, b: []const u8) bool { + return a.len == b.len and c.memcmp(a.ptr, b.ptr, a.len) == 0; +} + /// convert to C string (static buffer) pub inline fn to_cstr(str: []const u8) Str { return to_cstr_x(&.{str}); diff --git a/src/dnl.c b/src/dnl.c index d6bb155..5eaa995 100644 --- a/src/dnl.c +++ b/src/dnl.c @@ -109,12 +109,6 @@ static u32 alloc(u32 sz, u32 align) { /* ======================== name ======================== */ -static uint calc_hashv(const char *noalias name, u8 namelen) { - uint hashv = 0; - HASH_FUNCTION(name, namelen, hashv); /* uthash.h */ - return hashv; -} - #define get_hashv(nameaddr) \ (ptr_name(nameaddr)->hashv) diff --git a/src/misc.c b/src/misc.c index e37d11a..c371cf2 100644 --- a/src/misc.c +++ b/src/misc.c @@ -1,5 +1,6 @@ #define _GNU_SOURCE #include "misc.h" +#include "uthash.h" #include #include @@ -21,3 +22,9 @@ ssize_t fstat_size(int fd) { return st.st_size; return -1; } + +uint calc_hashv(const void *ptr, size_t len) { + uint hashv = 0; + HASH_FUNCTION(ptr, len, hashv); + return hashv; +} diff --git a/src/misc.h b/src/misc.h index 74a3d59..b1ae551 100644 --- a/src/misc.h +++ b/src/misc.h @@ -127,3 +127,5 @@ const void *SIG_DEFAULT(void); const void *SIG_ERROR(void); ssize_t fstat_size(int fd); + +uint calc_hashv(const void *ptr, size_t len); From 6bb138c1ddd368f99d4bf15bcf76dd1ca7de8f00 Mon Sep 17 00:00:00 2001 From: zfl9 Date: Sat, 20 Apr 2024 20:54:27 +0800 Subject: [PATCH 10/32] update cache.zig --- src/cache.zig | 48 +++++++++++++++--------------------------------- 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/src/cache.zig b/src/cache.zig index d46cb7a..3542f68 100644 --- a/src/cache.zig +++ b/src/cache.zig @@ -21,10 +21,6 @@ const map = opaque { var _buckets: []?*CacheMsg = &.{}; var _nitems: usize = 0; - fn calc_hashv(question: []const u8) c_uint { - return cc.calc_hashv(question); - } - fn calc_idx(hashv: c_uint) usize { return hashv & (_buckets.len - 1); } @@ -35,20 +31,17 @@ const map = opaque { const idx = calc_idx(hashv); - var prev: ?*CacheMsg = null; - var current = _buckets[idx]; - while (current) |cur| { + var p: *?*CacheMsg = &_buckets[idx]; + while (p.*) |cur| : (p = &cur.next) { if (cur.hashv == hashv and cc.memeql(cur.question(), question)) { - if (prev) |p| { - // move to head (easy to del it) - p.next = cur.next; + // move to head (easy to del it) + if (cur != _buckets[idx]) { + p.* = cur.next; cur.next = _buckets[idx]; _buckets[idx] = cur; } return cur; } - prev = cur; - current = cur.next; } return null; @@ -60,19 +53,13 @@ const map = opaque { const idx = calc_idx(cmsg.hashv); - var prev: ?*CacheMsg = null; - var current = _buckets[idx]; - while (current) |cur| { - if (cmsg == cur) { - if (prev) |p| - p.next = cur.next - else - _buckets[idx] = cur.next; + var p: *?*CacheMsg = &_buckets[idx]; + while (p.*) |cur| : (p = &cur.next) { + if (cur == cmsg) { + p.* = cur.next; _nitems -= 1; return; } - prev = cur; - current = cur.next; } } @@ -106,23 +93,18 @@ const map = opaque { var idx: usize = 0; while (idx < old_len) : (idx += 1) { - var prev: ?*CacheMsg = null; - var current = _buckets[idx]; - while (current) |cur| { - current = cur.next; + var p: *?*CacheMsg = &_buckets[idx]; + while (p.*) |cur| { const new_idx = calc_idx(cur.hashv); if (new_idx != idx) { assert(new_idx >= old_len); // remove from part 1 - if (prev) |p| - p.next = cur.next - else - _buckets[idx] = cur.next; + p.* = cur.next; // add to part 2 cur.next = _buckets[new_idx]; _buckets[new_idx] = cur; } else { - prev = cur; + p = &cur.next; } } } @@ -144,7 +126,7 @@ pub fn get(qmsg: []const u8, qnamelen: c_int, p_ttl: *i32, p_ttl_r: *i32) ?[]con return null; const question = dns.question(qmsg, qnamelen); - const hashv = map.calc_hashv(question); + const hashv = cc.calc_hashv(question); const cache_msg = map.get(question, hashv) orelse return null; // update ttl @@ -189,7 +171,7 @@ pub fn add(msg: []const u8, qnamelen: c_int, p_ttl: *i32) bool { const cache_msg = b: { const question = dns.question(msg, qnamelen); - const hashv = map.calc_hashv(question); + const hashv = cc.calc_hashv(question); const old_msg = map.get(question, hashv); if (old_msg) |old| { // avoid duplicate add From ffd02faaecc709f7cc62bc05a9294f3fa8d945a9 Mon Sep 17 00:00:00 2001 From: zfl9 Date: Thu, 25 Apr 2024 17:21:17 +0800 Subject: [PATCH 11/32] DoT --- .vscode/c_cpp_properties.json | 7 +- .vscode/settings.json | 7 - build.zig | 26 +- src/EvLoop.zig | 16 +- src/Upstream.zig | 462 ++++++++++++++++++++-------------- src/c.zig | 11 +- src/cc.zig | 236 ++++++++++++++++- src/dns.zig | 7 +- src/g.zig | 3 + src/misc.c | 28 +++ src/misc.h | 5 + src/opt.zig | 6 + src/server.zig | 12 +- src/wolfssl.c | 6 + src/wolfssl.h | 10 + src/wolfssl_opt.h | 9 + zls.build.json | 2 +- 17 files changed, 614 insertions(+), 239 deletions(-) create mode 100644 src/wolfssl.c create mode 100644 src/wolfssl.h create mode 100644 src/wolfssl_opt.h diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 14b5b35..1e0f037 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -5,16 +5,13 @@ "intelliSenseMode": "linux-clang-x64", "compilerPath": "/usr/bin/clang", "compilerArgs": [ - "-I${workspaceFolder}/src/wolfssl", - "-I${workspaceFolder}/dep/wolfssl-5.7.0", - "-I${workspaceFolder}/dep/openssl-3.2.0@x86_64-linux-musl@x86_64+native@fast+lto/include", + "-I${workspaceFolder}/dep/wolfssl-5.7.0-stable", "-I${workspaceFolder}/dep/mimalloc-2.1.2/include" ], "defines": [ "MUSL", "TEST", - "BUILDING_WOLFSSL", - "WOLFSSL_USER_SETTINGS", + "ENABLE_WOLFSSL" ], "cStandard": "c11", "cppStandard": "c++17" diff --git a/.vscode/settings.json b/.vscode/settings.json index 7c603eb..f42a4de 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,15 +1,8 @@ { "files.associations": { - "*.config": "lua", "*.h": "c", "*.i": "c", "*.in": "c", "*.S": "c", - "*.inc": "c", - "chrono": "c", - "typeinfo": "c", - "complex": "c", - "format": "c", - "random": "c" }, } diff --git a/build.zig b/build.zig index 2f99419..211779b 100644 --- a/build.zig +++ b/build.zig @@ -141,6 +141,7 @@ fn option_lto() void { else => false, }; _lto = _b.option(bool, "lto", "enable LTO, default to true if in fast/small/safe mode") orelse default; + _lto = false; } fn option_strip() void { @@ -515,6 +516,7 @@ fn build_wolfssl() *Step { \\ zig_cache_dir='{s}' \\ is_musl='{s}' \\ lto='{s}' + \\ cwd="$PWD" \\ \\ cd "$src_dir" \\ @@ -530,9 +532,23 @@ fn build_wolfssl() *Step { \\ [ "$target_triple" ] && host="--host=$target_triple" || host="" \\ \\ ./autogen.sh - \\ ./configure $host --prefix="$install_dir" --enable-opensslall --enable-static --disable-shared \ - \\ --enable-staticmemory --disable-crypttests --disable-benchmark --disable-examples \ - \\ --enable-singlethreaded + \\ ./configure $host \ + \\ --prefix="$install_dir" \ + \\ --enable-static \ + \\ --disable-shared \ + \\ --disable-harden \ + \\ --enable-staticmemory \ + \\ --enable-singlethreaded \ + \\ --disable-threadlocal \ + \\ --disable-asyncthreads \ + \\ --disable-error-queue-per-thread \ + \\ --enable-openssl-compatible-defaults \ + \\ --enable-opensslextra --enable-opensslall \ + \\ --disable-dtls --disable-oldtls --enable-tls13 \ + \\ --enable-chacha --enable-poly1305 --enable-aesgcm \ + \\ --enable-ecc --enable-sni --enable-session-ticket \ + \\ --disable-crypttests --disable-benchmark --disable-examples \ + \\ EXTRA_CFLAGS="-include $cwd/src/wolfssl_opt.h" \\ make install ; @@ -684,8 +700,10 @@ fn link_obj_chinadns(exe: *LibExeObjStep) void { obj.defineCMacroRaw("MUSL"); // wolfssl lib - if (_enable_wolfssl) + if (_enable_wolfssl) { + obj.defineCMacroRaw("ENABLE_WOLFSSL"); obj.addIncludePath(_dep_wolfssl.include_dir); + } // for log.h obj.defineCMacroRaw(fmt("LOG_FILENAME=\"{s}\"", .{file.name})); diff --git a/src/EvLoop.zig b/src/EvLoop.zig index f38bce5..b65f1f3 100644 --- a/src/EvLoop.zig +++ b/src/EvLoop.zig @@ -1,5 +1,6 @@ const std = @import("std"); const root = @import("root"); +const build_opts = @import("build_opts"); const g = @import("g.zig"); const c = @import("c.zig"); const cc = @import("cc.zig"); @@ -482,7 +483,6 @@ pub fn recvfrom(self: *EvLoop, fdobj: *Fd, buf: []u8, flags: c_int, src_addr: *c } } -/// length 0 means EOF pub fn recv(self: *EvLoop, fdobj: *Fd, buf: []u8, flags: c_int) ?usize { while (true) { return cc.recv(fdobj.fd, buf, flags) orelse { @@ -498,15 +498,15 @@ pub fn recv(self: *EvLoop, fdobj: *Fd, buf: []u8, flags: c_int) ?usize { } } -/// read exactly `buf.len` bytes (res:null and errno:0 means EOF) -pub fn recv_exactly(self: *EvLoop, fdobj: *Fd, buf: []u8, flags: c_int) ?void { +const ReadErr = error{ eof, other }; + +pub fn recv_exactly(self: *EvLoop, fdobj: *Fd, buf: []u8, flags: c_int) ReadErr!void { var nread: usize = 0; while (nread < buf.len) { - const n = self.recv(fdobj, buf[nread..], flags) orelse return null; - if (n == 0) { - cc.set_errno(0); // EOF - return null; - } + const n = self.recv(fdobj, buf[nread..], flags) orelse + return ReadErr.other; + if (n == 0) + return ReadErr.eof; nread += n; // https://man7.org/linux/man-pages/man7/epoll.7.html if (nread < buf.len) { diff --git a/src/Upstream.zig b/src/Upstream.zig index 0cc1bfc..0750c7a 100644 --- a/src/Upstream.zig +++ b/src/Upstream.zig @@ -190,8 +190,8 @@ fn udp_recv(self: *Upstream, fd: c_int) void { } } -fn udp_is_eol(self: *const Upstream, in_fdobj: *EvLoop.Fd) bool { - return self.udp_get_fdobj() != in_fdobj; +fn udp_is_eol(self: *const Upstream, fdobj: *EvLoop.Fd) bool { + return self.udp_get_fdobj() != fdobj; } fn udp_on_eol(self: *Upstream) void { @@ -217,8 +217,98 @@ fn udp_on_eol(self: *Upstream) void { const has_ssl = build_opts.enable_wolfssl; -const SSL = if (has_ssl) ?*c.SSL else void; -const SSL_null: SSL = if (has_ssl) null else {}; +const ssl_info_t = struct { + ssl: ?*c.SSL = null, + session: ?*c.SSL_SESSION = null, + + var _ctx: ?*c.SSL_CTX = null; + + pub fn init_ctx() void { + if (_ctx != null) return; + + const ctx = cc.SSL_CTX_new(); + _ctx = ctx; + + const src = @src(); + const ca_certs = b: { + if (g.ca_certs.is_null()) { + if (cc.SSL_CTX_load_sys_CA_certs(ctx)) |ca_certs| break :b ca_certs; + log.err(src, "please specify the CA certs path manually", .{}); + cc.exit(1); + } else { + const ca_certs = g.ca_certs.cstr(); + const ok = if (cc.is_dir(ca_certs)) + cc.SSL_CTX_load_CA_certs(ctx, null, ca_certs) + else + cc.SSL_CTX_load_CA_certs(ctx, ca_certs, null); + if (ok) break :b ca_certs; + log.err(src, "failed to load CA certs: %s", .{ca_certs}); + cc.SSL_ERR_print(src); + cc.exit(1); + } + }; + log.info(src, "loaded CA certs: %s", .{ca_certs}); + + cc.SSL_ERR_clear(); + } + + pub fn new_ssl(self: *SSL_INFO, fd: c_int, host: cc.ConstStr) ?void { + assert(self.ssl == null); + + const ssl = cc.SSL_new(_ctx.?); + + var ok = false; + defer if (!ok) { + const src = @src(); + log.err(src, "failed to create ssl", .{}); + cc.SSL_ERR_print(src); + cc.SSL_free(ssl); + }; + + cc.SSL_set_fd(ssl, fd) orelse return null; + cc.SSL_set_host(ssl, host) orelse return null; + + if (self.session) |session| { + defer { + cc.SSL_SESSION_free(session); + self.session = null; + } + cc.SSL_set_session(ssl, session) orelse return null; + } + + ok = true; + self.ssl = ssl; + } + + /// on EOF + pub fn on_eof(self: *SSL_INFO) void { + return cc.SSL_set_shutdown(self.ssl.?); + } + + // free the ssl obj + pub fn on_close(self: *SSL_INFO) void { + const ssl = self.ssl orelse return; + self.ssl = null; + + // the session should be free after it has been set into an ssl object + assert(self.session == null); + + // close the session + // cc.SSL_set_shutdown(ssl); + + // check if the session is resumable + if (cc.SSL_get1_session(ssl)) |session| { + if (cc.SSL_SESSION_is_resumable(session)) + self.session = session + else + cc.SSL_SESSION_free(session); + } + + cc.SSL_free(ssl); + } +}; + +const SSL_INFO = if (has_ssl) ssl_info_t else struct {}; const TcpCtx = struct { upstream: *const Upstream, @@ -227,7 +317,7 @@ const TcpCtx = struct { ack_list: std.AutoHashMapUnmanaged(u16, *RcMsg) = .{}, // qmsg to be ack pending_n: u16 = 0, // outstanding queries: send_list + ack_list healthy: bool = false, // current connection processed at least one query ? - ssl: SSL = SSL_null, + ssl_info: SSL_INFO = .{}, // for DoT upstream /// must <= u16_max const PENDING_MAX = 1000; @@ -347,10 +437,14 @@ const TcpCtx = struct { /// [async] used to send qmsg to upstream /// pop from send_list && add to ack_list - fn pop_qmsg(self: *TcpCtx, in_fdobj: *EvLoop.Fd) ?*RcMsg { - if (!self.fdobj_ok(in_fdobj)) return null; + fn pop_qmsg(self: *TcpCtx, fdobj: *EvLoop.Fd) ?*RcMsg { + if (!self.fdobj_ok(fdobj)) return null; + const qmsg = self.send_list.pop(true) orelse return null; self.on_sending(qmsg); + + if (!self.fdobj_ok(fdobj)) return null; + return qmsg; } @@ -373,14 +467,11 @@ const TcpCtx = struct { } } - /// connection closed + /// connection closed by peer fn on_close(self: *TcpCtx) void { - if (has_ssl) if (self.ssl) |ssl| { - c.SSL_free(ssl); - self.ssl = null; - }; - self.fdobj = null; + if (has_ssl) self.ssl_info.on_close(); + self.send_list.cancel_wait(); if (self.healthy) { @@ -406,8 +497,8 @@ const TcpCtx = struct { } /// check if disconnected or reconnected - fn fdobj_ok(self: *const TcpCtx, in_fdobj: *const EvLoop.Fd) bool { - return in_fdobj == self.fdobj; + fn fdobj_ok(self: *const TcpCtx, fdobj: *const EvLoop.Fd) bool { + return fdobj == self.fdobj; } fn start(self: *TcpCtx) void { @@ -423,229 +514,219 @@ const TcpCtx = struct { fn send(self: *TcpCtx) void { defer co.terminate(@frame(), @frameSize(TcpCtx.send)); - // nosuspend const fd = net.new_tcp_conn_sock(self.upstream.addr.family()) orelse return self.on_close(); - const fdobj = EvLoop.Fd.new(fd); self.fdobj = fdobj; - defer { if (self.fdobj_ok(fdobj)) self.on_close(); fdobj.free(); } - // async - const err: cc.ConstStr = e: { - self.do_connect() orelse break :e "connect"; - // g.evloop.connect(fdobj, &self.upstream.addr) orelse break :e "connect"; - - co.create(recv, .{self}); - - while (self.pop_qmsg(fdobj)) |qmsg| { - var iov = [_]cc.iovec_t{ - .{ - .iov_base = std.mem.asBytes(&cc.htons(qmsg.len)), - .iov_len = @sizeOf(u16), - }, - .{ - .iov_base = qmsg.msg().ptr, - .iov_len = qmsg.len, - }, - }; - const msg = cc.msghdr_t{ - .msg_iov = &iov, - .msg_iovlen = iov.len, - }; - // g.evloop.sendmsg(fdobj, &msg, 0) orelse break :e "send_query"; - self.do_sendmsg(&msg, 0) orelse break :e "send_query"; - } + self.do_connect(fdobj) orelse return; - return; - }; + co.create(recv, .{self}); - log.err(@src(), "%s(%d, '%s') failed: (%d) %m", .{ err, fdobj.fd, self.upstream.url, cc.errno() }); + while (self.pop_qmsg(fdobj)) |qmsg| { + var iov = [_]cc.iovec_t{ + .{ + .iov_base = std.mem.asBytes(&cc.htons(qmsg.len)), + .iov_len = @sizeOf(u16), + }, + .{ + .iov_base = qmsg.msg().ptr, + .iov_len = qmsg.len, + }, + }; + self.do_send(fdobj, &iov) orelse return; + } } fn recv(self: *TcpCtx) void { defer co.terminate(@frame(), @frameSize(recv)); - // nosuspend const fdobj = self.fdobj.?.ref(); - defer { if (self.fdobj_ok(fdobj)) self.on_close(); fdobj.unref(); } - // async - const err: struct { op: cc.ConstStr, msg: ?cc.ConstStr = null } = e: { - var free_rmsg: ?*RcMsg = null; - defer if (free_rmsg) |rmsg| rmsg.free(); - - while (self.fdobj_ok(fdobj)) { - // read the len - var rlen: u16 = undefined; - // g.evloop.recv_exactly(fdobj, std.mem.asBytes(&rlen), 0) orelse - self.recv_exactly(std.mem.asBytes(&rlen), 0) orelse - if (cc.errno() == 0) return else break :e .{ .op = "read_len" }; + var free_rmsg: ?*RcMsg = null; + defer if (free_rmsg) |rmsg| rmsg.free(); - rlen = cc.ntohs(rlen); - if (rlen < c.DNS_MSG_MINSIZE) - break :e .{ .op = "read_len", .msg = "invalid msg len" }; + while (true) { + // read the len + var rlen: u16 = undefined; + self.do_recv(fdobj, std.mem.asBytes(&rlen), 0) orelse return; - const rmsg: *RcMsg = if (free_rmsg) |rmsg| rmsg.realloc(rlen) else RcMsg.new(rlen); - free_rmsg = null; - - defer { - if (rmsg.is_unique()) - free_rmsg = rmsg - else - rmsg.unref(); - } + // check the len + rlen = cc.ntohs(rlen); + if (rlen < c.DNS_MSG_MINSIZE) { + log.warn(@src(), "read_len(%d, '%s') failed: invalid len:%u", .{ fdobj.fd, self.upstream.url, cc.to_uint(rlen) }); + return; + } - // read the msg - rmsg.len = rlen; - // g.evloop.recv_exactly(fdobj, rmsg.msg(), 0) orelse - self.recv_exactly(rmsg.msg(), 0) orelse - break :e .{ .op = "read_msg", .msg = if (cc.errno() == 0) "connection closed" else null }; + const rmsg: *RcMsg = if (free_rmsg) |rmsg| rmsg.realloc(rlen) else RcMsg.new(rlen); + free_rmsg = null; + defer { + if (rmsg.is_unique()) + free_rmsg = rmsg + else + rmsg.unref(); + } - if (self.fdobj_ok(fdobj)) - self.on_reply(rmsg); + // read the msg + rmsg.len = rlen; + self.do_recv(fdobj, rmsg.msg(), 0) orelse return; - server.on_reply(rmsg, self.upstream); - } + if (self.fdobj_ok(fdobj)) + self.on_reply(rmsg); - return; - }; + server.on_reply(rmsg, self.upstream); + } + } + /// `errmsg`: null means strerror(errno) + noinline fn on_error(self: *const TcpCtx, op: cc.ConstStr, errmsg: ?cc.ConstStr) ?void { const src = @src(); - if (err.msg) |msg| - log.err(src, "%s(%d, '%s') failed: %s", .{ err.op, fdobj.fd, self.upstream.url, msg }) + + if (errmsg) |msg| + log.warn(src, "%s(%s) failed: %s", .{ op, self.upstream.url, msg }) else - log.err(src, "%s(%d, '%s') failed: (%d) %m", .{ err.op, fdobj.fd, self.upstream.url, cc.errno() }); - } + log.warn(src, "%s(%s) failed: (%d) %m", .{ op, self.upstream.url, cc.errno() }); - fn do_connect(self: *TcpCtx) ?void { - g.evloop.connect(self.fdobj.?, &self.upstream.addr) orelse return null; + if (has_ssl and self.upstream.proto == .tls) + cc.SSL_ERR_print(src); - if (has_ssl and self.upstream.proto == .tls) { - const static = struct { - var ssl_ctx: ?*c.SSL_CTX = null; + return null; + } - fn get_ssl_ctx() *c.SSL_CTX { - if (ssl_ctx == null) { - ssl_ctx = c.wolfSSL_CTX_new(c.TLS_client_method()); - assert(ssl_ctx != null); - load_ca_certs(); - } - return ssl_ctx.?; - } + fn ssl(self: *const TcpCtx) *c.SSL { + return self.ssl_info.ssl.?; + } - fn load_ca_certs() void { - const file_list = [_][*:0]const u8{ - "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc. - "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6 - "/etc/ssl/ca-bundle.pem", // OpenSUSE - "/etc/pki/tls/cacert.pem", // OpenELEC - "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7 - "/etc/ssl/cert.pem", // Alpine Linux + fn do_connect(self: *TcpCtx, fdobj: *EvLoop.Fd) ?void { + // null means strerror(errno) + const errmsg: ?cc.ConstStr = e: { + g.evloop.connect(fdobj, &self.upstream.addr) orelse break :e null; + + if (has_ssl and self.upstream.proto == .tls) { + self.ssl_info.new_ssl(fdobj.fd, self.upstream.host.?) orelse break :e "ubable to create ssl object"; + + while (true) { + var err: c_int = undefined; + cc.SSL_connect(self.ssl(), &err) orelse switch (err) { + c.SSL_ERROR_WANT_READ => { + g.evloop.wait_readable(fdobj); + continue; + }, + c.SSL_ERROR_WANT_WRITE => { + g.evloop.wait_writable(fdobj); + continue; + }, + else => { + break :e if (err == c.SSL_ERROR_SYSCALL) null else cc.SSL_error_name(err); + }, }; - const dir_list = [_][*:0]const u8{ - "/etc/ssl/certs", // SLES10/SLES11, https://golang.org/issue/12139 - "/etc/pki/tls/certs", // Fedora/RHEL - }; - for (file_list) |path| { - if (c.SSL_CTX_load_verify_locations(ssl_ctx, path, null) == 1) { - if (g.verbose()) - log.info(@src(), "CA certs: %s", .{path}); - return; - } - } - for (dir_list) |path| { - if (c.SSL_CTX_load_verify_locations(ssl_ctx, null, path) == 1) { - if (g.verbose()) - log.info(@src(), "CA certs: %s", .{path}); - return; - } - } - log.err(@src(), "can't load CA certs, TODO: add option", .{}); - cc.exit(1); + break; } - }; - - self.ssl = c.SSL_new(static.get_ssl_ctx()); - assert(c.SSL_set_fd(self.ssl, self.fdobj.?.fd) == 1); - assert(c.SSL_set_tlsext_host_name(self.ssl, self.upstream.host) == 1); // SNI (client hello) - assert(c.SSL_set1_host(self.ssl, self.upstream.host) == 1); // host check (server hello) - while (true) { - const res = c.SSL_connect(self.ssl); - if (res == 1) break; - self.on_ssl_error(res) orelse return null; + if (g.verbose()) { + log.info(@src(), "%s | %s | %s | %s", .{ + self.upstream.url, + cc.SSL_get_version(self.ssl()), + cc.SSL_get_cipher(self.ssl()), + cc.b2s(cc.SSL_session_reused(self.ssl()), "reused", "non-reused"), + }); + } } - } - } - fn do_sendmsg(self: *TcpCtx, msg: *const cc.msghdr_t, flags: c_int) ?void { - if (!has_ssl or self.upstream.proto != .tls) - return g.evloop.sendmsg(self.fdobj.?, msg, flags); + return; + }; - for (msg.iov_items()) |iov| { - while (true) { - const res = c.SSL_write(self.ssl, iov.iov_base, cc.to_int(iov.iov_len)); - if (res > 0) break; - self.on_ssl_error(res) orelse return null; - } - } + return self.on_error("connect", errmsg); } - fn recv_exactly(self: *TcpCtx, buf: []u8, flags: c_int) ?void { - if (!has_ssl or self.upstream.proto != .tls) - return g.evloop.recv_exactly(self.fdobj.?, buf, flags); + fn do_send(self: *TcpCtx, fdobj: *EvLoop.Fd, iov: []cc.iovec_t) ?void { + // null means strerror(errno) + const errmsg: ?cc.ConstStr = e: { + if (!self.fdobj_ok(fdobj)) return null; - var nread: usize = 0; - while (nread < buf.len) { - const res = c.SSL_read(self.ssl, buf.ptr + nread, cc.to_int(buf.len - nread)); - if (res > 0) { - nread += cc.to_usize(res); - } else { - self.on_ssl_error(res) orelse return null; - } - } - } + if (self.upstream.proto != .tls) { + const msg = cc.msghdr_t{ + .msg_iov = iov.ptr, + .msg_iovlen = iov.len, + }; + g.evloop.sendmsg(fdobj, &msg, 0) orelse break :e null; // async + } else if (has_ssl) { + for (iov) |*v| { + while (true) { + if (!self.fdobj_ok(fdobj)) return null; + + var err: c_int = undefined; + cc.SSL_write(self.ssl(), v.iov_base[0..v.iov_len], &err) orelse switch (err) { + c.SSL_ERROR_WANT_WRITE => { + g.evloop.wait_writable(fdobj); // async + continue; + }, + else => { + break :e if (err == c.SSL_ERROR_SYSCALL) null else cc.SSL_error_name(err); + }, + }; + break; + } + } + } else unreachable; - /// null && errno=0 => EOF - fn on_ssl_error(self: *TcpCtx, res: c_int) ?void { - const err = c.SSL_get_error(self.ssl, res); - switch (err) { - c.SSL_ERROR_ZERO_RETURN => { - cc.set_errno(0); - return null; - }, - c.SSL_ERROR_WANT_READ => { - g.evloop.wait_readable(self.fdobj.?); - }, - c.SSL_ERROR_WANT_WRITE => { - g.evloop.wait_writable(self.fdobj.?); - }, - else => { - log.err(@src(), "ssl error: %s", .{get_ssl_errstr(@bitCast(c_ulong, @as(c_long, err)))}); - return null; - }, - } + return; + }; + + return self.on_error("send", errmsg); } - /// static buffer - fn get_ssl_errstr(err: c_ulong) cc.ConstStr { - const static = struct { - var buf: [50]u8 = undefined; + fn do_recv(self: *TcpCtx, fdobj: *EvLoop.Fd, buf: []u8, flags: c_int) ?void { + // null means strerror(errno) + const errmsg: ?cc.ConstStr = e: { + if (!self.fdobj_ok(fdobj)) return null; + + if (self.upstream.proto != .tls) { + g.evloop.recv_exactly(fdobj, buf, flags) catch |err| switch (err) { + error.eof => return null, + error.other => break :e null, + }; + } else if (has_ssl) { + var nread: usize = 0; + while (nread < buf.len) { + if (!self.fdobj_ok(fdobj)) return null; + + var err: c_int = undefined; + const n = cc.SSL_read(self.ssl(), buf[nread..], &err) orelse switch (err) { + c.SSL_ERROR_ZERO_RETURN => { + self.ssl_info.on_eof(); + return null; + }, + c.SSL_ERROR_WANT_READ => { + g.evloop.wait_readable(fdobj); //async + continue; + }, + else => { + // SSL_OP_IGNORE_UNEXPECTED_EOF (wolfssl does not support this) + if (err == c.SSL_ERROR_SYSCALL and cc.errno() == 0) { + self.ssl_info.on_eof(); + return null; + } + break :e if (err == c.SSL_ERROR_SYSCALL) null else cc.SSL_error_name(err); + }, + }; + nread += n; + } + } else unreachable; + + return; }; - return cc.ptrcast( - cc.ConstStr, - c.ERR_error_string(err, &static.buf), - ); + + return self.on_error("recv", errmsg); } }; @@ -843,6 +924,9 @@ pub const Group = struct { const ptr = self.list.addOne(g.allocator) catch unreachable; ptr.* = Upstream.init(tag, proto, &addr, host, ip, port); + + if (has_ssl and proto == .tls) + SSL_INFO.init_ctx(); } pub fn rm_useless(self: *Group) void { diff --git a/src/c.zig b/src/c.zig index 376fbe5..881afcd 100644 --- a/src/c.zig +++ b/src/c.zig @@ -2,20 +2,12 @@ // because it saves the compiler from invoking clang multiple times, // and prevents inline functions from being duplicated. -const build_opts = @import("build_opts"); - // import into the current namespace (c.zig) // mainly used to access C constants, C typedefs // please give priority to using functions in the `cc` namespace -pub usingnamespace @cImport({ +usingnamespace @cImport({ @cDefine("_GNU_SOURCE", {}); - if (build_opts.enable_wolfssl) { - @cInclude("wolfssl/options.h"); - @cInclude("wolfssl/openssl/ssl.h"); - @cInclude("wolfssl/openssl/err.h"); - } - @cInclude("stdio.h"); @cInclude("stdlib.h"); @cInclude("stdint.h"); @@ -40,6 +32,7 @@ pub usingnamespace @cImport({ @cInclude("src/dnl.h"); @cInclude("src/ipset.h"); @cInclude("src/misc.h"); + @cInclude("src/wolfssl.h"); }); /// assuming CHAR_BIT=8 diff --git a/src/cc.zig b/src/cc.zig index e5995b2..f5dabe6 100644 --- a/src/cc.zig +++ b/src/cc.zig @@ -4,6 +4,7 @@ const std = @import("std"); const c = @import("c.zig"); const g = @import("g.zig"); +const log = @import("log.zig"); const fmtchk = @import("fmtchk.zig"); const meta = std.meta; const testing = std.testing; @@ -132,6 +133,24 @@ pub inline fn memeql(a: []const u8, b: []const u8) bool { return a.len == b.len and c.memcmp(a.ptr, b.ptr, a.len) == 0; } +/// avoid static buffers all over the place, wasting memory +pub noinline fn static_buf(size: usize) []u8 { + const static = struct { + var buf: []u8 = &.{}; + }; + if (size > static.buf.len) { + if (static.buf.len == 0) { + static.buf = g.allocator.alloc(u8, size) catch unreachable; + } else if (g.allocator.resize(static.buf, size)) |buf| { + static.buf = buf; + } else { + g.allocator.free(static.buf); + static.buf = g.allocator.alloc(u8, size) catch unreachable; + } + } + return static.buf.ptr[0..size]; +} + /// convert to C string (static buffer) pub inline fn to_cstr(str: []const u8) Str { return to_cstr_x(&.{str}); @@ -139,25 +158,20 @@ pub inline fn to_cstr(str: []const u8) Str { /// convert to C string (static buffer) pub noinline fn to_cstr_x(str_list: []const []const u8) Str { - const static = struct { - var buffer: []u8 = &.{}; - }; - var total_len: usize = 0; for (str_list) |str| total_len += str.len; - if (total_len + 1 > static.buffer.len) - static.buffer = g.allocator.realloc(static.buffer, total_len + 1) catch unreachable; + const buf = static_buf(total_len + 1); - var ptr = static.buffer.ptr; + var ptr = buf.ptr; for (str_list) |str| { @memcpy(ptr, str.ptr, str.len); ptr += str.len; } ptr[0] = 0; - return @ptrCast(Str, static.buffer.ptr); + return @ptrCast(Str, buf.ptr); } /// end with sentinel 0 @@ -420,6 +434,10 @@ pub inline fn open(filename: ConstStr, flags: c_int, newfile_mode: ?c.mode_t) ?c return if (fd >= 0) fd else null; } +pub inline fn is_dir(path: ConstStr) bool { + return c.is_dir(path); +} + pub inline fn fstat_size(fd: c_int) ?usize { const sz = c.fstat_size(fd); return if (sz >= 0) to_usize(sz) else null; @@ -741,6 +759,208 @@ pub fn mmap_file(filename: ConstStr) ?[]const u8 { // ============================================================== +/// pop error from the openssl thread's error queue +/// the returned string is a pointer to the static buffer +/// the current thread's error queue must be empty before the SSL I/O operation is attempted +pub fn SSL_ERR_pop() ?ConstStr { + const err = c.ERR_get_error(); // pop error + return if (err != 0) c.ERR_error_string(err, null) else null; +} + +/// empties the current thread's error queue +/// the current thread's error queue must be empty before the SSL I/O operation is attempted +pub fn SSL_ERR_clear() void { + return c.ERR_clear_error(); +} + +/// print all error and clear the error queue +pub fn SSL_ERR_print(comptime src: std.builtin.SourceLocation) void { + while (SSL_ERR_pop()) |err| + log.warn(src, "ssl error: %s", .{err}); +} + +/// SSL I/O operation errcode => errname (`SSL_ERROR_*`) +/// the returned string is a pointer to the static buffer +pub fn SSL_error_name(err: c_int) ConstStr { + return switch (err) { + c.SSL_ERROR_WANT_READ => "SSL_ERROR_WANT_READ", + c.SSL_ERROR_WANT_WRITE => "SSL_ERROR_WANT_WRITE", + c.SSL_ERROR_SYSCALL => "SSL_ERROR_SYSCALL", + c.SSL_ERROR_ZERO_RETURN => "SSL_ERROR_ZERO_RETURN", + c.SSL_ERROR_WANT_CONNECT => "SSL_ERROR_WANT_CONNECT", + c.SSL_ERROR_WANT_ACCEPT => "SSL_ERROR_WANT_ACCEPT", + c.SSL_ERROR_WANT_X509_LOOKUP => "SSL_ERROR_WANT_X509_LOOKUP", + c.SSL_ERROR_SSL => "SSL_ERROR_SSL", + else => snprintf(static_buf(30), "SSL_ERROR_UNKNOWN(%d)", .{err}).ptr, + }; +} + +/// client-side only +pub fn SSL_CTX_new() *c.SSL_CTX { + // some macros are not translated correctly + // const ctx = c.SSL_CTX_new(switch (side) { + const ctx = c.wolfSSL_CTX_new(c.TLS_client_method()).?; + + // tls12 + tls13 + assert(c.SSL_CTX_set_min_proto_version(ctx, c.TLS1_2_VERSION) == 1); + + // cipher list + // openssl has a separate API for tls13, but wolfssl only has one + const chacha20 = "TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305"; + const aes128gcm = "TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256"; + const cipher_list = if (c.has_aes()) aes128gcm ++ ":" ++ chacha20 else chacha20 ++ ":" ++ aes128gcm; + assert(c.SSL_CTX_set_cipher_list(ctx, cipher_list) == 1); + + // options + _ = c.SSL_CTX_set_options(ctx, c.SSL_OP_NO_COMPRESSION | c.SSL_OP_NO_RENEGOTIATION); + + return ctx; +} + +pub fn SSL_CTX_load_CA_certs(ctx: *c.SSL_CTX, file: ?ConstStr, path: ?ConstStr) bool { + return c.SSL_CTX_load_verify_locations(ctx, file, path) == 1; +} + +pub fn SSL_CTX_load_sys_CA_certs(ctx: *c.SSL_CTX) ?ConstStr { + // https://go.dev/src/crypto/x509/root_linux.go + const file_list = [_]ConstStr{ + "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc. + "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6 + "/etc/ssl/ca-bundle.pem", // OpenSUSE + "/etc/pki/tls/cacert.pem", // OpenELEC + "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7 + "/etc/ssl/cert.pem", // Alpine Linux + }; + + const dir_list = [_]ConstStr{ + "/etc/ssl/certs", // SLES10/SLES11 + "/etc/pki/tls/certs", // Fedora/RHEL + }; + + for (file_list) |file| { + if (SSL_CTX_load_CA_certs(ctx, file, null)) + return file; + SSL_ERR_clear(); + } + + for (dir_list) |dir| { + if (SSL_CTX_load_CA_certs(ctx, null, dir)) + return dir; + SSL_ERR_clear(); + } + + // all attempts failed + // must be specified by the user + return null; +} + +pub fn SSL_new(ctx: *c.SSL_CTX) *c.SSL { + return c.SSL_new(ctx).?; +} + +pub fn SSL_free(ssl: *c.SSL) void { + return c.SSL_free(ssl); +} + +pub fn SSL_set_fd(ssl: *c.SSL, fd: c_int) ?void { + return if (c.SSL_set_fd(ssl, fd) == 1) {} else null; +} + +/// set SNI && enable hostname validation during SSL handshake +pub fn SSL_set_host(ssl: *c.SSL, hostname: ConstStr) ?void { + // tls_ext: SNI (ClientHello) + if (c.SSL_set_tlsext_host_name(ssl, hostname) != 1) + return null; + + // set hostname for validation + if (c.SSL_set1_host(ssl, hostname) != 1) + return null; + + // enable hostname validation + c.SSL_set_verify(ssl, c.SSL_VERIFY_PEER, null); +} + +/// set session to be used when the TLS/SSL connection is to be established (resumption) +/// when the session is set, the reference count of session is incremented by 1 (owner by ssl) +/// after session is set, SSL_SESSION_free() can be called to dereference it (release ownership) +pub fn SSL_set_session(ssl: *c.SSL, session: *c.SSL_SESSION) ?void { + return if (c.SSL_set_session(ssl, session) == 1) {} else null; +} + +/// perform SSL/TLS handshake (underlying transport is established) +/// `p_err`: to save the failure reason (SSL_ERROR_*) +pub fn SSL_connect(ssl: *c.SSL, p_err: *c_int) ?void { + const res = c.SSL_connect(ssl); + if (res == 1) { + return {}; + } else { + p_err.* = c.SSL_get_error(ssl, res); + return null; + } +} + +/// the name of the protocol used for the connection +pub fn SSL_get_version(ssl: *const c.SSL) ConstStr { + return c.SSL_get_version(ssl); +} + +/// the name of the cipher used for the connection +pub fn SSL_get_cipher(ssl: *c.SSL) ConstStr { + return c.SSL_get_cipher(ssl); +} + +/// queries whether session resumption occurred during the handshake +pub fn SSL_session_reused(ssl: *c.SSL) bool { + return c.SSL_session_reused(ssl) == 1; +} + +/// return the number of bytes read (> 0) +/// `p_err`: to save the failure reason (SSL_ERROR_*) +pub fn SSL_read(ssl: *c.SSL, buf: []u8, p_err: *c_int) ?usize { + const res = c.SSL_read(ssl, buf.ptr, to_int(buf.len)); + if (res > 0) { + return to_usize(res); + } else { + p_err.* = c.SSL_get_error(ssl, res); + return null; + } +} + +/// assume SSL_MODE_ENABLE_PARTIAL_WRITE is not in use +/// `p_err`: to save the failure reason (SSL_ERROR_*) +pub fn SSL_write(ssl: *c.SSL, buf: []const u8, p_err: *c_int) ?void { + const res = c.SSL_write(ssl, buf.ptr, to_int(buf.len)); + if (res > 0) { + return {}; + } else { + p_err.* = c.SSL_get_error(ssl, res); + return null; + } +} + +/// mark the SSL connection as complete two-directional shutdown (close the ssl session) +/// calling `SSL_get1_session()` after shutdown will give a resumable session if any was sent +pub fn SSL_set_shutdown(ssl: *c.SSL) void { + return c.SSL_set_shutdown(ssl, c.SSL_SENT_SHUTDOWN | c.SSL_RECEIVED_SHUTDOWN); +} + +/// the reference count of the SSL_SESSION is incremented by one +/// in TLSv1.3 it is recommended that each SSL_SESSION object is only used for resumption once +pub fn SSL_get1_session(ssl: *c.SSL) ?*c.SSL_SESSION { + return c.SSL_get1_session(ssl); +} + +/// determine whether an SSL_SESSION object can be used for resumption +pub fn SSL_SESSION_is_resumable(session: *const c.SSL_SESSION) bool { + return c.SSL_SESSION_is_resumable(session) == 1; +} + +pub fn SSL_SESSION_free(session: *c.SSL_SESSION) void { + return c.SSL_SESSION_free(session); +} + +// ============================================================== + pub fn @"test: strslice"() !void { const hello = "hello"; const N = hello.len; diff --git a/src/dns.zig b/src/dns.zig index d5f0115..d49e754 100644 --- a/src/dns.zig +++ b/src/dns.zig @@ -48,12 +48,11 @@ pub inline fn is_tc(msg: []const u8) bool { return c.dns_is_tc(msg.ptr); } -var _msgbuffer: [c.DNS_QMSG_MAXSIZE]u8 = undefined; - /// return the truncated msg (ptr to static buffer) pub inline fn truncate(msg: []const u8) []u8 { - const len = c.dns_truncate(msg.ptr, cc.to_isize(msg.len), &_msgbuffer); - return _msgbuffer[0..len]; + const res_msg = cc.static_buf(c.DNS_QMSG_MAXSIZE); + const len = c.dns_truncate(msg.ptr, cc.to_isize(msg.len), res_msg.ptr); + return res_msg[0..len]; } /// return the updated msg diff --git a/src/g.zig b/src/g.zig index 990583a..f337fd6 100644 --- a/src/g.zig +++ b/src/g.zig @@ -71,3 +71,6 @@ pub var evloop: EvLoop = undefined; /// global memory allocator pub var allocator: std.mem.Allocator = undefined; + +/// the location of CA certs +pub var ca_certs: DynStr = .{}; diff --git a/src/misc.c b/src/misc.c index c371cf2..54392d4 100644 --- a/src/misc.c +++ b/src/misc.c @@ -1,6 +1,8 @@ #define _GNU_SOURCE #include "misc.h" #include "uthash.h" +#include +#include #include #include @@ -16,6 +18,13 @@ const void *SIG_ERROR(void) { return SIG_ERR; } +bool is_dir(const char *path) { + struct stat st; + if (stat(path, &st) == 0) + return S_ISDIR(st.st_mode); + return false; +} + ssize_t fstat_size(int fd) { struct stat st; if (fstat(fd, &st) == 0) @@ -28,3 +37,22 @@ uint calc_hashv(const void *ptr, size_t len) { HASH_FUNCTION(ptr, len, hashv); return hashv; } + +bool has_aes(void) { + bool found = false; + + FILE *f = fopen("/proc/cpuinfo", "r"); + if (!f) goto out; + + char buf[10]; + while (fscanf(f, "%9s", buf) > 0) { + if (strstr(buf, "aes")) { + found = true; + break; + } + } + +out: + if (f) fclose(f); + return found; +} diff --git a/src/misc.h b/src/misc.h index b1ae551..0468e62 100644 --- a/src/misc.h +++ b/src/misc.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #define likely(x) __builtin_expect(!!(x), 1) @@ -126,6 +127,10 @@ const void *SIG_IGNORE(void); const void *SIG_DEFAULT(void); const void *SIG_ERROR(void); +bool is_dir(const char *path); + ssize_t fstat_size(int fd); uint calc_hashv(const void *ptr, size_t len); + +bool has_aes(void); diff --git a/src/opt.zig b/src/opt.zig index cd90b43..e3e9b3e 100644 --- a/src/opt.zig +++ b/src/opt.zig @@ -46,6 +46,7 @@ const help = \\ --verdict-cache enable verdict caching for tag:none domains \\ --hosts [path] load hosts file, default path is /etc/hosts \\ --dns-rr-ip = define local resource records of type A/AAAA + \\ --ca-certs CA certs path for SSL certificate validation \\ -o, --timeout-sec response timeout of upstream, default: 5 \\ -p, --repeat-times num of packets to trustdns, default:1, max:5 \\ -n, --noip-as-chnip allow no-ip reply from chinadns (tag:none) @@ -135,6 +136,7 @@ const optdef_array = [_]OptDef{ .{ .short = "", .long = "verdict-cache", .value = .required, .optfn = opt_verdict_cache, }, .{ .short = "", .long = "hosts", .value = .optional, .optfn = opt_hosts, }, .{ .short = "", .long = "dns-rr-ip", .value = .required, .optfn = opt_dns_rr_ip, }, + .{ .short = "", .long = "ca-certs", .value = .required, .optfn = opt_ca_certs, }, .{ .short = "o", .long = "timeout-sec", .value = .required, .optfn = opt_timeout_sec, }, .{ .short = "p", .long = "repeat-times", .value = .required, .optfn = opt_repeat_times, }, .{ .short = "n", .long = "noip-as-chnip", .value = .no_value, .optfn = opt_noip_as_chnip, }, @@ -492,6 +494,10 @@ fn opt_dns_rr_ip(in_value: ?[]const u8) void { } } +fn opt_ca_certs(in_value: ?[]const u8) void { + g.ca_certs.set(in_value.?); +} + fn opt_timeout_sec(in_value: ?[]const u8) void { const value = in_value.?; g.upstream_timeout = str2int.parse(@TypeOf(g.upstream_timeout), value, 10) orelse 0; diff --git a/src/server.zig b/src/server.zig index e8e7141..c73730e 100644 --- a/src/server.zig +++ b/src/server.zig @@ -215,8 +215,10 @@ fn service_tcp(fd: c_int, p_src_addr: *const cc.SockAddr) void { while (true) { // read len (be16) var len: u16 = undefined; - g.evloop.recv_exactly(fdobj, std.mem.asBytes(&len), 0) orelse - if (cc.errno() == 0) return else break :e .{ .op = "read_len" }; + g.evloop.recv_exactly(fdobj, std.mem.asBytes(&len), 0) catch |err| switch (err) { + error.eof => return, + error.other => break :e .{ .op = "read_len" }, + }; len = cc.ntohs(len); if (len < c.DNS_MSG_MINSIZE or len > c.DNS_QMSG_MAXSIZE) { @@ -236,8 +238,10 @@ fn service_tcp(fd: c_int, p_src_addr: *const cc.SockAddr) void { // read msg qmsg.len = len; - g.evloop.recv_exactly(fdobj, qmsg.msg(), 0) orelse - break :e .{ .op = "read_msg", .msg = if (cc.errno() == 0) "connection closed" else null }; + g.evloop.recv_exactly(fdobj, qmsg.msg(), 0) catch |err| switch (err) { + error.eof => break :e .{ .op = "read_msg", .msg = "connection closed" }, + error.other => break :e .{ .op = "read_msg" }, + }; on_query(qmsg, fdobj, &src_addr, .from_tcp); } diff --git a/src/wolfssl.c b/src/wolfssl.c new file mode 100644 index 0000000..dd1bfaf --- /dev/null +++ b/src/wolfssl.c @@ -0,0 +1,6 @@ +#define _GNU_SOURCE +#include "wolfssl.h" + +#ifdef ENABLE_WOLFSSL + +#endif diff --git a/src/wolfssl.h b/src/wolfssl.h new file mode 100644 index 0000000..bfe9e67 --- /dev/null +++ b/src/wolfssl.h @@ -0,0 +1,10 @@ +#pragma once + +#ifdef ENABLE_WOLFSSL + +#include "wolfssl_opt.h" +#include +#include +#include + +#endif diff --git a/src/wolfssl_opt.h b/src/wolfssl_opt.h new file mode 100644 index 0000000..974e950 --- /dev/null +++ b/src/wolfssl_opt.h @@ -0,0 +1,9 @@ +#pragma once + +#define NO_WOLFSSL_SERVER 1 +#define WOLFSSL_NO_ATOMICS 1 +#define WOLFSSL_AEAD_ONLY 1 + +#define LARGE_STATIC_BUFFERS 1 +#define STATIC_CHUNKS_ONLY 1 + diff --git a/zls.build.json b/zls.build.json index d780d5a..97c3c72 100644 --- a/zls.build.json +++ b/zls.build.json @@ -1,7 +1,7 @@ { "build_options": [ { - "name": "openssl", + "name": "wolfssl", "value": "true" }, { From 827274c714ed1f7d6f1d2d787ff9e1f6c9425526 Mon Sep 17 00:00:00 2001 From: zfl9 Date: Thu, 25 Apr 2024 17:21:46 +0800 Subject: [PATCH 12/32] DoT --- src/wolfssl.c | 6 ------ src/wolfssl_opt.h | 1 - 2 files changed, 7 deletions(-) delete mode 100644 src/wolfssl.c diff --git a/src/wolfssl.c b/src/wolfssl.c deleted file mode 100644 index dd1bfaf..0000000 --- a/src/wolfssl.c +++ /dev/null @@ -1,6 +0,0 @@ -#define _GNU_SOURCE -#include "wolfssl.h" - -#ifdef ENABLE_WOLFSSL - -#endif diff --git a/src/wolfssl_opt.h b/src/wolfssl_opt.h index 974e950..c195b41 100644 --- a/src/wolfssl_opt.h +++ b/src/wolfssl_opt.h @@ -6,4 +6,3 @@ #define LARGE_STATIC_BUFFERS 1 #define STATIC_CHUNKS_ONLY 1 - From e61fd7244a0a90c6cb77eebaf15d97c214f41310 Mon Sep 17 00:00:00 2001 From: zfl9 Date: Thu, 25 Apr 2024 20:40:31 +0800 Subject: [PATCH 13/32] DoT --- build.zig | 7 ++++--- src/Upstream.zig | 45 +++++++++++++++++++++------------------------ src/cc.zig | 2 +- src/groups.zig | 8 +++++++- src/wolfssl_opt.h | 2 +- 5 files changed, 34 insertions(+), 30 deletions(-) diff --git a/build.zig b/build.zig index 211779b..4e66ffc 100644 --- a/build.zig +++ b/build.zig @@ -141,7 +141,6 @@ fn option_lto() void { else => false, }; _lto = _b.option(bool, "lto", "enable LTO, default to true if in fast/small/safe mode") orelse default; - _lto = false; } fn option_strip() void { @@ -532,7 +531,8 @@ fn build_wolfssl() *Step { \\ [ "$target_triple" ] && host="--host=$target_triple" || host="" \\ \\ ./autogen.sh - \\ ./configure $host \ + \\ ./configure \ + \\ $host \ \\ --prefix="$install_dir" \ \\ --enable-static \ \\ --disable-shared \ @@ -545,7 +545,8 @@ fn build_wolfssl() *Step { \\ --enable-openssl-compatible-defaults \ \\ --enable-opensslextra --enable-opensslall \ \\ --disable-dtls --disable-oldtls --enable-tls13 \ - \\ --enable-chacha --enable-poly1305 --enable-aesgcm \ + \\ --enable-chacha --enable-poly1305 \ + \\ --enable-aesgcm --disable-aescbc \ \\ --enable-ecc --enable-sni --enable-session-ticket \ \\ --disable-crypttests --disable-benchmark --disable-examples \ \\ EXTRA_CFLAGS="-include $cwd/src/wolfssl_opt.h" diff --git a/src/Upstream.zig b/src/Upstream.zig index 0750c7a..cffe6d3 100644 --- a/src/Upstream.zig +++ b/src/Upstream.zig @@ -215,15 +215,15 @@ fn udp_on_eol(self: *Upstream) void { // ====================================================== -const has_ssl = build_opts.enable_wolfssl; +pub const has_tls = build_opts.enable_wolfssl; -const ssl_info_t = struct { +pub const TLS = struct { ssl: ?*c.SSL = null, session: ?*c.SSL_SESSION = null, var _ctx: ?*c.SSL_CTX = null; - pub fn init_ctx() void { + pub fn init() void { if (_ctx != null) return; const ctx = cc.SSL_CTX_new(); @@ -233,7 +233,7 @@ const ssl_info_t = struct { const ca_certs = b: { if (g.ca_certs.is_null()) { if (cc.SSL_CTX_load_sys_CA_certs(ctx)) |ca_certs| break :b ca_certs; - log.err(src, "please specify the CA certs path manually", .{}); + log.err(src, "please specify the CA certs path", .{}); cc.exit(1); } else { const ca_certs = g.ca_certs.cstr(); @@ -247,12 +247,12 @@ const ssl_info_t = struct { cc.exit(1); } }; - log.info(src, "loaded CA certs: %s", .{ca_certs}); + log.info(src, "%s", .{ca_certs}); cc.SSL_ERR_clear(); } - pub fn new_ssl(self: *SSL_INFO, fd: c_int, host: cc.ConstStr) ?void { + pub fn new_ssl(self: *tls_t, fd: c_int, host: cc.ConstStr) ?void { assert(self.ssl == null); const ssl = cc.SSL_new(_ctx.?); @@ -281,12 +281,12 @@ const ssl_info_t = struct { } /// on EOF - pub fn on_eof(self: *SSL_INFO) void { + pub fn on_eof(self: *tls_t) void { return cc.SSL_set_shutdown(self.ssl.?); } // free the ssl obj - pub fn on_close(self: *SSL_INFO) void { + pub fn on_close(self: *tls_t) void { const ssl = self.ssl orelse return; self.ssl = null; @@ -308,7 +308,7 @@ const ssl_info_t = struct { } }; -const SSL_INFO = if (has_ssl) ssl_info_t else struct {}; +const tls_t = if (has_tls) TLS else struct {}; const TcpCtx = struct { upstream: *const Upstream, @@ -317,7 +317,7 @@ const TcpCtx = struct { ack_list: std.AutoHashMapUnmanaged(u16, *RcMsg) = .{}, // qmsg to be ack pending_n: u16 = 0, // outstanding queries: send_list + ack_list healthy: bool = false, // current connection processed at least one query ? - ssl_info: SSL_INFO = .{}, // for DoT upstream + tls: tls_t = .{}, // for DoT upstream /// must <= u16_max const PENDING_MAX = 1000; @@ -470,7 +470,7 @@ const TcpCtx = struct { /// connection closed by peer fn on_close(self: *TcpCtx) void { self.fdobj = null; - if (has_ssl) self.ssl_info.on_close(); + if (has_tls) self.tls.on_close(); self.send_list.cancel_wait(); @@ -596,14 +596,14 @@ const TcpCtx = struct { else log.warn(src, "%s(%s) failed: (%d) %m", .{ op, self.upstream.url, cc.errno() }); - if (has_ssl and self.upstream.proto == .tls) + if (has_tls and self.upstream.proto == .tls) cc.SSL_ERR_print(src); return null; } fn ssl(self: *const TcpCtx) *c.SSL { - return self.ssl_info.ssl.?; + return self.tls.ssl.?; } fn do_connect(self: *TcpCtx, fdobj: *EvLoop.Fd) ?void { @@ -611,8 +611,8 @@ const TcpCtx = struct { const errmsg: ?cc.ConstStr = e: { g.evloop.connect(fdobj, &self.upstream.addr) orelse break :e null; - if (has_ssl and self.upstream.proto == .tls) { - self.ssl_info.new_ssl(fdobj.fd, self.upstream.host.?) orelse break :e "ubable to create ssl object"; + if (has_tls and self.upstream.proto == .tls) { + self.tls.new_ssl(fdobj.fd, self.upstream.host.?) orelse break :e "ubable to create ssl object"; while (true) { var err: c_int = undefined; @@ -637,7 +637,7 @@ const TcpCtx = struct { self.upstream.url, cc.SSL_get_version(self.ssl()), cc.SSL_get_cipher(self.ssl()), - cc.b2s(cc.SSL_session_reused(self.ssl()), "reused", "non-reused"), + cc.b2s(cc.SSL_session_reused(self.ssl()), "resume", "full"), }); } } @@ -659,7 +659,7 @@ const TcpCtx = struct { .msg_iovlen = iov.len, }; g.evloop.sendmsg(fdobj, &msg, 0) orelse break :e null; // async - } else if (has_ssl) { + } else if (has_tls) { for (iov) |*v| { while (true) { if (!self.fdobj_ok(fdobj)) return null; @@ -695,7 +695,7 @@ const TcpCtx = struct { error.eof => return null, error.other => break :e null, }; - } else if (has_ssl) { + } else if (has_tls) { var nread: usize = 0; while (nread < buf.len) { if (!self.fdobj_ok(fdobj)) return null; @@ -703,7 +703,7 @@ const TcpCtx = struct { var err: c_int = undefined; const n = cc.SSL_read(self.ssl(), buf[nread..], &err) orelse switch (err) { c.SSL_ERROR_ZERO_RETURN => { - self.ssl_info.on_eof(); + self.tls.on_eof(); return null; }, c.SSL_ERROR_WANT_READ => { @@ -713,7 +713,7 @@ const TcpCtx = struct { else => { // SSL_OP_IGNORE_UNEXPECTED_EOF (wolfssl does not support this) if (err == c.SSL_ERROR_SYSCALL and cc.errno() == 0) { - self.ssl_info.on_eof(); + self.tls.on_eof(); return null; } break :e if (err == c.SSL_ERROR_SYSCALL) null else cc.SSL_error_name(err); @@ -760,7 +760,7 @@ pub const Proto = enum { /// "tcp://" pub fn from_str(str: []const u8) ?Proto { - const map = if (has_ssl) .{ + const map = if (has_tls) .{ .{ .str = "tcp://", .proto = .tcp }, .{ .str = "udp://", .proto = .udp }, .{ .str = "tls://", .proto = .tls }, @@ -924,9 +924,6 @@ pub const Group = struct { const ptr = self.list.addOne(g.allocator) catch unreachable; ptr.* = Upstream.init(tag, proto, &addr, host, ip, port); - - if (has_ssl and proto == .tls) - SSL_INFO.init_ctx(); } pub fn rm_useless(self: *Group) void { diff --git a/src/cc.zig b/src/cc.zig index f5dabe6..f65d21f 100644 --- a/src/cc.zig +++ b/src/cc.zig @@ -791,7 +791,7 @@ pub fn SSL_error_name(err: c_int) ConstStr { c.SSL_ERROR_WANT_ACCEPT => "SSL_ERROR_WANT_ACCEPT", c.SSL_ERROR_WANT_X509_LOOKUP => "SSL_ERROR_WANT_X509_LOOKUP", c.SSL_ERROR_SSL => "SSL_ERROR_SSL", - else => snprintf(static_buf(30), "SSL_ERROR_UNKNOWN(%d)", .{err}).ptr, + else => snprintf(static_buf(30), "SSL_ERROR(%d)", .{err}).ptr, }; } diff --git a/src/groups.zig b/src/groups.zig index 5d6337d..6251838 100644 --- a/src/groups.zig +++ b/src/groups.zig @@ -120,6 +120,7 @@ pub fn on_start() void { const err: struct { tag: Tag, msg: cc.ConstStr } = e: { var tag_to_filenames = [_]?dnl.filenames_t{null} ** (c.TAG__MAX + 1); + var has_tls_upstream = false; for (_tag_to_group.items) |*group_, tag_v| { const group: *Group = group_; @@ -135,8 +136,10 @@ pub fn on_start() void { if (group.upstream_group.is_empty()) break :e .{ .tag = tag, .msg = "upstream_group is empty" }; - for (group.upstream_group.items()) |*upstream| + for (group.upstream_group.items()) |*upstream| { log.info(src, "tag:%s upstream: %s", .{ tag.name(), upstream.url }); + has_tls_upstream = has_tls_upstream or upstream.proto == .tls; + } if (!group.ipset_name46.is_empty()) { const name46 = group.ipset_name46.cstr(); @@ -145,6 +148,9 @@ pub fn on_start() void { } } + if (Upstream.has_tls and has_tls_upstream) + Upstream.TLS.init(); + dnl.init(&tag_to_filenames); // check for registered but not used tags diff --git a/src/wolfssl_opt.h b/src/wolfssl_opt.h index c195b41..bbcabb5 100644 --- a/src/wolfssl_opt.h +++ b/src/wolfssl_opt.h @@ -2,7 +2,7 @@ #define NO_WOLFSSL_SERVER 1 #define WOLFSSL_NO_ATOMICS 1 -#define WOLFSSL_AEAD_ONLY 1 +#define WOLFSSL_AEAD_ONLY #define LARGE_STATIC_BUFFERS 1 #define STATIC_CHUNKS_ONLY 1 From 813478dfe98fdb175446e03714a683900876b5f8 Mon Sep 17 00:00:00 2001 From: zfl9 Date: Fri, 26 Apr 2024 15:16:10 +0800 Subject: [PATCH 14/32] `--group null`: filter queries --- src/cc.zig | 2 +- src/dnl.c | 2 +- src/groups.zig | 13 ++++++++++--- src/opt.zig | 8 ++++---- src/server.zig | 20 ++++++++++++++++---- src/tag.zig | 4 ++++ 6 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/cc.zig b/src/cc.zig index f65d21f..d2cd040 100644 --- a/src/cc.zig +++ b/src/cc.zig @@ -184,7 +184,7 @@ pub inline fn strslice(str: anytype) StrSlice(@TypeOf(str), false) { const S = @TypeOf(str); if (comptime isManyItemPtr(S)) { comptime assert(meta.sentinel(S).? == 0); - return std.mem.sliceTo(str, 0); + return str[0..c.strlen(str) :0]; } return str; } diff --git a/src/dnl.c b/src/dnl.c index 5eaa995..a6476b1 100644 --- a/src/dnl.c +++ b/src/dnl.c @@ -499,7 +499,7 @@ void dnl_init(const filenames_t tag_to_filenames[TAG__MAX + 1], bool gfwlist_fir if (tag_to_count[tag] > 0) { u32 added = add_list(tag_to_addr0[tag], tag_to_count[tag]); total_added += added; - log_info("%slist loaded:%lu added:%lu cost:%.3fk", + log_info("%s_list loaded:%lu added:%lu cost:%.3fk", tag_to_name(tag), (ulong)tag_to_count[tag], (ulong)added, tag_to_cost[tag]/1024.0); } } diff --git a/src/groups.zig b/src/groups.zig index 6251838..0f509dd 100644 --- a/src/groups.zig +++ b/src/groups.zig @@ -98,6 +98,9 @@ pub noinline fn add_dnl(tag: Tag, filenames: []const u8) ?void { /// for opt.zig pub noinline fn add_upstream(tag: Tag, upstreams: []const u8) ?void { + if (tag.is_null()) + return null; + const upstream_group = &get_or_add(tag).upstream_group; var it = std.mem.split(u8, upstreams, ","); @@ -106,10 +109,11 @@ pub noinline fn add_upstream(tag: Tag, upstreams: []const u8) ?void { } /// for opt.zig -pub noinline fn set_ipset(tag: Tag, name46: []const u8) void { - const ipset_name46 = &get_or_add(tag).ipset_name46; +pub noinline fn set_ipset(tag: Tag, name46: []const u8) ?void { + if (tag.is_null()) + return null; - ipset_name46.set(name46); + get_or_add(tag).ipset_name46.set(name46); } // ======================================================== @@ -131,6 +135,9 @@ pub fn on_start() void { else if (tag != .chn and tag != .gfw and tag != g.default_tag) break :e .{ .tag = tag, .msg = "dnl_filenames is empty" }; + if (tag.is_null()) + continue; + group.upstream_group.rm_useless(); if (group.upstream_group.is_empty()) diff --git a/src/opt.zig b/src/opt.zig index e3e9b3e..68c9470 100644 --- a/src/opt.zig +++ b/src/opt.zig @@ -343,11 +343,11 @@ fn opt_default_tag(in_value: ?[]const u8) void { fn opt_add_tagchn_ip(in_value: ?[]const u8) void { // empty string means 'no_value' - groups.set_ipset(.chn, in_value orelse ""); + groups.set_ipset(.chn, in_value orelse "").?; } fn opt_add_taggfw_ip(in_value: ?[]const u8) void { - groups.set_ipset(.gfw, in_value.?); + groups.set_ipset(.gfw, in_value.?).?; } fn opt_ipset_name4(in_value: ?[]const u8) void { @@ -396,7 +396,7 @@ fn opt_group_ipset(in_value: ?[]const u8) void { const src = @src(); check_group_context(src, value); - groups.set_ipset(_tag, value); + groups.set_ipset(_tag, value) orelse invalid_optvalue(src, value); } fn opt_no_ipv6(in_value: ?[]const u8) void { @@ -592,7 +592,7 @@ const Parser = struct { const argv = std.os.argv; return if (self.idx < argv.len) - std.mem.sliceTo(argv[self.idx], 0) + cc.strslice_c(argv[self.idx]) else null; } diff --git a/src/server.zig b/src/server.zig index c73730e..87309b8 100644 --- a/src/server.zig +++ b/src/server.zig @@ -320,11 +320,16 @@ const QueryLog = struct { ); } - pub noinline fn filter(self: *const QueryLog) void { + pub noinline fn filter(self: *const QueryLog, rule: enum { tag_null, qtype }) void { + var buf: [20]u8 = undefined; + const rule_str: cc.ConstStr = switch (rule) { + .tag_null => "tag:null", + .qtype => cc.snprintf(&buf, "qtype:%u", .{cc.to_uint(self.qtype)}).ptr, + }; log.info( @src(), - "query(id:%u, tag:%s, qtype:%u, '%s') filtered by rule: qtype_%u", - .{ cc.to_uint(self.id), self.tag.name(), cc.to_uint(self.qtype), self.name, cc.to_uint(self.qtype) }, + "query(id:%u, tag:%s, qtype:%u, '%s') filtered by rule: %s", + .{ cc.to_uint(self.id), self.tag.name(), cc.to_uint(self.qtype), self.name, rule_str }, ); } @@ -411,6 +416,13 @@ fn on_query(qmsg: *RcMsg, fdobj: *EvLoop.Fd, src_addr: *const cc.SockAddr, in_qf else dns.get_bufsz(msg, qnamelen); + // tag:null filter + if (tag.is_null()) { + if (g.verbose()) qlog.filter(.tag_null); + const rmsg = dns.empty_reply(msg, qnamelen); + return send_reply(rmsg, fdobj, src_addr, bufsz, id, qflags); + } + // AAAA filter if (qtype == c.DNS_TYPE_AAAA) if (g.noaaaa_rule.by_tag(tag)) |rule| { @@ -421,7 +433,7 @@ fn on_query(qmsg: *RcMsg, fdobj: *EvLoop.Fd, src_addr: *const cc.SockAddr, in_qf // qtype filter if (std.mem.indexOfScalar(u16, g.filter_qtypes, qtype) != null) { - if (g.verbose()) qlog.filter(); + if (g.verbose()) qlog.filter(.qtype); const rmsg = dns.empty_reply(msg, qnamelen); return send_reply(rmsg, fdobj, src_addr, bufsz, id, qflags); } diff --git a/src/tag.zig b/src/tag.zig index 2c06c3f..35a778a 100644 --- a/src/tag.zig +++ b/src/tag.zig @@ -24,6 +24,10 @@ pub const Tag = enum(u8) { return c.tag_is_valid(tag.int()); } + pub inline fn is_null(tag: Tag) bool { + return cc.memeql(cc.strslice_c(tag.name()), "null"); + } + pub inline fn from_int(v: u8) Tag { return @intToEnum(Tag, v); } From 7a37fa5f692322c4b04d3a9a30b85f610e7f570c Mon Sep 17 00:00:00 2001 From: zfl9 Date: Fri, 26 Apr 2024 15:23:32 +0800 Subject: [PATCH 15/32] `--group null`: filter queries --- src/dnl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dnl.c b/src/dnl.c index a6476b1..430792b 100644 --- a/src/dnl.c +++ b/src/dnl.c @@ -499,7 +499,7 @@ void dnl_init(const filenames_t tag_to_filenames[TAG__MAX + 1], bool gfwlist_fir if (tag_to_count[tag] > 0) { u32 added = add_list(tag_to_addr0[tag], tag_to_count[tag]); total_added += added; - log_info("%s_list loaded:%lu added:%lu cost:%.3fk", + log_info("tag:%s loaded:%lu added:%lu cost:%.3fk", tag_to_name(tag), (ulong)tag_to_count[tag], (ulong)added, tag_to_cost[tag]/1024.0); } } From 27af0c49d3f9be358f0d5a53fb40364b1d56ca91 Mon Sep 17 00:00:00 2001 From: zfl9 Date: Fri, 26 Apr 2024 15:53:39 +0800 Subject: [PATCH 16/32] log level adjustment --- src/Upstream.zig | 12 +++--------- src/dnl.c | 2 +- src/ipset.c | 4 ++-- src/server.zig | 18 +++++++++--------- 4 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/Upstream.zig b/src/Upstream.zig index cffe6d3..c2958b3 100644 --- a/src/Upstream.zig +++ b/src/Upstream.zig @@ -148,7 +148,7 @@ fn udp_send(self: *Upstream, qmsg: *RcMsg) void { } // error handling - log.err(@src(), "send_query(%d, '%s') failed: (%d) %m", .{ fd, self.url, cc.errno() }); + log.warn(@src(), "send(%s) failed: (%d) %m", .{ self.url, cc.errno() }); } fn udp_recv(self: *Upstream, fd: c_int) void { @@ -176,7 +176,7 @@ fn udp_recv(self: *Upstream, fd: c_int) void { const rlen = while (!self.udp_is_eol(fdobj)) { break cc.recv(fd, rmsg.buf(), 0) orelse { if (cc.errno() != c.EAGAIN) { - log.err(@src(), "recv(%d, '%s') failed: (%d) %m", .{ fd, self.url, cc.errno() }); + log.warn(@src(), "recv(%s) failed: (%d) %m", .{ self.url, cc.errno() }); return; } g.evloop.wait_readable(fdobj); @@ -200,12 +200,6 @@ fn udp_on_eol(self: *Upstream) void { assert(fdobj.write_frame == null); - // log.debug( - // @src(), - // "udp upstream socket(fd:%d, url:'%s', group:%s) is end-of-life ...", - // .{ fdobj.fd, self.url, @tagName(self.group.tag).ptr }, - // ); - if (fdobj.read_frame) |frame| { co.do_resume(frame); } else { @@ -563,7 +557,7 @@ const TcpCtx = struct { // check the len rlen = cc.ntohs(rlen); if (rlen < c.DNS_MSG_MINSIZE) { - log.warn(@src(), "read_len(%d, '%s') failed: invalid len:%u", .{ fdobj.fd, self.upstream.url, cc.to_uint(rlen) }); + log.warn(@src(), "recv(%s) failed: invalid len:%u", .{ self.upstream.url, cc.to_uint(rlen) }); return; } diff --git a/src/dnl.c b/src/dnl.c index 430792b..6088fed 100644 --- a/src/dnl.c +++ b/src/dnl.c @@ -395,7 +395,7 @@ static bool load_list(u8 tag, filenames_t filenames, } else { fp = fopen(fname, "rb"); unlikely_if (!fp) { - log_error("failed to open '%s': (%d) %m", fname, errno); + log_warning("failed to open '%s': (%d) %m", fname, errno); continue; } } diff --git a/src/ipset.c b/src/ipset.c index fbcabf4..c1f19e2 100644 --- a/src/ipset.c +++ b/src/ipset.c @@ -654,7 +654,7 @@ static int send_req(int n_msg) { int n_sent = sendall(SENDMMSG, s_sock, s_msgv, n_msg, 0); assert(n_sent != 0); unlikely_if (n_sent != n_msg) - log_error("failed to send nlmsg: %d != %d, (%d) %m", n_sent, n_msg, errno); + log_warning("failed to send nlmsg: %d != %d, (%d) %m", n_sent, n_msg, errno); return n_sent; } @@ -668,7 +668,7 @@ static int recv_res(int n_msg, bool err_if_nomsg) { if (errno == EAGAIN || errno == EWOULDBLOCK) n_recv = 0; unlikely_if (err_if_nomsg || n_recv < 0) - log_error("failed to recv nlmsg: (%d) %m", errno); + log_warning("failed to recv nlmsg: (%d) %m", errno); } return n_recv; } diff --git a/src/server.zig b/src/server.zig index 87309b8..ad681c4 100644 --- a/src/server.zig +++ b/src/server.zig @@ -126,7 +126,7 @@ const QueryCtx = struct { first_query: *bool, ) ?*QueryCtx { if (self.len() >= std.math.maxInt(u16) + 1) { - log.err(@src(), "too many pending requests: %zu", .{self.len()}); + log.warn(@src(), "too many pending requests: %zu", .{self.len()}); return null; } @@ -182,7 +182,7 @@ fn listen_tcp(fd: c_int, ip: cc.ConstStr) void { while (true) { var src_addr: cc.SockAddr = undefined; const conn_fd = g.evloop.accept(fdobj, &src_addr) orelse { - log.err(@src(), "accept(fd:%d, %s#%u) failed: (%d) %m", .{ fd, ip, cc.to_uint(g.bind_port), cc.errno() }); + log.warn(@src(), "accept(fd:%d, %s#%u) failed: (%d) %m", .{ fd, ip, cc.to_uint(g.bind_port), cc.errno() }); continue; }; net.setup_tcp_conn_sock(conn_fd); @@ -222,7 +222,7 @@ fn service_tcp(fd: c_int, p_src_addr: *const cc.SockAddr) void { len = cc.ntohs(len); if (len < c.DNS_MSG_MINSIZE or len > c.DNS_QMSG_MAXSIZE) { - log.err(src, "invalid query_msg length: %u", .{cc.to_uint(len)}); + log.warn(src, "invalid query_msg length: %u", .{cc.to_uint(len)}); break :e .{ .op = "read_len", .msg = "invalid query_msg length" }; } @@ -252,9 +252,9 @@ fn service_tcp(fd: c_int, p_src_addr: *const cc.SockAddr) void { if (!g.verbose()) src_addr.to_text(&ip, &port); if (e.msg) |msg| - log.err(src, "%s(fd:%d, %s#%u) failed: %s", .{ e.op, fd, &ip, cc.to_uint(port), msg }) + log.warn(src, "%s(fd:%d, %s#%u) failed: %s", .{ e.op, fd, &ip, cc.to_uint(port), msg }) else - log.err(src, "%s(fd:%d, %s#%u) failed: (%d) %m", .{ e.op, fd, &ip, cc.to_uint(port), cc.errno() }); + log.warn(src, "%s(fd:%d, %s#%u) failed: (%d) %m", .{ e.op, fd, &ip, cc.to_uint(port), cc.errno() }); } fn listen_udp(fd: c_int, bind_ip: cc.ConstStr) void { @@ -279,7 +279,7 @@ fn listen_udp(fd: c_int, bind_ip: cc.ConstStr) void { var src_addr: cc.SockAddr = undefined; const len = g.evloop.recvfrom(fdobj, qmsg.buf(), 0, &src_addr) orelse { - log.err(@src(), "recvfrom(fd:%d, %s#%u) failed: (%d) %m", .{ fd, bind_ip, cc.to_uint(g.bind_port), cc.errno() }); + log.warn(@src(), "recvfrom(fd:%d, %s#%u) failed: (%d) %m", .{ fd, bind_ip, cc.to_uint(g.bind_port), cc.errno() }); continue; }; qmsg.len = cc.to_u16(len); @@ -388,7 +388,7 @@ fn on_query(qmsg: *RcMsg, fdobj: *EvLoop.Fd, src_addr: *const cc.SockAddr, in_qf var qnamelen: c_int = undefined; if (!dns.check_query(msg, p_ascii_namebuf, &qnamelen)) { - log.err(@src(), "dns.check_query(fd:%d) failed: invalid query msg", .{fdobj.fd}); + log.warn(@src(), "dns.check_query(fd:%d) failed: invalid query msg", .{fdobj.fd}); return; } @@ -644,7 +644,7 @@ pub fn on_reply(rmsg: *RcMsg, upstream: *const Upstream) void { var newlen: u16 = undefined; if (!dns.check_reply(msg, p_ascii_namebuf, &qnamelen, &newlen)) { - log.err(@src(), "dns.check_reply(upstream:%s) failed: invalid reply msg", .{upstream.url}); + log.warn(@src(), "dns.check_reply(upstream:%s) failed: invalid reply msg", .{upstream.url}); return; } @@ -811,7 +811,7 @@ fn send_reply(msg: []const u8, fdobj: *EvLoop.Fd, src_addr: *const cc.SockAddr, var port: u16 = undefined; src_addr.to_text(&ip, &port); - log.err( + log.warn( @src(), "reply(id:%u, size:%zu) to %s://%s#%u failed: (%d) %m", .{ cc.to_uint(dns.get_id(msg)), msg.len, proto, &ip, cc.to_uint(port), cc.errno() }, From f81035c6ad6aa36b5ad644937a8b51392697b3b3 Mon Sep 17 00:00:00 2001 From: zfl9 Date: Fri, 26 Apr 2024 16:00:30 +0800 Subject: [PATCH 17/32] `--group null`: filter queries --- src/server.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.zig b/src/server.zig index ad681c4..fb9d7f9 100644 --- a/src/server.zig +++ b/src/server.zig @@ -568,7 +568,7 @@ const ReplyLog = struct { /// string literal fn tag_name(self: *const ReplyLog) cc.ConstStr { - return if (self.tag) |tag| tag.name() else "null"; + return if (self.tag) |tag| tag.name() else "(null)"; } pub noinline fn reply(self: *const ReplyLog, action: cc.ConstStr, alt_url: ?cc.ConstStr) void { From f55b7f2e6442f8cc081275d7773bbd586510038b Mon Sep 17 00:00:00 2001 From: zfl9 Date: Fri, 26 Apr 2024 17:24:52 +0800 Subject: [PATCH 18/32] add the commit_id to the version info --- build.zig | 42 ++++++++++++++++++++++++++++++------------ src/opt.zig | 2 +- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/build.zig b/build.zig index 4e66ffc..3ab6421 100644 --- a/build.zig +++ b/build.zig @@ -97,6 +97,7 @@ fn init(b: *Builder) void { _build_opts.addOption(bool, "enable_wolfssl", _enable_wolfssl); _build_opts.addOption(bool, "enable_mimalloc", _enable_mimalloc); _build_opts.addOption([]const u8, "version", chinadns_version); + _build_opts.addOption([]const u8, "commit_id", get_commit_id()); _build_opts.addOption([]const u8, "wolfssl_version", _dep_wolfssl.version); _build_opts.addOption([]const u8, "mimalloc_version", _dep_mimalloc.version); _build_opts.addOption([]const u8, "target", desc_target()); @@ -234,30 +235,32 @@ fn add_tar_extract(tarball_path: []const u8, to_dir: []const u8) *Step { var _first_error: bool = true; /// print to stderr, auto append '\n' -fn _print(comptime format: []const u8, args: anytype) void { +fn print(comptime format: []const u8, args: anytype) void { _ = std.io.getStdErr().write(fmt(format ++ "\n", args)) catch unreachable; } fn newline() void { - return _print("", .{}); + return print("", .{}); } -fn _print_err(comptime format: []const u8, args: anytype) void { +/// print("> ERROR: msg") +fn print_err(comptime format: []const u8, args: anytype) void { if (_first_error) { _first_error = false; newline(); } - _print("> ERROR: " ++ format, args); + print("> ERROR: " ++ format, args); } -/// print err msg and mark user input as invalid +/// print("> ERROR: msg") && mark user_input as invalid fn err_invalid(comptime format: []const u8, args: anytype) void { - _print_err(format, args); + print_err(format, args); _b.invalid_user_input = true; } +/// print("> ERROR: msg") && std.os.exit(1) fn err_exit(comptime format: []const u8, args: anytype) noreturn { - _print_err(format, args); + print_err(format, args); newline(); std.os.exit(1); } @@ -276,6 +279,7 @@ fn path_exists(rel_path: []const u8) bool { return if (std.fs.cwd().access(rel_path, .{})) true else |_| false; } +/// caller owns the returned memory `_b.allocator.free(mem)` fn string_concat(str_list: []const []const u8, sep: []const u8) []const u8 { return std.mem.join(_b.allocator, sep, str_list) catch unreachable; } @@ -305,6 +309,18 @@ fn get_optval_cpu() ?[]const u8 { return get_optval("cpu"); } +/// caller owns the returned stdout `_b.allocator.free(mem)` +fn exec_command(argv: []const []const u8, exit_code: ?*u8) Builder.ExecError![]u8 { + var code: u8 = undefined; + const p_code = exit_code orelse &code; + return _b.execAllowFail(argv, p_code, .Inherit) catch |err| { + const cmd = string_concat(argv, " "); + defer _b.allocator.free(cmd); + print_err("failed to execute: {s} ({s} exit_code:{d})", .{ cmd, @errorName(err), p_code.* }); + return err; + }; +} + // ========================================================================= const ModeOpt = enum { fast, small, safe, debug }; @@ -327,7 +343,7 @@ fn to_mode_opt(mode: BuildMode) ModeOpt { }; } -/// caller owns the returned stdout (_b.allocator.free(mem)) +/// caller owns the returned stdout `_b.allocator.free(mem)` fn show_builtin() []const u8 { var argv = std.ArrayList([]const u8).init(_b.allocator); defer argv.deinit(); @@ -342,10 +358,7 @@ fn show_builtin() []const u8 { argv.append("--show-builtin") catch unreachable; - var code: u8 = undefined; - return _b.execAllowFail(argv.items, &code, .Inherit) catch |err| { - err_exit("failed to show builtin for the given target: {s} (exit-code: {d})", .{ @errorName(err), code }); - }; + return exec_command(argv.items, null) catch unreachable; } fn get_glibc_version() std.builtin.Version { @@ -437,6 +450,11 @@ fn get_target_mcpu() []const u8 { fmt("-target {s}", .{target}); } +fn get_commit_id() []const u8 { + const str = exec_command(&.{ "git", "rev-parse", "--short", "HEAD" }, null) catch "unknown"; + return trim_whitespace(str); +} + fn gen_modules_zig() void { var f = std.fs.cwd().createFile("src/modules.zig", .{}) catch unreachable; defer f.close(); diff --git a/src/opt.zig b/src/opt.zig index 68c9470..01f0cf8 100644 --- a/src/opt.zig +++ b/src/opt.zig @@ -72,7 +72,7 @@ const version: cc.ConstStr = b: { if (!std.mem.startsWith(u8, build_opts.cpu, cpu_model)) @compileError("cpu-model mismatch: " ++ cpu_model ++ " != " ++ build_opts.cpu); - var prefix: [:0]const u8 = "ChinaDNS-NG " ++ build_opts.version; + var prefix: [:0]const u8 = "ChinaDNS-NG " ++ build_opts.version ++ " " ++ build_opts.commit_id; if (build_opts.enable_wolfssl) prefix = prefix ++ " | wolfssl-" ++ build_opts.wolfssl_version; From 407cbbfab805ea95863c450481ea08dd2bcedd0a Mon Sep 17 00:00:00 2001 From: zfl9 Date: Fri, 26 Apr 2024 18:37:19 +0800 Subject: [PATCH 19/32] add blacklist for `add ip` --- src/ipset.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/ipset.c b/src/ipset.c index c1f19e2..5a5996d 100644 --- a/src/ipset.c +++ b/src/ipset.c @@ -737,10 +737,23 @@ static void add_ip_nftset(const struct ipset_addctx *noalias ctx, bool v4, const } } +static bool in_blacklist(const ubyte *noalias ip, bool v4) { + if (v4) { + if (ip[0] == 127) return true; + if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0) return true; + } else { + const ubyte zeros[15] = {0}; + if (memcmp(ip, zeros, 15) == 0 && (ip[15] == 0 || ip[15] == 1)) return true; + } + return false; +} + void ipset_add_ip(struct ipset_addctx *noalias ctx, const void *noalias ip, bool v4) { struct nlmsghdr *msg = a_msg(ctx, v4); if (!msg) return; + if (in_blacklist(ip, v4)) return; + int n = a_ipcnt(ctx, v4); if (n <= 0) msg->nlmsg_len = a_baselen(ctx, v4); From 7161c4dabc8a26c6624baf6bf14297a2259d6399 Mon Sep 17 00:00:00 2001 From: zfl9 Date: Fri, 26 Apr 2024 22:41:52 +0800 Subject: [PATCH 20/32] --no-ipset-blacklist --- src/ipset.c | 8 ++++-- src/ipset.h | 2 ++ src/opt.zig | 79 +++++++++++++++++++++++++++++------------------------ 3 files changed, 50 insertions(+), 39 deletions(-) diff --git a/src/ipset.c b/src/ipset.c index 5a5996d..bfe2514 100644 --- a/src/ipset.c +++ b/src/ipset.c @@ -227,6 +227,9 @@ static const char *ipset_strerror(int errcode) { /* ====================================================== */ +/* add ip */ +bool ipset_blacklist = true; + static int s_sock = -1; /* netlink socket fd */ static u32 s_portid = 0; /* local address (port-id) */ @@ -739,8 +742,7 @@ static void add_ip_nftset(const struct ipset_addctx *noalias ctx, bool v4, const static bool in_blacklist(const ubyte *noalias ip, bool v4) { if (v4) { - if (ip[0] == 127) return true; - if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0) return true; + if (ip[0] == 127 || ip[0] == 0) return true; } else { const ubyte zeros[15] = {0}; if (memcmp(ip, zeros, 15) == 0 && (ip[15] == 0 || ip[15] == 1)) return true; @@ -752,7 +754,7 @@ void ipset_add_ip(struct ipset_addctx *noalias ctx, const void *noalias ip, bool struct nlmsghdr *msg = a_msg(ctx, v4); if (!msg) return; - if (in_blacklist(ip, v4)) return; + if (ipset_blacklist && in_blacklist(ip, v4)) return; int n = a_ipcnt(ctx, v4); if (n <= 0) diff --git a/src/ipset.h b/src/ipset.h index 8582dab..7d8c005 100644 --- a/src/ipset.h +++ b/src/ipset.h @@ -3,6 +3,8 @@ #include "misc.h" #include +extern bool ipset_blacklist; + struct ipset_testctx; struct ipset_addctx; diff --git a/src/opt.zig b/src/opt.zig index 01f0cf8..3a055d8 100644 --- a/src/opt.zig +++ b/src/opt.zig @@ -47,6 +47,8 @@ const help = \\ --hosts [path] load hosts file, default path is /etc/hosts \\ --dns-rr-ip = define local resource records of type A/AAAA \\ --ca-certs CA certs path for SSL certificate validation + \\ --no-ipset-blacklist add-ip: don't enable built-in ip blacklist + \\ blacklist: 127.0.0.0/8, 0.0.0.0/8, ::1, :: \\ -o, --timeout-sec response timeout of upstream, default: 5 \\ -p, --repeat-times num of packets to trustdns, default:1, max:5 \\ -n, --noip-as-chnip allow no-ip reply from chinadns (tag:none) @@ -109,42 +111,43 @@ const OptFn = std.meta.FnPtr(fn (in_value: ?[]const u8) void); // zig fmt: off const optdef_array = [_]OptDef{ - .{ .short = "C", .long = "config", .value = .required, .optfn = opt_config, }, - .{ .short = "b", .long = "bind-addr", .value = .required, .optfn = opt_bind_addr, }, - .{ .short = "l", .long = "bind-port", .value = .required, .optfn = opt_bind_port, }, - .{ .short = "c", .long = "china-dns", .value = .required, .optfn = opt_china_dns, }, - .{ .short = "t", .long = "trust-dns", .value = .required, .optfn = opt_trust_dns, }, - .{ .short = "m", .long = "chnlist-file", .value = .required, .optfn = opt_chnlist_file, }, - .{ .short = "g", .long = "gfwlist-file", .value = .required, .optfn = opt_gfwlist_file, }, - .{ .short = "M", .long = "chnlist-first", .value = .no_value, .optfn = opt_chnlist_first, }, - .{ .short = "d", .long = "default-tag", .value = .required, .optfn = opt_default_tag, }, - .{ .short = "a", .long = "add-tagchn-ip", .value = .optional, .optfn = opt_add_tagchn_ip, }, - .{ .short = "A", .long = "add-taggfw-ip", .value = .required, .optfn = opt_add_taggfw_ip, }, - .{ .short = "4", .long = "ipset-name4", .value = .required, .optfn = opt_ipset_name4, }, - .{ .short = "6", .long = "ipset-name6", .value = .required, .optfn = opt_ipset_name6, }, - .{ .short = "", .long = "group", .value = .required, .optfn = opt_group, }, - .{ .short = "", .long = "group-dnl", .value = .required, .optfn = opt_group_dnl, }, - .{ .short = "", .long = "group-upstream", .value = .required, .optfn = opt_group_upstream, }, - .{ .short = "", .long = "group-ipset", .value = .required, .optfn = opt_group_ipset, }, - .{ .short = "N", .long = "no-ipv6", .value = .optional, .optfn = opt_no_ipv6, }, - .{ .short = "", .long = "filter-qtype", .value = .required, .optfn = opt_filter_qtype, }, - .{ .short = "", .long = "cache", .value = .required, .optfn = opt_cache, }, - .{ .short = "", .long = "cache-stale", .value = .required, .optfn = opt_cache_stale, }, - .{ .short = "", .long = "cache-refresh", .value = .required, .optfn = opt_cache_refresh, }, - .{ .short = "", .long = "cache-nodata-ttl", .value = .required, .optfn = opt_cache_nodata_ttl, }, - .{ .short = "", .long = "cache-ignore", .value = .required, .optfn = opt_cache_ignore, }, - .{ .short = "", .long = "verdict-cache", .value = .required, .optfn = opt_verdict_cache, }, - .{ .short = "", .long = "hosts", .value = .optional, .optfn = opt_hosts, }, - .{ .short = "", .long = "dns-rr-ip", .value = .required, .optfn = opt_dns_rr_ip, }, - .{ .short = "", .long = "ca-certs", .value = .required, .optfn = opt_ca_certs, }, - .{ .short = "o", .long = "timeout-sec", .value = .required, .optfn = opt_timeout_sec, }, - .{ .short = "p", .long = "repeat-times", .value = .required, .optfn = opt_repeat_times, }, - .{ .short = "n", .long = "noip-as-chnip", .value = .no_value, .optfn = opt_noip_as_chnip, }, - .{ .short = "f", .long = "fair-mode", .value = .no_value, .optfn = opt_fair_mode, }, - .{ .short = "r", .long = "reuse-port", .value = .no_value, .optfn = opt_reuse_port, }, - .{ .short = "v", .long = "verbose", .value = .no_value, .optfn = opt_verbose, }, - .{ .short = "V", .long = "version", .value = .no_value, .optfn = opt_version, }, - .{ .short = "h", .long = "help", .value = .no_value, .optfn = opt_help, }, + .{ .short = "C", .long = "config", .value = .required, .optfn = opt_config, }, + .{ .short = "b", .long = "bind-addr", .value = .required, .optfn = opt_bind_addr, }, + .{ .short = "l", .long = "bind-port", .value = .required, .optfn = opt_bind_port, }, + .{ .short = "c", .long = "china-dns", .value = .required, .optfn = opt_china_dns, }, + .{ .short = "t", .long = "trust-dns", .value = .required, .optfn = opt_trust_dns, }, + .{ .short = "m", .long = "chnlist-file", .value = .required, .optfn = opt_chnlist_file, }, + .{ .short = "g", .long = "gfwlist-file", .value = .required, .optfn = opt_gfwlist_file, }, + .{ .short = "M", .long = "chnlist-first", .value = .no_value, .optfn = opt_chnlist_first, }, + .{ .short = "d", .long = "default-tag", .value = .required, .optfn = opt_default_tag, }, + .{ .short = "a", .long = "add-tagchn-ip", .value = .optional, .optfn = opt_add_tagchn_ip, }, + .{ .short = "A", .long = "add-taggfw-ip", .value = .required, .optfn = opt_add_taggfw_ip, }, + .{ .short = "4", .long = "ipset-name4", .value = .required, .optfn = opt_ipset_name4, }, + .{ .short = "6", .long = "ipset-name6", .value = .required, .optfn = opt_ipset_name6, }, + .{ .short = "", .long = "group", .value = .required, .optfn = opt_group, }, + .{ .short = "", .long = "group-dnl", .value = .required, .optfn = opt_group_dnl, }, + .{ .short = "", .long = "group-upstream", .value = .required, .optfn = opt_group_upstream, }, + .{ .short = "", .long = "group-ipset", .value = .required, .optfn = opt_group_ipset, }, + .{ .short = "N", .long = "no-ipv6", .value = .optional, .optfn = opt_no_ipv6, }, + .{ .short = "", .long = "filter-qtype", .value = .required, .optfn = opt_filter_qtype, }, + .{ .short = "", .long = "cache", .value = .required, .optfn = opt_cache, }, + .{ .short = "", .long = "cache-stale", .value = .required, .optfn = opt_cache_stale, }, + .{ .short = "", .long = "cache-refresh", .value = .required, .optfn = opt_cache_refresh, }, + .{ .short = "", .long = "cache-nodata-ttl", .value = .required, .optfn = opt_cache_nodata_ttl, }, + .{ .short = "", .long = "cache-ignore", .value = .required, .optfn = opt_cache_ignore, }, + .{ .short = "", .long = "verdict-cache", .value = .required, .optfn = opt_verdict_cache, }, + .{ .short = "", .long = "hosts", .value = .optional, .optfn = opt_hosts, }, + .{ .short = "", .long = "dns-rr-ip", .value = .required, .optfn = opt_dns_rr_ip, }, + .{ .short = "", .long = "ca-certs", .value = .required, .optfn = opt_ca_certs, }, + .{ .short = "", .long = "no-ipset-blacklist", .value = .no_value, .optfn = opt_no_ipset_blacklist, }, + .{ .short = "o", .long = "timeout-sec", .value = .required, .optfn = opt_timeout_sec, }, + .{ .short = "p", .long = "repeat-times", .value = .required, .optfn = opt_repeat_times, }, + .{ .short = "n", .long = "noip-as-chnip", .value = .no_value, .optfn = opt_noip_as_chnip, }, + .{ .short = "f", .long = "fair-mode", .value = .no_value, .optfn = opt_fair_mode, }, + .{ .short = "r", .long = "reuse-port", .value = .no_value, .optfn = opt_reuse_port, }, + .{ .short = "v", .long = "verbose", .value = .no_value, .optfn = opt_verbose, }, + .{ .short = "V", .long = "version", .value = .no_value, .optfn = opt_version, }, + .{ .short = "h", .long = "help", .value = .no_value, .optfn = opt_help, }, }; // zig fmt: on @@ -498,6 +501,10 @@ fn opt_ca_certs(in_value: ?[]const u8) void { g.ca_certs.set(in_value.?); } +fn opt_no_ipset_blacklist(_: ?[]const u8) void { + c.ipset_blacklist = false; +} + fn opt_timeout_sec(in_value: ?[]const u8) void { const value = in_value.?; g.upstream_timeout = str2int.parse(@TypeOf(g.upstream_timeout), value, 10) orelse 0; From 60feb2b51d334a22840d492f73dc253bd294713a Mon Sep 17 00:00:00 2001 From: zfl9 Date: Sat, 27 Apr 2024 10:25:43 +0800 Subject: [PATCH 21/32] TcpCtx => TCP --- src/Upstream.zig | 56 ++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/Upstream.zig b/src/Upstream.zig index c2958b3..b8be75e 100644 --- a/src/Upstream.zig +++ b/src/Upstream.zig @@ -246,7 +246,7 @@ pub const TLS = struct { cc.SSL_ERR_clear(); } - pub fn new_ssl(self: *tls_t, fd: c_int, host: cc.ConstStr) ?void { + pub fn new_ssl(self: *TLS, fd: c_int, host: cc.ConstStr) ?void { assert(self.ssl == null); const ssl = cc.SSL_new(_ctx.?); @@ -275,12 +275,12 @@ pub const TLS = struct { } /// on EOF - pub fn on_eof(self: *tls_t) void { + pub fn on_eof(self: *TLS) void { return cc.SSL_set_shutdown(self.ssl.?); } // free the ssl obj - pub fn on_close(self: *tls_t) void { + pub fn on_close(self: *TLS) void { const ssl = self.ssl orelse return; self.ssl = null; @@ -302,9 +302,7 @@ pub const TLS = struct { } }; -const tls_t = if (has_tls) TLS else struct {}; - -const TcpCtx = struct { +const TCP = struct { upstream: *const Upstream, fdobj: ?*EvLoop.Fd = null, send_list: MsgQueue = .{}, // qmsg to be sent @@ -313,6 +311,8 @@ const TcpCtx = struct { healthy: bool = false, // current connection processed at least one query ? tls: tls_t = .{}, // for DoT upstream + const tls_t = if (has_tls) TLS else struct {}; + /// must <= u16_max const PENDING_MAX = 1000; @@ -407,8 +407,8 @@ const TcpCtx = struct { } }; - pub fn new(upstream: *const Upstream) *TcpCtx { - const self = g.allocator.create(TcpCtx) catch unreachable; + pub fn new(upstream: *const Upstream) *TCP { + const self = g.allocator.create(TCP) catch unreachable; self.* = .{ .upstream = upstream, }; @@ -416,7 +416,7 @@ const TcpCtx = struct { } /// [tcp_send] add to send queue, `qmsg.ref++` - pub fn push_qmsg(self: *TcpCtx, qmsg: *RcMsg) void { + pub fn push_qmsg(self: *TCP, qmsg: *RcMsg) void { if (self.pending_n >= PENDING_MAX) { log.warn(@src(), "too many pending queries: %u", .{cc.to_uint(self.pending_n)}); return; @@ -431,7 +431,7 @@ const TcpCtx = struct { /// [async] used to send qmsg to upstream /// pop from send_list && add to ack_list - fn pop_qmsg(self: *TcpCtx, fdobj: *EvLoop.Fd) ?*RcMsg { + fn pop_qmsg(self: *TCP, fdobj: *EvLoop.Fd) ?*RcMsg { if (!self.fdobj_ok(fdobj)) return null; const qmsg = self.send_list.pop(true) orelse return null; @@ -443,13 +443,13 @@ const TcpCtx = struct { } /// add qmsg to ack_list - fn on_sending(self: *TcpCtx, qmsg: *RcMsg) void { + fn on_sending(self: *TCP, qmsg: *RcMsg) void { const qid = dns.get_id(qmsg.msg()); self.ack_list.putNoClobber(g.allocator, qid, qmsg) catch unreachable; } /// remove qmsg from ack_list && qmsg.unref() - fn on_reply(self: *TcpCtx, rmsg: *const RcMsg) void { + fn on_reply(self: *TCP, rmsg: *const RcMsg) void { const qid = dns.get_id(rmsg.msg()); if (self.ack_list.fetchRemove(qid)) |kv| { self.healthy = true; @@ -462,7 +462,7 @@ const TcpCtx = struct { } /// connection closed by peer - fn on_close(self: *TcpCtx) void { + fn on_close(self: *TCP) void { self.fdobj = null; if (has_tls) self.tls.on_close(); @@ -478,7 +478,7 @@ const TcpCtx = struct { } } - fn clear_ack_list(self: *TcpCtx, op: enum { resend, unref }) void { + fn clear_ack_list(self: *TCP, op: enum { resend, unref }) void { var it = self.ack_list.valueIterator(); while (it.next()) |value_ptr| { const qmsg = value_ptr.*; @@ -491,22 +491,22 @@ const TcpCtx = struct { } /// check if disconnected or reconnected - fn fdobj_ok(self: *const TcpCtx, fdobj: *const EvLoop.Fd) bool { + fn fdobj_ok(self: *const TCP, fdobj: *const EvLoop.Fd) bool { return fdobj == self.fdobj; } - fn start(self: *TcpCtx) void { + fn start(self: *TCP) void { assert(self.fdobj == null); assert(self.pending_n > 0); assert(!self.send_list.is_empty()); assert(self.ack_list.count() == 0); self.healthy = false; - co.create(TcpCtx.send, .{self}); + co.create(TCP.send, .{self}); } - fn send(self: *TcpCtx) void { - defer co.terminate(@frame(), @frameSize(TcpCtx.send)); + fn send(self: *TCP) void { + defer co.terminate(@frame(), @frameSize(TCP.send)); const fd = net.new_tcp_conn_sock(self.upstream.addr.family()) orelse return self.on_close(); const fdobj = EvLoop.Fd.new(fd); @@ -536,7 +536,7 @@ const TcpCtx = struct { } } - fn recv(self: *TcpCtx) void { + fn recv(self: *TCP) void { defer co.terminate(@frame(), @frameSize(recv)); const fdobj = self.fdobj.?.ref(); @@ -582,7 +582,7 @@ const TcpCtx = struct { } /// `errmsg`: null means strerror(errno) - noinline fn on_error(self: *const TcpCtx, op: cc.ConstStr, errmsg: ?cc.ConstStr) ?void { + noinline fn on_error(self: *const TCP, op: cc.ConstStr, errmsg: ?cc.ConstStr) ?void { const src = @src(); if (errmsg) |msg| @@ -596,11 +596,11 @@ const TcpCtx = struct { return null; } - fn ssl(self: *const TcpCtx) *c.SSL { + fn ssl(self: *const TCP) *c.SSL { return self.tls.ssl.?; } - fn do_connect(self: *TcpCtx, fdobj: *EvLoop.Fd) ?void { + fn do_connect(self: *TCP, fdobj: *EvLoop.Fd) ?void { // null means strerror(errno) const errmsg: ?cc.ConstStr = e: { g.evloop.connect(fdobj, &self.upstream.addr) orelse break :e null; @@ -642,7 +642,7 @@ const TcpCtx = struct { return self.on_error("connect", errmsg); } - fn do_send(self: *TcpCtx, fdobj: *EvLoop.Fd, iov: []cc.iovec_t) ?void { + fn do_send(self: *TCP, fdobj: *EvLoop.Fd, iov: []cc.iovec_t) ?void { // null means strerror(errno) const errmsg: ?cc.ConstStr = e: { if (!self.fdobj_ok(fdobj)) return null; @@ -679,7 +679,7 @@ const TcpCtx = struct { return self.on_error("send", errmsg); } - fn do_recv(self: *TcpCtx, fdobj: *EvLoop.Fd, buf: []u8, flags: c_int) ?void { + fn do_recv(self: *TCP, fdobj: *EvLoop.Fd, buf: []u8, flags: c_int) ?void { // null means strerror(errno) const errmsg: ?cc.ConstStr = e: { if (!self.fdobj_ok(fdobj)) return null; @@ -724,11 +724,11 @@ const TcpCtx = struct { } }; -fn tcp_ctx(self: *Upstream) *TcpCtx { +fn tcp_ctx(self: *Upstream) *TCP { assert(self.proto == .tcpi or self.proto == .tcp or self.proto == .tls); if (self.ctx == null) - self.ctx = TcpCtx.new(self); - return cc.ptrcast(*TcpCtx, self.ctx.?); + self.ctx = TCP.new(self); + return cc.ptrcast(*TCP, self.ctx.?); } fn tcp_send(self: *Upstream, qmsg: *RcMsg) void { From 926f2dfff5a307b538183550df90f81bfe8c7c28 Mon Sep 17 00:00:00 2001 From: zfl9 Date: Sat, 27 Apr 2024 11:58:58 +0800 Subject: [PATCH 22/32] review --- src/Upstream.zig | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Upstream.zig b/src/Upstream.zig index b8be75e..2ec7f0d 100644 --- a/src/Upstream.zig +++ b/src/Upstream.zig @@ -287,9 +287,6 @@ pub const TLS = struct { // the session should be free after it has been set into an ssl object assert(self.session == null); - // close the session - // cc.SSL_set_shutdown(ssl); - // check if the session is resumable if (cc.SSL_get1_session(ssl)) |session| { if (cc.SSL_SESSION_is_resumable(session)) @@ -697,6 +694,7 @@ const TCP = struct { var err: c_int = undefined; const n = cc.SSL_read(self.ssl(), buf[nread..], &err) orelse switch (err) { c.SSL_ERROR_ZERO_RETURN => { + cc.SSL_ERR_clear(); self.tls.on_eof(); return null; }, @@ -707,6 +705,7 @@ const TCP = struct { else => { // SSL_OP_IGNORE_UNEXPECTED_EOF (wolfssl does not support this) if (err == c.SSL_ERROR_SYSCALL and cc.errno() == 0) { + cc.SSL_ERR_clear(); self.tls.on_eof(); return null; } From c643c0d7b457b7d430c5a08050809920d34b05fc Mon Sep 17 00:00:00 2001 From: zfl9 Date: Sat, 27 Apr 2024 16:19:33 +0800 Subject: [PATCH 23/32] review --- src/Upstream.zig | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Upstream.zig b/src/Upstream.zig index 2ec7f0d..0c2c506 100644 --- a/src/Upstream.zig +++ b/src/Upstream.zig @@ -274,8 +274,7 @@ pub const TLS = struct { self.ssl = ssl; } - /// on EOF - pub fn on_eof(self: *TLS) void { + pub fn on_eof(self: *const TLS) void { return cc.SSL_set_shutdown(self.ssl.?); } @@ -385,7 +384,7 @@ const TCP = struct { } } - pub fn cancel_wait(self: *MsgQueue) void { + pub fn cancel_wait(self: *const MsgQueue) void { if (self.waiter) |waiter| { assert(self.is_empty()); _pushed_msg = null; @@ -603,7 +602,7 @@ const TCP = struct { g.evloop.connect(fdobj, &self.upstream.addr) orelse break :e null; if (has_tls and self.upstream.proto == .tls) { - self.tls.new_ssl(fdobj.fd, self.upstream.host.?) orelse break :e "ubable to create ssl object"; + self.tls.new_ssl(fdobj.fd, self.upstream.host.?) orelse break :e "unable to create ssl object"; while (true) { var err: c_int = undefined; From aebb68b96af1a0a6c92e8229a431a1be2e7529ed Mon Sep 17 00:00:00 2001 From: zfl9 Date: Sat, 27 Apr 2024 18:22:28 +0800 Subject: [PATCH 24/32] update wolfssl config opt --- build.zig | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/build.zig b/build.zig index 3ab6421..789c1e1 100644 --- a/build.zig +++ b/build.zig @@ -533,6 +533,9 @@ fn build_wolfssl() *Step { \\ zig_cache_dir='{s}' \\ is_musl='{s}' \\ lto='{s}' + \\ aesni='{s}' + \\ intelasm='{s}' + \\ armasm='{s}' \\ cwd="$PWD" \\ \\ cd "$src_dir" @@ -551,10 +554,17 @@ fn build_wolfssl() *Step { \\ ./autogen.sh \\ ./configure \ \\ $host \ + \\ $aesni \ + \\ $intelasm \ + \\ $armasm \ \\ --prefix="$install_dir" \ \\ --enable-static \ \\ --disable-shared \ + \\ --enable-asm \ \\ --disable-harden \ + \\ --disable-ocsp \ + \\ --disable-oldnames \ + \\ --disable-sys-ca-certs \ \\ --enable-staticmemory \ \\ --enable-singlethreaded \ \\ --disable-threadlocal \ @@ -573,6 +583,18 @@ fn build_wolfssl() *Step { const str_musl: [:0]const u8 = if (is_musl()) "1" else "0"; const str_lto: [:0]const u8 = if (_lto) "-flto" else ""; + const str_aesni: [:0]const u8 = switch (_target.getCpuArch()) { + .x86_64 => "--enable-aesni", + else => "", + }; + const str_intelasm: [:0]const u8 = switch (_target.getCpuArch()) { + .x86_64 => "--enable-intelasm", + else => "", + }; + const str_armasm: [:0]const u8 = switch (_target.getCpuArch()) { + .aarch64 => "--enable-armasm", + else => "", + }; const cmd = fmt(cmd_, .{ _b.pathFromRoot(_dep_wolfssl.base_dir), @@ -583,6 +605,9 @@ fn build_wolfssl() *Step { _b.pathFromRoot(_b.cache_root), str_musl, str_lto, + str_aesni, + str_intelasm, + str_armasm, }); wolfssl.dependOn(add_sh_cmd_x(cmd)); From bed4dfb8ea137a46df2881817fd6aac0930ff4e1 Mon Sep 17 00:00:00 2001 From: zfl9 Date: Sat, 27 Apr 2024 19:07:45 +0800 Subject: [PATCH 25/32] update wolfssl config opt --- build.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/build.zig b/build.zig index 789c1e1..7767b14 100644 --- a/build.zig +++ b/build.zig @@ -576,6 +576,7 @@ fn build_wolfssl() *Step { \\ --enable-chacha --enable-poly1305 \ \\ --enable-aesgcm --disable-aescbc \ \\ --enable-ecc --enable-sni --enable-session-ticket \ + \\ --disable-sha224 --disable-sha3 --disable-base64encode \ \\ --disable-crypttests --disable-benchmark --disable-examples \ \\ EXTRA_CFLAGS="-include $cwd/src/wolfssl_opt.h" \\ make install From 073ffee8363e0ecfc55a0e5262e3dcaf9a01111e Mon Sep 17 00:00:00 2001 From: zfl9 Date: Sat, 27 Apr 2024 19:57:14 +0800 Subject: [PATCH 26/32] review --- src/Upstream.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Upstream.zig b/src/Upstream.zig index 0c2c506..a2ca1aa 100644 --- a/src/Upstream.zig +++ b/src/Upstream.zig @@ -217,9 +217,13 @@ pub const TLS = struct { var _ctx: ?*c.SSL_CTX = null; + /// called at startup pub fn init() void { if (_ctx != null) return; + // library init + assert(c.wolfSSL_Init() == c.SSL_SUCCESS); + const ctx = cc.SSL_CTX_new(); _ctx = ctx; From 2782053c00495d6c615707c7cac1a842950c6b0a Mon Sep 17 00:00:00 2001 From: zfl9 Date: Sat, 27 Apr 2024 20:05:29 +0800 Subject: [PATCH 27/32] version: 2024.04.27 --- build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 7767b14..58149bb 100644 --- a/build.zig +++ b/build.zig @@ -7,7 +7,7 @@ const Step = std.build.Step; const LibExeObjStep = std.build.LibExeObjStep; const OptionsStep = std.build.OptionsStep; -const chinadns_version = "2024.04.20"; +const chinadns_version = "2024.04.27"; var _b: *Builder = undefined; From 7d2a92a84342dc77aaf719edfd440ae37d1d4473 Mon Sep 17 00:00:00 2001 From: zfl9 Date: Sat, 27 Apr 2024 20:54:50 +0800 Subject: [PATCH 28/32] update build.zig --- build.zig | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 58149bb..779d055 100644 --- a/build.zig +++ b/build.zig @@ -153,10 +153,26 @@ fn option_strip() void { } fn option_name() void { - const default = with_target_desc(if (_test) "test" else "chinadns-ng", null); + var vec = std.ArrayList(u8).init(_b.allocator); + defer vec.deinit(); + + if (_test) + vec.appendSlice("test") catch unreachable + else + vec.appendSlice("chinadns-ng") catch unreachable; + + if (_enable_wolfssl) + vec.appendSlice("+wolfssl") catch unreachable; + + if (_enable_mimalloc) + vec.appendSlice("+mimalloc") catch unreachable; + + const default = with_target_desc(vec.items(), null); const desc = fmt("executable name, default: '{s}'", .{default}); + const name = _b.option([]const u8, "name", desc) orelse default; const trimmed = trim_whitespace(name); + if (trimmed.len > 0 and std.mem.eql(u8, trimmed, name)) { _exe_name = name; } else { From 606265ecb2576fc3aa2d8c1482a979ce08bebd5d Mon Sep 17 00:00:00 2001 From: zfl9 Date: Sat, 27 Apr 2024 20:55:36 +0800 Subject: [PATCH 29/32] update build.zig --- build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 779d055..204c89e 100644 --- a/build.zig +++ b/build.zig @@ -167,7 +167,7 @@ fn option_name() void { if (_enable_mimalloc) vec.appendSlice("+mimalloc") catch unreachable; - const default = with_target_desc(vec.items(), null); + const default = with_target_desc(vec.items, null); const desc = fmt("executable name, default: '{s}'", .{default}); const name = _b.option([]const u8, "name", desc) orelse default; From 9977be6afcd225b22b43357fcaf4605c963330e2 Mon Sep 17 00:00:00 2001 From: zfl9 Date: Sat, 27 Apr 2024 21:03:34 +0800 Subject: [PATCH 30/32] update build.zig --- build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 204c89e..79d7e23 100644 --- a/build.zig +++ b/build.zig @@ -83,9 +83,9 @@ fn init(b: *Builder) void { option_mode(); option_lto(); option_strip(); - option_name(); option_wolfssl(); option_mimalloc(); + option_name(); // must be at the end _dep_wolfssl.base_dir = with_target_desc(_dep_wolfssl.src_dir, .ReleaseFast); // dependency lib always ReleaseFast _dep_wolfssl.include_dir = fmt("{s}/include", .{_dep_wolfssl.base_dir}); From 69145a839aa5f03e627f1440f90f82ba94d0ccd0 Mon Sep 17 00:00:00 2001 From: zfl9 Date: Sat, 27 Apr 2024 22:05:28 +0800 Subject: [PATCH 31/32] update readme --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 72f23f8..ee09098 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,9 @@ usage: chinadns-ng . the existing options are as follows: --verdict-cache enable verdict caching for tag:none domains --hosts [path] load hosts file, default path is /etc/hosts --dns-rr-ip = define local resource records of type A/AAAA + --ca-certs CA certs path for SSL certificate validation + --no-ipset-blacklist add-ip: don't enable built-in ip blacklist + blacklist: 127.0.0.0/8, 0.0.0.0/8, ::1, :: -o, --timeout-sec response timeout of upstream, default: 5 -p, --repeat-times num of packets to trustdns, default:1, max:5 -n, --noip-as-chnip allow no-ip reply from chinadns (tag:none) @@ -216,6 +219,7 @@ bug report: https://github.com/zfl9/chinadns-ng. email: zfl9.com@gmail.com (Otok - 2024.03.07 版本起,支持 UDP + TCP 上游(根据查询方的传入协议决定)。 - 2024.03.07 版本起,可在上游地址前加上 `tcp://` 来强制使用 TCP DNS。 - 2024.04.13 版本起,可在上游地址前加上 `udp://` 来强制使用 UDP DNS。 +- 2024.04.27 版本起,支持 DoT 上游,`tls://域名@IP`,端口默认为 853。 --- @@ -272,6 +276,8 @@ bug report: https://github.com/zfl9/chinadns-ng. email: zfl9.com@gmail.com (Otok - `group-dnl` 当前组的域名列表文件,多个用逗号隔开,可多次指定。 - `group-upstream` 当前组的上游 DNS,多个用逗号隔开,可多次指定。 - `group-ipset` 当前组的 ipset/nftset (可选),用于收集解析出的结果 IP。 +- 2024.04.27 版本起,使用 `null` 作为 group 名时,表示过滤该组域名的查询。 + - null 组只有 `group-dnl` 信息,查询相关域名时,将返回 NODATA 响应消息。 以配置文件举例: @@ -343,6 +349,13 @@ group-upstream 192.168.1.1 --- +- `ca-certs` 根证书路径,用于验证 DoT 上游的 SSL 证书。默认自动检测。 +- `no-ipset-blacklist` 若指定此选项,则 add-ip 时不进行内置的 IP 过滤。 + - 默认情况下,以下 IP 不会被添加到 ipset/nftset 集合,见 [#162](https://github.com/zfl9/chinadns-ng/issues/162) + - `127.0.0.0/8`、`0.0.0.0/8`、`::1`、`::` (loopback地址、全0地址) + +--- + - `timeout-sec` 用于指定上游的响应超时时长,单位秒,默认 5 秒。 - `repeat-times` 针对可信 DNS (UDP) [重复发包](#trust上游存在一定的丢包怎么缓解),默认为 1,最大为 5。 - `noip-as-chnip` 接受来自 china 上游的没有 IP 地址的响应,[详细说明](#--noip-as-chnip-选项的作用)。 @@ -480,7 +493,8 @@ chinadns-ng -c 114.114.114.114 -t '127.0.0.1#5353' ### 为什么不内置 TCP、DoH、DoT 等协议的支持 -> 2024.03.07 版本起,已内置完整的 TCP 支持(传入、传出);DoH 也许会在 2.0 中实现。 +> 2024.03.07 版本起,已内置完整的 TCP 支持(传入、传出);~~DoH 也许会在 2.0 中实现~~。 +> 2024.04.27 版本起,支持 DoT 协议的上游,DoH 不打算实现。 我想让代码保持简单,只做真正必要的事情,其他事情让专业的工具去干。 From ba78ba78e338027e0fcda4bd3f47efb7a04cfad2 Mon Sep 17 00:00:00 2001 From: zfl9 Date: Sat, 27 Apr 2024 22:15:16 +0800 Subject: [PATCH 32/32] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ee09098..bc60f91 100644 --- a/README.md +++ b/README.md @@ -276,7 +276,7 @@ bug report: https://github.com/zfl9/chinadns-ng. email: zfl9.com@gmail.com (Otok - `group-dnl` 当前组的域名列表文件,多个用逗号隔开,可多次指定。 - `group-upstream` 当前组的上游 DNS,多个用逗号隔开,可多次指定。 - `group-ipset` 当前组的 ipset/nftset (可选),用于收集解析出的结果 IP。 -- 2024.04.27 版本起,使用 `null` 作为 group 名时,表示过滤该组域名的查询。 +- 2024.04.27 版本起,使用 `null` 作为 group 名时,表示过滤该组的域名查询。 - null 组只有 `group-dnl` 信息,查询相关域名时,将返回 NODATA 响应消息。 以配置文件举例: @@ -493,7 +493,7 @@ chinadns-ng -c 114.114.114.114 -t '127.0.0.1#5353' ### 为什么不内置 TCP、DoH、DoT 等协议的支持 -> 2024.03.07 版本起,已内置完整的 TCP 支持(传入、传出);~~DoH 也许会在 2.0 中实现~~。 +> 2024.03.07 版本起,已内置完整的 TCP 支持(传入、传出)。\ > 2024.04.27 版本起,支持 DoT 协议的上游,DoH 不打算实现。 我想让代码保持简单,只做真正必要的事情,其他事情让专业的工具去干。