From dff38bef3fcd25f29a50705dd310d16cf29a5144 Mon Sep 17 00:00:00 2001 From: chn Date: Mon, 21 Apr 2025 17:30:27 +0800 Subject: [PATCH] devices.cross.wireguard: finish --- devices/cross/wireguard.nix | 194 ++++++++++++++++++++++++++++++------ 1 file changed, 163 insertions(+), 31 deletions(-) diff --git a/devices/cross/wireguard.nix b/devices/cross/wireguard.nix index 206c036d..f03221d3 100644 --- a/devices/cross/wireguard.nix +++ b/devices/cross/wireguard.nix @@ -54,32 +54,152 @@ let # 两两互连 wg1 = let + # 查询域名对应的 ip getAddress = deviceName: let dns = inputs.topInputs.self.config.dns."chn.moe"; f = domain: if dns.${domain}.type == "A" then dns.${domain}.value - else if dns.${domain}.type == "CNAME" then f (inputs.lib.removeSuffix ".chn.moe" dns.${domain}.value) + else if dns.${domain}.type == "CNAME" then f (inputs.lib.removeSuffix ".chn.moe." dns.${domain}.value) else throw "Not found ${domain}"; in f deviceName; + # 设备之间可以直接连接的子网 + # 若一个设备可以主动接受连接,则设置它接受连接的 ip;否则设置为 null + subnet = + [ + # 所有设备都可以连接到公网,但只有有公网 ip 的设备可以接受连接 + (builtins.listToAttrs + ( + (builtins.map (n: { name = n; value = getAddress n; }) [ "vps6" "vps7" "srv3" ]) + ++ (builtins.map (n: { name = n; value = null; }) [ "pc" "nas" "one" "srv1-node0" "srv2-node0" ]) + )) + # 校内网络 + (builtins.listToAttrs + ( + (builtins.map (n: { name = n; value = getAddress n; }) [ "srv1-node0" "srv2-node0" ]) + ++ (builtins.map (n: { name = n; value = null; }) [ "pc" "nas" "one" ]) + )) + # 办公室或者宿舍局域网 + (builtins.listToAttrs (builtins.map (n: { name = n; value = getAddress n; }) [ "pc" "nas" "one" ])) + # 集群内部网络 + (builtins.listToAttrs (builtins.map + (n: { name = "srv1-node${builtins.toString n}"; value = "192.168.178.${builtins.toString (n + 1)}"; }) + (builtins.genList (n: n) 3))) + (builtins.listToAttrs (builtins.map + (n: { name = "srv2-node${builtins.toString n}"; value = "192.168.178.${builtins.toString (n + 1)}"; }) + (builtins.genList (n: n) 2))) + ]; + # 给定起止点,返回最短路径的第一跳的目的地 + # 如果两个设备不能连接,返回 null; + # 如果可以直接、主动连接,返回 { ip = 地址; };如果可以直接连接但是被动连接,返回 { ip = null; }; + # 如果需要中转,返回 { jump = 下一跳; } connection = - # 这个表用来表示,从某一个设备出发,可以主动直连到哪个设备,并且通过这个直连的设备,应该可以抵达哪些设备 - # 被动连接的设备不需要写 - { - vps6.vps7.ip = getAddress "vps7"; - vps7.vps6.ip = getAddress "vps6"; - pc = - { - vps6.ip = getAddress "vps6"; - vps7.ip = getAddress "vps7"; - nas.ip = getAddress "nas"; - one.ip = getAddress "one"; - srv1-node0 = { ip = getAddress "srv1-node0"; }; - }; - }; - in let listenIps = - let office = "210.34.16.60"; - in { "srv1-node0" = "59.77.36.250"; "srv2-node0" = office; pc = office; nas = office; }; + let + # 将给定子网翻译成一列边,返回 [{ dev1 = null or ip; dev2 = null or ip; }] + netToEdges = subnet: + let devWithAddress = builtins.filter (n: subnet.${n} != null) (builtins.attrNames subnet); + in inputs.lib.unique (builtins.concatLists (builtins.map + (dev1: builtins.map + (dev2: { "${dev1}" = subnet."${dev1}"; "${dev2}" = subnet."${dev2}"; }) + (inputs.lib.remove dev1 (builtins.attrNames subnet))) + devWithAddress)); + # 在一个图中加入一个边,current 的结构是:from.to = null or { ip = "" or null; length = l; jump = ""; } + addEdge = current: newEdge: builtins.mapAttrs + (nameFrom: valueFrom: builtins.mapAttrs + (nameTo: valueTo: + # 忽略自己到自己的路 + if nameFrom == nameTo then null + # 如果要加入的边包含起点 + else if newEdge ? "${nameFrom}" then + # 如果要加入的边包含终点,那么这两个点可以直连 + if newEdge ? "${nameTo}" then { ip = newEdge.${nameTo}; length = 1; } + else let edgePoint2 = builtins.head (inputs.lib.remove nameFrom (builtins.attrNames newEdge)); in + # 如果边的另外一个点到终点可以连接 + if current.${edgePoint2}.${nameTo} != null then + # 如果之前不能连接,则使用新的连接 + if current.${nameFrom}.${nameTo} == null then + { jump = edgePoint2; length = 1 + current.${edgePoint2}.${nameTo}.length; } + # 如果之前可以连接,且新连接更短,同样更新连接 + else if current.${nameFrom}.${nameTo}.length > 1 + current.${edgePoint2}.${nameTo}.length then + { jump = edgePoint2; length = 1 + current.${edgePoint2}.${nameTo}.length; } + # 否则,不更新连接 + else current.${nameFrom}.${nameTo} + # 否则,不更新连接 + else current.${nameFrom}.${nameTo} + # 如果要加入的边包不包含起点但包含终点 + else if newEdge ? "${nameTo}" then + let edgePoint2 = builtins.head (inputs.lib.remove nameTo (builtins.attrNames newEdge)); in + # 如果起点与另外一个点可以相连 + if current.${nameFrom}.${edgePoint2} != null then + # 如果之前不能连接,则使用新的连接 + if current.${nameFrom}.${nameTo} == null then + { + jump = current.${nameFrom}.${edgePoint2}.jump or edgePoint2; + length = current.${nameFrom}.${edgePoint2}.length + 1; + } + # 如果之前可以连接,且新连接更短,同样更新连接 + else if current.${nameFrom}.${nameTo}.length > current.${nameFrom}.${edgePoint2}.length + 1 then + { + jump = current.${nameFrom}.${edgePoint2}.jump or edgePoint2; + length = current.${nameFrom}.${edgePoint2}.length + 1; + } + # 否则,不更新连接 + else current.${nameFrom}.${nameTo} + # 如果起点与另外一个点不可以相连,则不改变连接 + else current.${nameFrom}.${nameTo} + # 如果要加入的边不包含起点和终点 + else + let + edgePoints = builtins.attrNames newEdge; + p1 = builtins.elemAt edgePoints 0; + p2 = builtins.elemAt edgePoints 1; + in + # 如果起点与边的第一个点可以连接、终点与边的第二个点可以连接 + if current.${nameFrom}.${p1} != null && current.${p2}.${nameTo} != null then + # 如果之前不能连接,则新连接必然是唯一的连接,使用新连接 + if current.${nameFrom}.${nameTo} == null then + { + jump = current.${nameFrom}.${p1}.jump or p1; + length = current.${nameFrom}.${p1}.length + 1 + current.${p2}.${nameTo}.length; + } + # 如果之前可以连接,那么反过来一定也能连接,选取三种连接中最短的 + else builtins.head (inputs.lib.sort + (a: b: if a == null then false else if b == null then true else a.length < b.length) + [ + # 原先的连接 + current.${nameFrom}.${nameTo} + # 正着连接 + { + jump = current.${nameFrom}.${p1}.jump or p1; + length = current.${nameFrom}.${p1}.length + 1 + current.${p2}.${nameTo}.length; + } + # 反着连接 + { + jump = current.${nameFrom}.${p2}.jump or p2; + length = current.${nameFrom}.${p2}.length + 1 + current.${p1}.${nameTo}.length; + } + ]) + # 如果正着不能连接、反过来可以连接,那么反过来连接一定是唯一的通路,使用反向的连接 + else if current.${nameFrom}.${p2} != null && current.${p1}.${nameTo} != null then + { + jump = current.${nameFrom}.${p2}.jump or p2; + length = current.${nameFrom}.${p2}.length + 1 + current.${p1}.${nameTo}.length; + } + # 如果正着连接、反向连接都不行,那么就不更新连接 + else current.${nameFrom}.${nameTo}) + valueFrom) + current; + # 初始时,所有点之间都不连接 + init = builtins.listToAttrs (builtins.map + (dev1: + { + name = dev1; + value = builtins.listToAttrs (builtins.map + (dev2: { name = dev2; value = null; }) + (builtins.attrNames publicKey)); + }) + (builtins.attrNames publicKey)); + in builtins.foldl' addEdge init (builtins.concatLists (builtins.map netToEdges subnet)); in { devices = builtins.listToAttrs (builtins.map @@ -89,22 +209,34 @@ let value = { listenPort = 51820 + dns.peer.${deviceName}; - peer = builtins.listToAttrs (builtins.map + peer = builtins.listToAttrs (builtins.concatLists (builtins.map (peerName: - { - name = peerName; - value = - { - publicKey = publicKey.${peerName}; - endpoint = "${listenIps.${peerName}}:${builtins.toString (51820 + dns.peer.${peerName})}"; - allowedIPs = - [ "192.168.${builtins.toString dns.net.wg1}.${builtins.toString dns.peer.${peerName}}" ]; - }; - }) - (inputs.lib.remove deviceName (builtins.attrNames listenIps))); + # 如果不能直连,就不用加 peer + inputs.lib.optionals (connection.${deviceName}.${peerName} ? ip) + [{ + name = peerName; + value = + { + publicKey = publicKey.${peerName}; + allowedIPs = + [ "192.168.${builtins.toString dns.net.wg1}.${builtins.toString dns.peer.${peerName}}" ] + ++ builtins.map + (destination: + "192.168.${builtins.toString dns.net.wg1}.${builtins.toString dns.peer.${destination}}") + (builtins.filter + (destination: connection.${deviceName}.${destination}.jump or null == peerName) + (builtins.attrNames publicKey)); + } + // inputs.lib.optionalAttrs (connection.${deviceName}.${peerName}.ip != null) + { + endpoint = "${connection.${deviceName}.${peerName}.ip}:" + + builtins.toString (51820 + dns.peer.${peerName}); + }; + }]) + (inputs.lib.remove deviceName (builtins.attrNames publicKey)))); }; }) - (builtins.attrNames listenIps)); + (builtins.attrNames publicKey)); }; }; in