├── .gitmodules ├── configuration ├── network │ ├── hosts.nix │ ├── interface │ │ ├── tor │ │ │ └── default.nix │ │ ├── wan │ │ │ ├── ppp │ │ │ │ ├── pppd.nix │ │ │ │ └── default.nix │ │ │ ├── dhcp │ │ │ │ └── default.nix │ │ │ └── qos.nix │ │ ├── manage │ │ │ └── default.nix │ │ ├── security │ │ │ └── default.nix │ │ ├── default.nix │ │ ├── wireguard │ │ │ └── default.nix │ │ ├── br-lan │ │ │ └── default.nix │ │ ├── cu │ │ │ ├── qos.nix │ │ │ └── default.nix │ │ └── vlan.nix │ ├── dnsmasq.nix │ ├── nftables.nix │ └── mosdns.nix ├── features │ ├── telemetry.nix │ └── network │ │ ├── miniupnpd.nix │ │ ├── tor.nix │ │ ├── tailscale.nix │ │ ├── ddns.nix │ │ └── dae.nix ├── user.nix ├── resource.nix └── default.nix ├── .sops.yaml ├── packages ├── v2dat │ └── default.nix └── mosdns │ └── default.nix ├── modules └── mosdns │ └── default.nix ├── vector.yaml ├── flake.nix ├── secrets.yaml ├── README.md └── flake.lock /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /configuration/network/hosts.nix: -------------------------------------------------------------------------------- 1 | _: 2 | { 3 | networking.hosts = { 4 | "10.0.0.10" = [ "nuc9" ]; 5 | "10.0.0.11" = [ "pve2" ]; 6 | }; 7 | } -------------------------------------------------------------------------------- /configuration/features/telemetry.nix: -------------------------------------------------------------------------------- 1 | _: { 2 | services.prometheus.exporters = { 3 | dnsmasq = { 4 | enable = true; 5 | leasesPath = "/var/lib/dnsmasq/dnsmasq.leases"; 6 | }; 7 | }; 8 | } -------------------------------------------------------------------------------- /configuration/features/network/miniupnpd.nix: -------------------------------------------------------------------------------- 1 | { config, ... }: 2 | { 3 | # TODO: 安全问题? 毕竟现在inbound全部允许 4 | services.miniupnpd = { 5 | enable = true; 6 | natpmp = true; 7 | externalInterface = config.network.interface.world; 8 | internalIPs = [ config.network.interface.private.lan ]; 9 | }; 10 | } -------------------------------------------------------------------------------- /configuration/user.nix: -------------------------------------------------------------------------------- 1 | _: 2 | { 3 | users = { 4 | # Don't allow mutation of users outside of the config. 5 | mutableUsers = false; 6 | # Privilege User 7 | users.root.openssh.authorizedKeys.keys = [ "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBC5HypvbsI4xvwfd4Uw7D+SV0AevYPS/nCarFwfBwrMHKybbqUJV1cLM1ySZPxXcZD7+3m48Riiwlssh6o7WM/M= openpgp:0xDE4C24F6" ]; 8 | }; 9 | } -------------------------------------------------------------------------------- /configuration/features/network/tor.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | { 3 | services.tor = { 4 | enable = true; 5 | client = { 6 | enable = true; 7 | dns.enable = true; 8 | # transparentProxy.enable = true; 9 | }; 10 | # settings.TransProxyType = "TPROXY"; 11 | }; 12 | 13 | # systemd.services.tor.serviceConfig = { 14 | # AmbientCapabilities = [ "CAP_NET_ADMIN" "CAP_NET_RAW" ]; 15 | # CapabilityBoundingSet = [ "CAP_NET_ADMIN" "CAP_NET_RAW" ]; 16 | # NoNewPrivileges = lib.mkForce false; 17 | # }; 18 | } -------------------------------------------------------------------------------- /.sops.yaml: -------------------------------------------------------------------------------- 1 | # This example uses YAML anchors which allows reuse of multiple keys 2 | # without having to repeat yourself. 3 | # Also see https://github.com/Mic92/dotfiles/blob/master/nixos/.sops.yaml 4 | # for a more complex example. 5 | keys: 6 | - &admin_lostattractor 57EDCBEDDE7ABA6B44377FF2B96725F36430B3D1 7 | - &server_router age1228fl3wyx640gz9uv3x8zrv3sk0yt5aftc63fpqgqwrnlnry4drqdysp84 8 | creation_rules: 9 | - path_regex: secrets.yaml 10 | key_groups: 11 | - pgp: 12 | - *admin_lostattractor 13 | age: 14 | - *server_router -------------------------------------------------------------------------------- /configuration/features/network/tailscale.nix: -------------------------------------------------------------------------------- 1 | { network, config, ... }: 2 | { 3 | services.tailscale = { 4 | enable = true; 5 | useRoutingFeatures = "both"; 6 | authKeyFile = config.sops.secrets.tailscale.path; 7 | extraUpFlags = [ 8 | "--ssh" 9 | "--advertise-exit-node" 10 | "--accept-routes" 11 | "--advertise-routes=10.0.0.0/16,10.1.0.0/16,10.10.0.0/16,10.100.0.0/16,10.255.0.0/16,fd7a:115c:a1e0:b1a:0:1:a00:0/104,fd23:3333:3333::/48" 12 | ]; 13 | }; 14 | 15 | sops.secrets.tailscale = {}; 16 | 17 | network.interface.private.tailscale = network.interface.tailscale; 18 | } -------------------------------------------------------------------------------- /packages/v2dat/default.nix: -------------------------------------------------------------------------------- 1 | { lib 2 | , buildGoModule 3 | , fetchFromGitHub 4 | }: 5 | buildGoModule { 6 | pname = "v2dat"; 7 | version = "47b8ee5"; 8 | 9 | src = fetchFromGitHub ({ 10 | owner = "urlesistiana"; 11 | repo = "v2dat"; 12 | rev = "47b8ee51fb528e11e1a83453b7e767a18d20d1f7"; 13 | sha256 = "sha256-dJld4hYdfnpphIEJvYsj5VvEF4snLvXZ059HJ2BXwok="; 14 | }); 15 | 16 | vendorHash = "sha256-ndWasQUHt35D528PyGan6JGXh5TthpOhyJI2xBDn0zI="; 17 | 18 | meta = with lib; { 19 | description = "A cli tool that can unpack v2ray data packages."; 20 | homepage = "https://github.com/urlesistiana/v2dat"; 21 | license = licenses.gpl3; 22 | }; 23 | } -------------------------------------------------------------------------------- /configuration/network/interface/tor/default.nix: -------------------------------------------------------------------------------- 1 | { network, ... }: 2 | { 3 | systemd.network = { 4 | networks."10-${network.interface.tor}" = { 5 | name = network.interface.tor; 6 | networkConfig = { 7 | Address = [ "10.1.0.1/16" "fd23:3333:3333:1::1/64" ]; 8 | DHCPPrefixDelegation = true; # 自动选择第一个有 PD 的链路, 并获得子网前缀 9 | IPv6SendRA = true; # 会自动关闭 IPv6AcceptRA 并打开 IPv6Forwarding 10 | LLDP = true; 11 | EmitLLDP = true; 12 | }; 13 | ipv6SendRAConfig = { Managed = true; OtherInformation = true; }; 14 | dhcpPrefixDelegationConfig = { 15 | SubnetId = 1; 16 | Token = "::1"; 17 | }; 18 | }; 19 | }; 20 | 21 | network.interface.private.tor = network.interface.tor; 22 | } -------------------------------------------------------------------------------- /configuration/network/interface/wan/ppp/pppd.nix: -------------------------------------------------------------------------------- 1 | { config, network, ... }: 2 | { 3 | services.pppd = { 4 | enable = true; 5 | peers.edpnet = { 6 | enable = true; 7 | configFile = config.sops.templates."edpnet".path; 8 | }; 9 | }; 10 | 11 | sops.templates."edpnet".content = '' 12 | plugin pppoe.so ${network.interface.upstream} 13 | 14 | name "${config.sops.placeholder."network/pppoe/name"}" 15 | password "${config.sops.placeholder."network/pppoe/password"}" 16 | ifname ${network.interface.ppp} 17 | 18 | usepeerdns 19 | defaultroute # v4默认路由 20 | 21 | persist 22 | holdoff 10 23 | maxfail 0 24 | ''; 25 | 26 | sops.secrets."network/pppoe/name" = {}; 27 | sops.secrets."network/pppoe/password" = {}; 28 | } -------------------------------------------------------------------------------- /configuration/network/interface/manage/default.nix: -------------------------------------------------------------------------------- 1 | { network, ... }: 2 | { 3 | systemd.network = { 4 | networks."10-${network.interface.manage}" = { 5 | name = network.interface.manage; 6 | networkConfig = { 7 | Address = [ "10.100.0.1/16" "fd23:3333:3333:100::1/64" ]; 8 | DHCPPrefixDelegation = true; # 自动选择第一个有 PD 的链路, 并获得子网前缀 9 | IPv6SendRA = true; # 会自动关闭 IPv6AcceptRA 并打开 IPv6Forwarding 10 | LLDP = true; 11 | EmitLLDP = true; 12 | }; 13 | ipv6SendRAConfig = { Managed = true; OtherInformation = true; }; 14 | dhcpPrefixDelegationConfig = { 15 | SubnetId = 3; 16 | Token = "::1"; 17 | }; 18 | }; 19 | }; 20 | 21 | network.interface.private.manage = network.interface.manage; 22 | } -------------------------------------------------------------------------------- /configuration/network/interface/security/default.nix: -------------------------------------------------------------------------------- 1 | { network, ... }: 2 | { 3 | systemd.network = { 4 | networks."10-${network.interface.security}" = { 5 | name = network.interface.security; 6 | networkConfig = { 7 | Address = [ "10.10.0.1/16" "fd23:3333:3333:10::1/64" ]; 8 | DHCPPrefixDelegation = true; # 自动选择第一个有 PD 的链路, 并获得子网前缀 9 | IPv6SendRA = true; # 会自动关闭 IPv6AcceptRA 并打开 IPv6Forwarding 10 | LLDP = true; 11 | EmitLLDP = true; 12 | }; 13 | ipv6SendRAConfig = { Managed = true; OtherInformation = true; }; 14 | dhcpPrefixDelegationConfig = { 15 | SubnetId = 2; 16 | Token = "::1"; 17 | }; 18 | }; 19 | }; 20 | 21 | network.interface.private.security = network.interface.security; 22 | } -------------------------------------------------------------------------------- /packages/mosdns/default.nix: -------------------------------------------------------------------------------- 1 | { lib 2 | , buildGoModule 3 | , fetchFromGitHub 4 | }: 5 | buildGoModule rec { 6 | pname = "mosdns"; 7 | version = "5.3.3"; 8 | 9 | src = fetchFromGitHub { 10 | owner = "IrineSistiana"; 11 | repo = "mosdns"; 12 | rev = "v${version}"; 13 | sha256 = "sha256-nSqSfbpi91W28DaLjCsWlPiLe1gLVHeZnstktc/CLag="; 14 | }; 15 | 16 | vendorHash = "sha256-RpvWkIDhHSNbdkpBCcXYbbvbmGiG15qyB5aEJRmg9s4="; 17 | doCheck = false; 18 | 19 | buildPhase = '' 20 | buildFlagsArray=(-v -p $NIX_BUILD_CORES -ldflags="-s -w") 21 | runHook preBuild 22 | go build "''${buildFlagsArray[@]}" -o mosdns ./ 23 | runHook postBuild 24 | ''; 25 | 26 | installPhase = '' 27 | install -Dm755 mosdns -t $out/bin 28 | ''; 29 | 30 | meta = with lib; { 31 | description = "A DNS proxy server"; 32 | homepage = "https://github.com/IrineSistiana/mosdns"; 33 | license = licenses.gpl3; 34 | }; 35 | } -------------------------------------------------------------------------------- /configuration/network/interface/default.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | with lib; 3 | { 4 | # https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html 5 | # TODO: Multiple IPv6 SubnetID(Prefix) 6 | imports = [ 7 | ./vlan.nix 8 | ./wan/ppp 9 | ./cu 10 | ./br-lan 11 | ./tor 12 | ./security 13 | ./manage 14 | ./wireguard 15 | ]; 16 | 17 | config = { 18 | # Using systemd-networkd 19 | networking.useNetworkd = true; 20 | }; 21 | 22 | options.network.interface = { 23 | world = mkOption { type = types.str; }; 24 | worlds = mkOption { type = with types; listOf str; }; 25 | private = { 26 | lan = mkOption { type = types.str; }; 27 | tor = mkOption { type = types.str; }; 28 | security = mkOption { type = types.str; }; 29 | manage = mkOption { type = types.str; }; 30 | wg = mkOption { type = types.str; }; 31 | tailscale = mkOption { type = types.str; }; 32 | }; 33 | }; 34 | } -------------------------------------------------------------------------------- /configuration/network/interface/wan/dhcp/default.nix: -------------------------------------------------------------------------------- 1 | { network, pkgs, ... }: 2 | { 3 | import = [ (import ../qos.nix ({ inherit pkgs; interface = network.interface.onu; })) ]; 4 | 5 | # https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html 6 | # Example 3. IPv6 Prefix Delegation (DHCPv6 PD) 7 | systemd.network = { 8 | networks."10-${network.interface.onu}" = { 9 | name = network.interface.onu; 10 | networkConfig = { 11 | DHCP = "yes"; 12 | IPv6AcceptRA = true; # 由于 systemd 需要知道 RA 的详细信息, 因此 kernel 的 accept_ra 需要禁用 13 | }; 14 | # Add in systemd-networkd version 255 15 | dhcpV4Config.RequestAddress = "192.168.1.2"; 16 | # 允许上游提供 RA 但却没有 M Flag 时启用 DHCP-PD (不使用DHCP获得地址) 17 | dhcpV6Config.UseAddress = false; 18 | ipv6AcceptRAConfig.DHCPv6Client = "always"; 19 | }; 20 | }; 21 | 22 | network.interface.world = network.interface.onu; 23 | network.interface.worlds = [ network.interface.onu ]; 24 | } -------------------------------------------------------------------------------- /configuration/features/network/ddns.nix: -------------------------------------------------------------------------------- 1 | { pkgs, config, ... }: 2 | { 3 | systemd.services.ddns-go = { 4 | wantedBy = [ "multi-user.target" ]; 5 | description = "A simple, easy-to-use DDNS service"; 6 | serviceConfig = { 7 | ExecStart="${pkgs.ddns-go}/bin/ddns-go -c ${config.sops.templates."ddns-go.yaml".path} -noweb"; 8 | Restart = "always"; 9 | RestartSec = 120; 10 | }; 11 | unitConfig = { 12 | StartLimitIntervalSec = 5; 13 | StartLimitBurst = 10; 14 | }; 15 | }; 16 | 17 | sops.templates."ddns-go.yaml".content = '' 18 | dnsconf: 19 | - ipv6: 20 | enable: true 21 | gettype: netInterface 22 | netinterface: ${config.network.interface.world} 23 | domains: 24 | - ns.lostattractor.net 25 | dns: 26 | name: cloudflare 27 | secret: ${config.sops.placeholder."ddns-go/cloudflare/secret"} 28 | ''; 29 | 30 | sops.secrets."ddns-go/cloudflare/secret" = {}; 31 | } -------------------------------------------------------------------------------- /configuration/network/interface/wireguard/default.nix: -------------------------------------------------------------------------------- 1 | { config, network, ... }: 2 | { 3 | systemd.network = { 4 | netdevs."00-${network.interface.wg}" = { 5 | netdevConfig = { 6 | Kind = "wireguard"; 7 | Name = network.interface.wg; 8 | }; 9 | wireguardConfig = { 10 | # pubkey: vuDPg3CcJH60zEBCBwBpKdzqW7oLtZChWynTKFh6SkU= 11 | PrivateKeyFile = config.sops.secrets."network/wireguard/privkey".path; 12 | ListenPort = 51820; 13 | }; 14 | wireguardPeers = [ 15 | { 16 | PublicKey = "WLfPF6oBHGdZkq0ZkPVQSlkfnEiFU1+xtqNT7W7rRjw="; 17 | AllowedIPs = [ "10.255.0.2" ]; 18 | } 19 | { 20 | PublicKey = "RAqne2m36M6i48XDa9kBmfjGR/EsgYm6xsJte75DVhA="; 21 | AllowedIPs = [ "10.255.0.3" ]; 22 | } 23 | ]; 24 | }; 25 | networks."10-${network.interface.wg}" = { 26 | name = network.interface.wg; 27 | address = [ "10.255.0.1/24" ]; 28 | }; 29 | }; 30 | 31 | networking.firewall.allowedUDPPorts = [ 51820 ]; 32 | 33 | network.interface.private.wg = network.interface.wg; 34 | 35 | sops.secrets."network/wireguard/privkey" = { 36 | mode = "0440"; 37 | group = "systemd-network"; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /configuration/network/interface/wan/ppp/default.nix: -------------------------------------------------------------------------------- 1 | { network, pkgs, ... }: 2 | { 3 | imports = [ 4 | ./pppd.nix 5 | (import ../qos.nix ({ inherit pkgs; interface = network.interface.ppp; })) 6 | ]; 7 | 8 | # https://github.com/JQ-Networks/NixOS/blob/a7bf792a4411971d8229eb43a3547097ab06e65b/services/ppp/default.nix#L137 9 | # https://github.com/RMTT/machines/blob/b58cddca27d81c8bed8fa44e1db4b20dceded40d/nixos/modules/services/pppoe.nix#L49 10 | systemd.network = { 11 | networks."10-${network.interface.onu}" = { # ONU上联接口 / 仅用于管理ONU 12 | name = network.interface.onu; 13 | routes = [{ Destination = "192.168.11.0/24"; }]; 14 | }; 15 | networks."10-${network.interface.ppp}" = { 16 | name = network.interface.ppp; 17 | networkConfig = { 18 | DHCP = "ipv6"; # 需要先接收到包含 M Flag 的 RA 才会尝试 DHCP-PD 19 | KeepConfiguration = "static"; # 防止清除 PPPD 通过 IPCP 获取的 IPV4 地址 20 | DefaultRouteOnDevice = true; # 设置默认路由到该接口 21 | }; 22 | dhcpV6Config = { 23 | UseAddress = false; # 使用 SLAAC 24 | UseDNS = false; # TODO: Why? 25 | }; 26 | ipv6AcceptRAConfig.DHCPv6Client = "always"; 27 | }; 28 | }; 29 | 30 | network.interface.world = network.interface.ppp; 31 | network.interface.worlds = [ network.interface.ppp ]; 32 | } -------------------------------------------------------------------------------- /configuration/network/interface/br-lan/default.nix: -------------------------------------------------------------------------------- 1 | { network, ... }: 2 | { 3 | systemd.network = { 4 | netdevs."00-${network.interface.br-lan}".netdevConfig = { 5 | Kind = "bridge"; 6 | Name = network.interface.br-lan; 7 | }; 8 | networks."10-${network.interface.lan}" = { 9 | name = network.interface.lan; 10 | networkConfig = { 11 | Bridge = network.interface.br-lan; 12 | LinkLocalAddressing = "no"; 13 | }; 14 | }; 15 | networks."10-${network.interface.direct}" = { 16 | name = network.interface.direct; 17 | networkConfig = { 18 | Bridge = network.interface.br-lan; 19 | LinkLocalAddressing = "no"; 20 | }; 21 | }; 22 | networks."10-${network.interface.br-lan}" = { 23 | name = network.interface.br-lan; 24 | networkConfig = { 25 | Address = [ "10.0.0.1/16" "fd23:3333:3333::1/64" ]; 26 | DHCPPrefixDelegation = true; # 自动选择第一个有 PD 的链路, 并获得子网前缀 27 | IPv6SendRA = true; # 会自动关闭 IPv6AcceptRA 并打开 IPv6Forwarding 28 | LLDP = true; 29 | EmitLLDP = true; 30 | }; 31 | ipv6SendRAConfig = { Managed = true; OtherInformation = true; }; 32 | dhcpPrefixDelegationConfig = { 33 | SubnetId = 0; 34 | Token = "::1"; 35 | }; 36 | }; 37 | }; 38 | 39 | network.interface.private.lan = network.interface.br-lan; 40 | } -------------------------------------------------------------------------------- /modules/mosdns/default.nix: -------------------------------------------------------------------------------- 1 | { config, lib, pkgs, ... }: 2 | with lib; 3 | let 4 | cfg = config.services.mosdns; 5 | configFormat = pkgs.formats.yaml { }; 6 | configFile = 7 | if cfg.configFile != null 8 | then cfg.configFile 9 | else configFormat.generate "mosdns.yaml" cfg.config; 10 | in 11 | { 12 | options.services.mosdns = { 13 | enable = mkEnableOption "mosdns service"; 14 | package = mkPackageOption pkgs "mosdns" { default = [ "mosdns" ]; }; 15 | configFile = mkOption { 16 | type = types.nullOr types.path; 17 | default = null; 18 | description = "The absolute path to the configuration file."; 19 | }; 20 | config = mkOption { 21 | type = configFormat.type; 22 | default = { }; 23 | description = "The configuration attribute set."; 24 | }; 25 | }; 26 | 27 | config = mkIf cfg.enable { 28 | assertions = [{ 29 | assertion = (cfg.configFile != null) -> (cfg.config == { }); 30 | message = "Either but not both `configFile` and `config` should be specified for mosdns."; 31 | }]; 32 | 33 | systemd.services.mosdns = { 34 | description = "mosdns Daemon"; 35 | after = [ "network.target" ]; 36 | wantedBy = [ "multi-user.target" ]; 37 | restartTriggers = [ configFile ]; 38 | serviceConfig = { 39 | ExecStart = "${cfg.package}/bin/mosdns start -c ${configFile}"; 40 | }; 41 | }; 42 | 43 | environment.systemPackages = [ cfg.package ]; 44 | }; 45 | } -------------------------------------------------------------------------------- /configuration/network/interface/cu/qos.nix: -------------------------------------------------------------------------------- 1 | { pkgs, interface, ... }: 2 | { 3 | # 流量必须不拥塞在ISP处, 不然一切都没有意义 4 | # 如果ISP处不存在瓶颈, 则我可以优先丢弃会导致队列膨胀的包, 并接收不导致队列膨胀的包, 而 ISP 限速时使用的丢弃方法一般是rate limit 5 | # 实际上我的接收没有瓶颈, 但丢弃保证了上游ISP的限速不会触发, 因此不导致队列膨胀的包我可以正常收到, 导致队列膨胀的包则因为丢弃降速以保证上游 ISP 的限速不触发 6 | # 因此这里使用 CAKE 并锁定速率 7 | # 但事实上瓶颈可能在光猫的千兆链路? 那其实道理也一样, 不过应该设法解决这个瓶颈 8 | systemd.network = { 9 | networks."10-${interface}" = { 10 | cakeConfig = { 11 | Bandwidth = "40M"; 12 | RTTSec = "50ms"; 13 | }; 14 | }; 15 | netdevs."00-ifb4${interface}".netdevConfig = { 16 | Kind = "ifb"; 17 | Name = "ifb4${interface}"; 18 | }; 19 | networks."10-ifb4${interface}" = { 20 | name = "ifb4${interface}"; 21 | networkConfig.LinkLocalAddressing = "no"; 22 | linkConfig.RequiredForOnline = false; 23 | cakeConfig = { 24 | Bandwidth = "1000M"; 25 | OverheadBytes = 50; 26 | RTTSec = "50ms"; 27 | }; 28 | }; 29 | }; 30 | 31 | services.networkd-dispatcher.rules."10-tc-cu" = { 32 | onState = [ "routable" ]; # or configured 33 | script = '' 34 | #!${pkgs.runtimeShell} 35 | if [[ $IFACE == "${interface}" ]] && ! ${pkgs.iproute2}/bin/tc filter show dev "${interface}" ingress | grep -q "mirred"; then 36 | # Add parent qdisc if not loaded yet 37 | if ! tc qdisc show dev ${interface} | grep -q "clsact"; then 38 | ${pkgs.iproute2}/bin/tc qdisc add dev "${interface}" clsact 39 | fi 40 | ${pkgs.iproute2}/bin/tc filter add dev ${interface} ingress matchall action mirred egress redirect dev ifb4${interface} 41 | fi 42 | ''; 43 | }; 44 | } -------------------------------------------------------------------------------- /configuration/network/interface/cu/default.nix: -------------------------------------------------------------------------------- 1 | { network, pkgs, ... }: 2 | { 3 | imports = [ (import ./qos.nix ({ inherit pkgs; interface = network.interface.cu; })) ]; 4 | 5 | systemd.network = { 6 | networks."10-${network.interface.cu}" = { 7 | name = network.interface.cu; 8 | networkConfig = { 9 | DHCP = "yes"; 10 | VRF = "vrf-cu"; 11 | }; 12 | # dhcpV4Config.UseRoutes = false; 13 | }; 14 | networks."01-vrf-cu" = { 15 | name = "vrf-cu"; 16 | routingPolicyRules = [ 17 | { 18 | Priority = 98; 19 | To = "54.64.0.0/15"; 20 | Table = 10; 21 | } 22 | { 23 | Priority = 99; 24 | FirewallMark = 10; 25 | Table = 52; 26 | } 27 | { 28 | Priority = 100; 29 | FirewallMark = 10; 30 | Table = 254; 31 | } 32 | ]; 33 | }; 34 | netdevs."00-vrf-cu" = { 35 | netdevConfig = { 36 | Kind = "vrf"; 37 | Name = "vrf-cu"; 38 | }; 39 | vrfConfig.Table = 10; 40 | }; 41 | }; 42 | 43 | # fib saddr type != local ct mark set 10? 44 | # fib only checks main table 45 | networking.nftables.tables.mangle_vrf = { 46 | family = "inet"; 47 | content = '' 48 | chain forward { 49 | type filter hook forward priority 0; policy accept; 50 | ct state new ct mark set 10 51 | } 52 | 53 | chain prerouting { 54 | type filter hook prerouting priority 0; policy accept; 55 | ct mark 10 meta mark set 10 56 | } 57 | ''; 58 | }; 59 | 60 | network.interface.worlds = [ network.interface.cu ]; 61 | } -------------------------------------------------------------------------------- /configuration/network/interface/vlan.nix: -------------------------------------------------------------------------------- 1 | { network , ... }: 2 | { 3 | # https://unix.stackexchange.com/questions/215580/untagged-interface-in-linux 4 | systemd.network = { 5 | ## Carrier 6 | networks."01-${network.interface.downstream}" = { 7 | name = network.interface.downstream; 8 | networkConfig = { 9 | LinkLocalAddressing = "no"; 10 | VLAN = with network.interface; [ lan direct tor security manage cu ]; 11 | }; 12 | linkConfig = { 13 | RequiredForOnline = false; 14 | MTUBytes = "9000"; 15 | }; 16 | }; 17 | ## VLANs 18 | # lan zone 19 | netdevs."00-${network.interface.lan}" = { 20 | netdevConfig = { 21 | Kind = "vlan"; 22 | Name = network.interface.lan; 23 | }; 24 | vlanConfig.Id = 1; 25 | }; 26 | netdevs."00-${network.interface.direct}" = { 27 | netdevConfig = { 28 | Kind = "vlan"; 29 | Name = network.interface.direct; 30 | }; 31 | vlanConfig.Id = 2; 32 | }; 33 | netdevs."00-${network.interface.tor}" = { 34 | netdevConfig = { 35 | Kind = "vlan"; 36 | Name = network.interface.tor; 37 | }; 38 | vlanConfig.Id = 3; 39 | }; 40 | # security zone 41 | netdevs."00-${network.interface.security}" = { 42 | netdevConfig = { 43 | Kind = "vlan"; 44 | Name = network.interface.security; 45 | }; 46 | vlanConfig.Id = 10; 47 | }; 48 | # manage zone 49 | netdevs."00-${network.interface.manage}" = { 50 | netdevConfig = { 51 | Kind = "vlan"; 52 | Name = network.interface.manage; 53 | }; 54 | vlanConfig.Id = 100; 55 | }; 56 | # China Unicom 57 | netdevs."00-${network.interface.cu}" = { 58 | netdevConfig = { 59 | Kind = "vlan"; 60 | Name = network.interface.cu; 61 | }; 62 | vlanConfig.Id = 4094; 63 | }; 64 | }; 65 | } -------------------------------------------------------------------------------- /vector.yaml: -------------------------------------------------------------------------------- 1 | sources: 2 | mosdns-log: 3 | type: journald 4 | include_units: 5 | - mosdns 6 | 7 | transforms: 8 | mosdns-data: 9 | type: remap 10 | inputs: 11 | - mosdns-log 12 | drop_on_error: true 13 | source: | 14 | del(.PRIORITY) 15 | del(.SYSLOG_FACILITY) 16 | del(._BOOT_ID) 17 | del(._CAP_EFFECTIVE) 18 | del(._CMDLINE) 19 | del(._COMM) 20 | del(._EXE) 21 | del(._GID) 22 | del(._MACHINE_ID) 23 | del(._PID) 24 | del(._RUNTIME_SCOPE) 25 | del(._STREAM_ID) 26 | del(._SYSTEMD_CGROUP) 27 | del(._SYSTEMD_INVOCATION_ID) 28 | del(._SYSTEMD_SLICE) 29 | del(._SYSTEMD_UNIT) 30 | del(._UID) 31 | del(.__MONOTONIC_TIMESTAMP) 32 | del(.__REALTIME_TIMESTAMP) 33 | del(.__SEQNUM) 34 | del(.__SEQNUM_ID) 35 | 36 | message_parts = split!(.message, r'\t') 37 | 38 | .timestamp = parse_timestamp!(message_parts[0], format: "%FT%T%.9fZ") 39 | .level = message_parts[1] 40 | 41 | if (length(message_parts) == 6) { 42 | .plugin = message_parts[2] 43 | .processor = message_parts[3] 44 | .message = message_parts[4] 45 | 46 | if (exists(message_parts[5])) { 47 | .metadata = parse_json!(message_parts[5]) 48 | . = merge!(., .metadata) 49 | del(.metadata) 50 | } 51 | } else { 52 | .processor = message_parts[2] 53 | .message = message_parts[3] 54 | 55 | if (exists(message_parts[4])) { 56 | .metadata = parse_json!(message_parts[4]) 57 | . = merge!(., .metadata) 58 | del(.metadata) 59 | } 60 | } 61 | 62 | if (exists(.query)) { 63 | query_parts = split!(.query, r'\s') 64 | .domain = query_parts[0] 65 | .record = query_parts[2] 66 | .address = query_parts[5] 67 | } 68 | 69 | sinks: 70 | # 同步到 loki,根据实际情况修改 endpoint 的值 71 | loki: 72 | type: loki 73 | inputs: 74 | - mosdns-data 75 | endpoint: 'http://metrics.home.lostattractor.net:3100' 76 | encoding: 77 | codec: json 78 | labels: 79 | app: '{{ SYSLOG_IDENTIFIER }}' 80 | host: '{{ host }}' 81 | healthcheck: 82 | enabled: true 83 | 84 | # 临时输出转换数据到 vector 控制台(生产环境请禁用) 85 | debug_mosdns: 86 | type: console 87 | inputs: 88 | - mosdns-data 89 | encoding: 90 | codec: json 91 | -------------------------------------------------------------------------------- /configuration/network/interface/wan/qos.nix: -------------------------------------------------------------------------------- 1 | { pkgs, interface, ... }: 2 | { 3 | # 流量必须不拥塞在ISP处, 不然一切都没有意义 4 | # 如果ISP处不存在瓶颈, 则我可以优先丢弃会导致队列膨胀的包, 并接收不导致队列膨胀的包, 而 ISP 限速时使用的丢弃方法一般是rate limit 5 | # 实际上我的接收没有瓶颈, 但丢弃保证了上游ISP的限速不会触发, 因此不导致队列膨胀的包我可以正常收到, 导致队列膨胀的包则因为丢弃降速以保证上游 ISP 的限速不触发 6 | # 因此这里使用 CAKE 并锁定速率 7 | # 但事实上瓶颈可能在光猫的千兆链路? 那其实道理也一样, 不过应该设法解决这个瓶颈 8 | systemd.network = { 9 | networks."10-${interface}" = { 10 | cakeConfig = { 11 | Bandwidth = "105M"; 12 | RTTSec = "50ms"; 13 | }; 14 | }; 15 | netdevs."00-ifb4${interface}".netdevConfig = { 16 | Kind = "ifb"; 17 | Name = "ifb4${interface}"; 18 | }; 19 | networks."10-ifb4${interface}" = { 20 | name = "ifb4${interface}"; 21 | networkConfig.LinkLocalAddressing = "no"; 22 | linkConfig.RequiredForOnline = false; 23 | cakeConfig = { 24 | Bandwidth = "1100M"; 25 | # Bandwidth = "1000M"; 26 | # OverheadBytes = 50; # 35 是典型的PON网络开销, 38 是典型的以太网开销, 当前实践可能不应低于 50 27 | RTTSec = "50ms"; 28 | }; 29 | }; 30 | }; 31 | 32 | # clsact 和 ingress 互相是排他性的 (Exclusivity) 33 | # clsact 是 ingress (和egress)? 加载 bpf 程序的前提 34 | # 它们都会有一个句柄, 对于 clsact, 它不会被使用, 因此可以省略 35 | services.networkd-dispatcher.rules."10-tc-wan" = { 36 | onState = [ "routable" ]; # or configured 37 | script = '' 38 | #!${pkgs.runtimeShell} 39 | if [[ $IFACE == "${interface}" ]] && ! ${pkgs.iproute2}/bin/tc filter show dev "${interface}" ingress | grep -q "mirred"; then 40 | # Add parent qdisc if not loaded yet 41 | if ! tc qdisc show dev ${interface} | grep -q "clsact"; then 42 | ${pkgs.iproute2}/bin/tc qdisc add dev "${interface}" clsact 43 | fi 44 | ${pkgs.iproute2}/bin/tc filter add dev ${interface} ingress matchall action mirred egress redirect dev ifb4${interface} 45 | fi 46 | ''; 47 | }; 48 | 49 | # services.networkd-dispatcher.rules."10-tc" = { 50 | # onState = [ "routable" ]; 51 | # script = '' 52 | # #!${pkgs.runtimeShell} 53 | # if [[ $IFACE == "${network.interface.upstream}" ]]; then 54 | # # ${pkgs.iproute2}/bin/tc qdisc del dev ${network.interface.upstream} ingress 55 | # ${pkgs.iproute2}/bin/tc qdisc add dev ${network.interface.upstream} handle ffff: ingress 56 | # ${pkgs.iproute2}/bin/tc filter add dev ${network.interface.upstream} parent ffff: matchall action mirred egress redirect dev ifb4${interface} 57 | # fi 58 | # ''; 59 | # }; 60 | } -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "ChaosAttractor's Router Flake"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 | deploy-rs.url = "github:serokell/deploy-rs"; 7 | deploy-rs.inputs.nixpkgs.follows = "nixpkgs"; 8 | sops-nix.url = "github:Mic92/sops-nix"; 9 | sops-nix.inputs.nixpkgs.follows = "nixpkgs"; 10 | daeuniverse.url = "github:daeuniverse/flake.nix/unstable"; 11 | daeuniverse.inputs.nixpkgs.follows = "nixpkgs"; 12 | vscode-server.url = "github:nix-community/nixos-vscode-server"; 13 | vscode-server.inputs.nixpkgs.follows = "nixpkgs"; 14 | homelab.url = "github:lostattractor/homelab"; 15 | homelab.inputs.nixpkgs.follows = "nixpkgs"; 16 | }; 17 | 18 | outputs = { self, nixpkgs, deploy-rs, ... } @ inputs : 19 | let 20 | network = { 21 | interface = rec { 22 | # Physics 23 | downstream = "enp1s0f0np0"; 24 | upstream = "enp1s0f1np1"; 25 | # Layer 2 encapsulated 26 | lan = "lan"; # VLAN 1 on downstream 27 | direct = "direct"; # VLAN 2 on downstream 28 | tor = "tor"; # VLAN 3 on downstream 29 | security = "security"; # VLAN 10 on downstream 30 | manage = "manage"; # VLAN 100 on downstream 31 | onu = upstream; # Untagged on upstream 32 | ppp = "pppoe-wan"; # PPP encap on upstream 33 | cu = "cu"; # VLAN 4094 on downstream 34 | # Bridges 35 | br-lan = "br-lan"; 36 | # SDN 37 | wg = "wg0"; # Wireguard 38 | tailscale = "tailscale0"; # Tailscale 39 | }; 40 | domain = "home.lostattractor.net"; 41 | }; 42 | in rec { 43 | # Router@NUC9.home.lostattractor.net 44 | nixosConfigurations."router" = nixpkgs.lib.nixosSystem rec { 45 | system = "x86_64-linux"; 46 | specialArgs = { inherit inputs network; }; 47 | modules = [ 48 | ./configuration 49 | (inputs.homelab + "/hardware/kvm/proxmox.nix") 50 | { networking.hostName = "router"; } 51 | inputs.sops-nix.nixosModules.sops 52 | inputs.daeuniverse.nixosModules.dae 53 | { services.dae.package = inputs.daeuniverse.packages.${system}.dae-experiment; } 54 | inputs.vscode-server.nixosModules.default 55 | ./modules/mosdns 56 | { 57 | nixpkgs.overlays = [ 58 | (final: prev: with prev; { 59 | mosdns = callPackage ./packages/mosdns { buildGoModule = pkgs.buildGo123Module; }; 60 | v2dat = callPackage ./packages/v2dat { buildGoModule = pkgs.buildGo123Module; }; 61 | }) 62 | ]; 63 | } 64 | ]; 65 | }; 66 | 67 | # Deploy-RS Configuration 68 | deploy = { 69 | sshUser = "root"; 70 | magicRollback = false; 71 | 72 | nodes."router@nuc9.home.lostattractor.net" = { 73 | hostname = "router.home.lostattractor.net"; 74 | profiles.system.path = deploy-rs.lib.x86_64-linux.activate.nixos nixosConfigurations."router"; 75 | }; 76 | }; 77 | 78 | # This is highly advised, and will prevent many possible mistakes 79 | checks = builtins.mapAttrs (_system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; 80 | 81 | hydraJobs = with nixpkgs.lib; { 82 | nixosConfigurations = mapAttrs' (name: config: 83 | nameValuePair name config.config.system.build.toplevel) 84 | nixosConfigurations; 85 | image = mapAttrs' (name: config: 86 | nameValuePair name config.config.system.build.image) 87 | nixosConfigurations; 88 | }; 89 | }; 90 | } 91 | -------------------------------------------------------------------------------- /secrets.yaml: -------------------------------------------------------------------------------- 1 | network: 2 | pppoe: 3 | name: ENC[AES256_GCM,data:7UKOvSiBuyL5A60=,iv:VCv+cx5fwVgLY6abBj7RGyRvZxGmF08yDtjvL1BWQzE=,tag:VBayi9UBKJV2HmoK6AiCiA==,type:str] 4 | password: ENC[AES256_GCM,data:gB7oGdzb,iv:xPhNSXVj2LBnTelQ8Dk6Ho/S9aszK182nGpwTflbQj8=,tag:wA7EpTclarwUV0/wRE9thQ==,type:str] 5 | wireguard: 6 | privkey: ENC[AES256_GCM,data:e2GowcrNaFdUAr864D6Q2fnk9KDhH3EQDDgxgrMyi0OCs6sZ9d+Y/USoWAM=,iv:fUAKCnMLed8h7GzzuDuyfqcu3NyKQJGrkAd8AwWsCD4=,tag:FDxerxyNHsu6z9pvLRVvKg==,type:str] 7 | ddns-go: 8 | cloudflare: 9 | secret: ENC[AES256_GCM,data:xe/pjBnVkH813RLhBQPBQDC4O3epcUfZG/U4klNHeOjewuMUs2hbug==,iv:kJuC3fjqqFn++3nbMwLU80hj2XLBX57qWMxoNfIh5ro=,tag:ILttrt/0FZ9pLKkClHjGjQ==,type:str] 10 | dae: 11 | subscription: 12 | imm: ENC[AES256_GCM,data:jLE/EO/hZj5rYb0vk9PlCGQMA5d4Hw2LrRPNAQC2V/xSlRP5Tq6utfwAnMSfLDUbxrFtIK0mYUaMQGXgaN174k6mYL5sGwV0/LW/UJaSnqadJzFdGrr/2BcFW3xkw0Z4WEi0DZbk1yB/4lg9CXfJP7f3aMxc1AVy8Dc/BEtAoTpH+8ASyLnwb1RRCh0=,iv:ks9UrICgh04BA9wFusMiJkHKOQOePBnZl2a6ZZsNiCc=,tag:k9AawfBaCfE/q+Z0uxqZgA==,type:str] 13 | cloudflared: 14 | credentials: 15 | 14818fcc-fe2d-4e3b-b69c-67cd03bcdf70: ENC[AES256_GCM,data:CRUR7Phei5SAFLebSKHqKW9IJWAsUmgPONsdtcz03P9rdSqEwhARNugbXVryExeb5UPvRhFDjtYhUHpBnWKniqZMXVKxGOvoyf8eaT2gE2EZ0xhbJAxEEDZZ8voq2IEXsuZkaa5kCsXdDR4F0pTXTfKrsVVPcMh4PBiuvvwjn4Wv4UcmsP6NSJSZUANAyH/3OcXpnUt23dzMlDptn/yjbDc=,iv:WFY+vTAG/KPXuOhlsBOUjjHpEHNvNcTDyAAHu1GYSYM=,tag:RVvIafCr2rar8oBPjbG1JA==,type:str] 16 | tailscale: ENC[AES256_GCM,data:A0VFrICJTR1KW99Dt+LJdbEluweN23QwnAJNHpVjmd8Y24mUg33AqFS0PwlcOjRwhYyE7rfvu4RHkpR2/Q==,iv:fCUj5TqDBZ1ObM38eNP1OVTANnh0RljGJhksHR/VMX4=,tag:gxnskLmSDFKzFc0xhjHXSw==,type:str] 17 | promtail: ENC[AES256_GCM,data:0qloJIV8vTVILUv0MtM=,iv:BVYsq78m1AExx6AA8XVDJp0MKiTA0um3Lev8QK/2wTs=,tag:brioSiR4yu3iGg4VqP78jg==,type:str] 18 | vector: ENC[AES256_GCM,data:Qe/fujD9uzXnx4ZWdSDmxiJSZFhFWGGvcQ==,iv:o/oAGNAEZ8RggjMjcWwWJzQl4VM5UpwPyhl6Ti+UwGQ=,tag:80okD8Z1wqP8oyB6AtRC8g==,type:str] 19 | sops: 20 | kms: [] 21 | gcp_kms: [] 22 | azure_kv: [] 23 | hc_vault: [] 24 | age: 25 | - recipient: age1228fl3wyx640gz9uv3x8zrv3sk0yt5aftc63fpqgqwrnlnry4drqdysp84 26 | enc: | 27 | -----BEGIN AGE ENCRYPTED FILE----- 28 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMK2YzTmxCS3g4bzFGUUxZ 29 | RHRnYmxQMEFpOVFVTlVNL0hZUWF2VXdyNFU4CmppWEtiV3F0dVF5RXpFaDZMQ1lx 30 | REZFZ2NkdnBRZWFpNkZxdnNQN21VdTAKLS0tIE5ZVElIdExQNXNGVDhNdmpNS21t 31 | K2dKY2NUbHNOZlZEUTQrVEh3WmxkWTgKH+bz1GGokksaLQIfP8OSAhbk4YXUITsF 32 | oAtuB8kDaYunWLqGG+7KZBz5FARxrMYLuWWRw4+m30oCIp803zsX3Q== 33 | -----END AGE ENCRYPTED FILE----- 34 | lastmodified: "2024-12-05T09:34:21Z" 35 | mac: ENC[AES256_GCM,data:tjik9wXcPRJ4vvIsTDCqUg1nFSV8du3QrnAS/qK2RAOXiHQn4HSKDRCD9MgK2Yxj7f5J67p81BfMcUedEFD0a+6Ym9aNIa4Xd+lQSVzbfKSZI8EIrG7VehpYBvNHr6cseojmE4hD8N5s/pK9V2RuekYzQZ3gJ5KSEshEe6fp36w=,iv:IMuGPhNaze1LLihvStsq1dNJXG4yEa+Kl5ik6kZS5Uk=,tag:jcEfxhpnY1ZEJKiYd1E/Qg==,type:str] 36 | pgp: 37 | - created_at: "2024-09-11T09:33:11Z" 38 | enc: |- 39 | -----BEGIN PGP MESSAGE----- 40 | 41 | hH4DuWcl82Qws9ESAgMEa+JUKjNfko4qy5OotAGXDFbc4fmv9bXesgN6VyKl8QPe 42 | W/HYCRzn6xhRaOQcqTP9GLdhrZPONtDq2eMh7H6mVjDrT/HdvnVaHLK/bqlEdehj 43 | xa6YMqZWPUMkSoXGNFoJ5+pVgzyMvjMGsgBmtGp5Z4bUZgEJAhAGaZQGAZ6YwFZK 44 | +PUbIwFbXsu7Wep3DdVpcknAxbIxNmLQTl5+ROCqkZpRzm1N7sF3/kXkJEP9bnQ3 45 | 3zRXRdGThovJZLLv5YABJAPV5g6kbHYEoFy8ccuTkjyMsa1mlpEMnA== 46 | =6RiO 47 | -----END PGP MESSAGE----- 48 | fp: 57EDCBEDDE7ABA6B44377FF2B96725F36430B3D1 49 | unencrypted_suffix: _unencrypted 50 | version: 3.9.1 51 | -------------------------------------------------------------------------------- /configuration/features/network/dae.nix: -------------------------------------------------------------------------------- 1 | { config, network, ... }: 2 | { 3 | services.dae = { 4 | enable = true; 5 | configFile = config.sops.templates."config.dae".path; 6 | assetsPath = "/tmp/v2ray"; 7 | }; 8 | 9 | sops.templates."config.dae".reloadUnits = [ "dae.service" ]; 10 | sops.templates."config.dae".content = '' 11 | global { 12 | # Bind to LAN and/or WAN as you want. Replace the interface name to your own. 13 | lan_interface: ${network.interface.lan}, ${network.interface.tor}, ${network.interface.manage}, ${network.interface.security}, ${network.interface.wg}, ${network.interface.tailscale} 14 | wan_interface: auto # Use "auto" to auto detect WAN interface. 15 | 16 | log_level: info 17 | allow_insecure: false 18 | auto_config_kernel_parameter: true 19 | 20 | check_interval: 360s # or 180? 21 | 22 | # domain: 进行 SNI 嗅探, 会验证 SNI 是否可信, 通过要求客户端每次进行连接时都产生 DNS 查询, 将 SNI/目标IP 与 DNS/查询结果 进行匹配 23 | # 如果一致, 则覆盖连接目标为 SNI 目标, 这要求 DAE 监听所有客户端的 DNS 请求并进行缓存, 以保证客户端每次发起连接时都重新进行 DNS 查询 24 | # DAE 会篡改 TTL 为 0, 因此只要 DNS 查询链路尊重上游 TTL, 即便下游有缓存也不会导致问题, 而缓存工作由 DAE 完成 25 | # 但显然这样的行为会使得 TTL 缓存完全失效, 增加网络负担和延迟 26 | # 但这不会改变路由 27 | # domain+: 忽略 SNI 验证, 这使得 DNS 查询可以完全不经由 DAE 处理, 不过这可能导致提供了错误的SNI的情况下无法工作, 如 Tor 28 | # domain++: 忽略 SNI 验证的同时, 以嗅探到的域名重新进行路由, 带来更好的准确度, 同时也意味着整个过程中,如果域名分流正确工作,可以抵御 DNS 污染 29 | # 但也带来了更大的性能开销 30 | dial_mode: domain++ # SNI错误? 31 | } 32 | 33 | node { 34 | tor: 'socks5://127.0.0.1:9050' 35 | } 36 | 37 | subscription { 38 | imm: '${config.sops.placeholder."dae/subscription/imm"}' 39 | } 40 | 41 | group { 42 | proxy { 43 | filter: subtag(imm) && name(keyword: 'JPN') 44 | policy: min_avg10 45 | } 46 | proxy_jp { 47 | filter: subtag(imm) && name(keyword: 'JPN') 48 | policy: min_avg10 49 | } 50 | proxy_hk { 51 | filter: subtag(imm) && name(keyword: 'HKG') 52 | policy: min_avg10 53 | } 54 | proxy_tw { 55 | filter: subtag(imm) && name(keyword: 'TWN') 56 | policy: min_avg10 57 | } 58 | proxy_sg { 59 | filter: subtag(imm) && name(keyword: 'SGP') 60 | policy: min_avg10 61 | } 62 | tor { 63 | filter: name(tor) 64 | policy: fixed(0) 65 | } 66 | } 67 | 68 | # See https://github.com/daeuniverse/dae/blob/main/docs/en/configuration/routing.md for full examples. 69 | routing { 70 | ## DO NOT hijack DNS (for ip/domain++ mode) 71 | # Client -> DNSMASQ -> MOSDNS -(DOH)-> Upstream 72 | dport(53) -> must_rules 73 | ## Or hijack and sniff verify (for domain mode, not working for router itself) 74 | ## Client -(sniff verify)-> DNSMASQ -> MOSDNS -(no verify) -> Upstream 75 | ## Client -(no verify)-> Upstream 76 | # dport(53) && !dip(geoip:private) -> must_rules 77 | 78 | ## Bypass Private IPs 79 | dip(geoip:private) -> direct 80 | 81 | ## Interface-based routing 82 | ifname(tor) -> tor 83 | 84 | ## Application-based routing 85 | # Bypass DSCP 0x4 (e.g. Bittorrent) 86 | dscp(0x4) -> direct 87 | 88 | # Bypass Tailscale (P2P, so conntrack may work, but not reliable, rules only for `randomizeClientPort` is disable) 89 | sport(41641) && dport(41641) && l4proto(udp) -> direct 90 | 91 | ## Region-based routing 92 | # Bypass CN 93 | dip(geoip:cn) -> direct 94 | domain(geosite:cn) -> direct 95 | 96 | # Proxy 97 | dip(geoip:jp) -> proxy_jp 98 | dip(geoip:hk) -> proxy_hk 99 | dip(geoip:tw) -> proxy_tw 100 | dip(geoip:sg) -> proxy_sg 101 | fallback: proxy 102 | } 103 | ''; 104 | 105 | sops.secrets."dae/subscription/imm" = {}; 106 | } -------------------------------------------------------------------------------- /configuration/resource.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | let 3 | # cdn.jsdelivr.net 在境内仍然不太可用 4 | bootstrapDNS = "8.8.8.8"; 5 | setup-resource = pkgs.writeShellScript "setup-resource" '' 6 | set -e 7 | 8 | mkdir -p /tmp/oisd /tmp/v2ray 9 | if [ ! -e "/tmp/oisd/domainswild2_big.txt" ]; then 10 | ${pkgs.curl}/bin/curl --resolve big.oisd.nl:443:$(${pkgs.dig}/bin/dig +short @${bootstrapDNS} big.oisd.nl | grep -m 1 '^[.0-9]*$') https://big.oisd.nl/domainswild2 -o /tmp/oisd/domainswild2_big.txt 11 | fi 12 | if [ ! -e "/tmp/oisd/dnsmasq2_big.txt" ]; then 13 | ${pkgs.curl}/bin/curl --resolve big.oisd.nl:443:$(${pkgs.dig}/bin/dig +short @${bootstrapDNS} big.oisd.nl | grep -m 1 '^[.0-9]*$') https://big.oisd.nl/dnsmasq2 -o /tmp/oisd/dnsmasq2_big.txt 14 | fi 15 | if [ ! -e "/tmp/v2ray/geoip.dat" ]; then 16 | ${pkgs.curl}/bin/curl --resolve fastly.jsdelivr.net:443:$(${pkgs.dig}/bin/dig +short @${bootstrapDNS} fastly.jsdelivr.net | grep -m 1 '^[.0-9]*$') https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat -o /tmp/v2ray/geoip.dat 17 | fi 18 | if [ ! -e "/tmp/v2ray/geosite.dat" ]; then 19 | ${pkgs.curl}/bin/curl --resolve fastly.jsdelivr.net:443:$(${pkgs.dig}/bin/dig +short @${bootstrapDNS} fastly.jsdelivr.net | grep -m 1 '^[.0-9]*$') https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat -o /tmp/v2ray/geosite.dat 20 | fi 21 | if [ ! -e "/tmp/v2ray/geosite_cn.txt" ]; then 22 | ${pkgs.v2dat}/bin/v2dat unpack geosite -o /tmp/v2ray /tmp/v2ray/geosite.dat 23 | fi 24 | ''; 25 | update-resource = pkgs.writeShellScript "update-resource" '' 26 | set -e 27 | 28 | mkdir -p /tmp/oisd /tmp/v2ray 29 | ${pkgs.curl}/bin/curl --resolve big.oisd.nl:443:$(${pkgs.dig}/bin/dig +short @${bootstrapDNS} big.oisd.nl | grep -m 1 '^[.0-9]*$') https://big.oisd.nl/domainswild2 -o /tmp/oisd/domainswild2_big.txt 30 | ${pkgs.curl}/bin/curl --resolve big.oisd.nl:443:$(${pkgs.dig}/bin/dig +short @${bootstrapDNS} big.oisd.nl | grep -m 1 '^[.0-9]*$') https://big.oisd.nl/dnsmasq2 -o /tmp/oisd/dnsmasq2_big.txt 31 | ${pkgs.curl}/bin/curl --resolve fastly.jsdelivr.net:443:$(${pkgs.dig}/bin/dig +short @${bootstrapDNS} fastly.jsdelivr.net | grep -m 1 '^[.0-9]*$') https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat -o /tmp/v2ray/geoip.dat 32 | ${pkgs.curl}/bin/curl --resolve fastly.jsdelivr.net:443:$(${pkgs.dig}/bin/dig +short @${bootstrapDNS} fastly.jsdelivr.net | grep -m 1 '^[.0-9]*$') https://fastly.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat -o /tmp/v2ray/geosite.dat 33 | ${pkgs.v2dat}/bin/v2dat unpack geosite -o /tmp/v2ray /tmp/v2ray/geosite.dat 34 | 35 | ${pkgs.dae}/bin/dae reload 36 | ${pkgs.systemd}/bin/systemctl restart mosdns 37 | ${pkgs.systemd}/bin/systemctl restart dnsmasq 38 | ''; 39 | in 40 | { 41 | systemd.services.setup-resource = { 42 | description = "Pre-start script for updating resource for mosdns and dae"; 43 | wantedBy = [ "multi-user.target" ]; 44 | after = [ "network.target" ]; 45 | serviceConfig = { 46 | Type = "oneshot"; 47 | RemainAfterExit = true; 48 | ExecStart = [ "${pkgs.bash}/bin/bash ${setup-resource}" ]; 49 | }; 50 | }; 51 | 52 | # 将 pre-start-script 设置为 mosdns 和 dae 的依赖 53 | systemd.services.mosdns = { 54 | after = [ "setup-resource.service" ]; 55 | requires = [ "setup-resource.service" ]; 56 | }; 57 | 58 | systemd.services.dae = { 59 | after = [ "setup-resource.service" ]; 60 | requires = [ "setup-resource.service" ]; 61 | }; 62 | 63 | systemd.services.dnsmasq = { 64 | after = [ "setup-resource.service" ]; 65 | requires = [ "setup-resource.service" ]; 66 | serviceConfig.PrivateTmp = lib.mkForce false; 67 | }; 68 | 69 | services.cron = { 70 | enable = true; 71 | systemCronJobs = [ 72 | "0 0 * * * root bash ${update-resource}" 73 | ]; 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /configuration/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, inputs, ... }: 2 | { 3 | imports = [ 4 | ./resource.nix 5 | ./network/interface 6 | ./network/dnsmasq.nix 7 | ./network/mosdns.nix 8 | ./network/nftables.nix 9 | ./network/hosts.nix 10 | ./features/network/ddns.nix 11 | ./features/network/miniupnpd.nix 12 | ./features/network/tailscale.nix 13 | ./features/network/dae.nix 14 | ./features/network/tor.nix 15 | (inputs.homelab + "/features/basic.nix") 16 | (inputs.homelab + "/features/network/avahi") 17 | (inputs.homelab + "/features/nix") 18 | (inputs.homelab + "/features/fish.nix") 19 | (inputs.homelab + "/features/develop.nix") 20 | (import (inputs.homelab + "/features/telemetry/mdns.nix") ({ inherit config; promtail_password_file = config.sops.secrets.promtail.path; })) 21 | ./features/telemetry.nix 22 | ./user.nix 23 | ]; 24 | 25 | 26 | # Load before sysctl (using systemd-modules-load.service) 27 | boot.kernelModules = [ "nf_conntrack" ]; 28 | 29 | # /proc/sys/ to should be writeble 30 | boot.kernel.sysctl = { 31 | ### Conntrack 32 | "net.netfilter.nf_conntrack_acct" = true; 33 | "net.netfilter.nf_conntrack_timestamp" = true; 34 | "net.netfilter.nf_conntrack_buckets" = 262144; 35 | "net.netfilter.nf_conntrack_max" = 262144; 36 | # Timeout 37 | "net.netfilter.nf_conntrack_tcp_timeout_established" = 21600; # Default: 432000 38 | "net.netfilter.nf_conntrack_udp_timeout" = 60; 39 | "net.netfilter.nf_conntrack_udp_timeout_stream" = 180; 40 | ## Layer 3 forwarding 41 | "net.ipv4.conf.all.forwarding" = true; 42 | "net.ipv6.conf.all.forwarding" = true; 43 | ## TCP optimization 44 | # TCP Fast Open is a TCP extension that reduces network latency by packing 45 | # data in the sender’s initial TCP SYN. Setting 3 = enable TCP Fast Open for 46 | # both incoming and outgoing connections: 47 | "net.ipv4.tcp_fastopen" = 3; 48 | ## TCP congestion control 49 | "net.ipv4.tcp_congestion_control" = "bbr"; 50 | ## Queueing discipline 51 | ## https://www.bufferbloat.net/projects/codel/wiki/ 52 | ## https://www.bufferbloat.net/projects/codel/wiki/Cake/ 53 | "net.core.default_qdisc" = "cake"; 54 | ## UDP Buffersize (https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes) 55 | ## https://docs.redhat.com/en/documentation/red_hat_data_grid/7.2/html/performance_tuning_guide/networking_configuration#adjusting_send_receive_window_settings 56 | "net.core.wmem_max" = 655360; 57 | "net.core.rmem_max" = 26214400; 58 | "net.ipv4.tcp_wmem" = "4096 16384 655360"; 59 | "net.ipv4.tcp_rmem" = "4096 87380 26214400"; 60 | }; 61 | 62 | # https://nixos.wiki/wiki/Networkd-dispatcher 63 | services.networkd-dispatcher = { 64 | enable = true; 65 | rules = { 66 | "50-offload" = { 67 | onState = [ "routable" "carrier" ]; 68 | # https://www.kernel.org/doc/html/latest/networking/segmentation-offloads.html 69 | # https://tailscale.com/kb/1320/performance-best-practices#ethtool-configuration 70 | # https://tailscale.com/blog/more-throughput 71 | # https://lore.kernel.org/netdev/90c19324a093536f1e0e2e3de3a36df4207a28d3.camel@redhat.com 72 | # https://lore.kernel.org/netdev/20220203015140.3022854-15-eric.dumazet@gmail.com 73 | script = '' 74 | #!${pkgs.runtimeShell} 75 | ${pkgs.ethtool}/bin/ethtool -K $IFACE tx-udp-segmentation on rx-udp-gro-forwarding on rx-gro-list off 76 | ${pkgs.iproute2}/bin/ip link set dev $IFACE gso_max_size 524280 gro_max_size 524280 77 | ${pkgs.iproute2}/bin/ip link set dev $IFACE gso_ipv4_max_size 524280 gro_ipv4_max_size 524280 78 | ''; 79 | }; 80 | }; 81 | }; 82 | 83 | environment.systemPackages = with pkgs; [ 84 | neofetch hyfetch # to see system infomation 85 | ppp # for some manual debugging of pppd 86 | conntrack-tools # view network connection states 87 | wireguard-tools # view wireguard status 88 | iperf3 speedtest-go cfspeedtest # speedtest tools 89 | tcping-go gping mtr trippy # latency/tracing tools 90 | bridge-utils # brctl 91 | cloudflared # cloudflare zero trust 92 | stuntman # stun 93 | ]; 94 | 95 | proxmox.qemuConf.net0 = ""; 96 | 97 | sops.defaultSopsFile = ../secrets.yaml; 98 | sops.secrets.promtail.owner = "promtail"; 99 | 100 | system.stateVersion = "24.05"; 101 | } 102 | -------------------------------------------------------------------------------- /configuration/network/dnsmasq.nix: -------------------------------------------------------------------------------- 1 | { config, network, ... }: 2 | # https://openwrt.org/docs/guide-user/base-system/dhcp 3 | # https://thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html 4 | let 5 | domain = network.domain; 6 | in { 7 | services.resolved.enable = false; 8 | 9 | services.dnsmasq = { 10 | enable = true; 11 | settings = with config.network.interface; { 12 | # Upstream (mosdns) 13 | server = [ "127.0.0.53" ]; 14 | no-resolv = true; 15 | # Local domain 16 | domain = domain; 17 | local = "/${domain}/"; # Not forwarding local domain to upstream 18 | expand-hosts = true; 19 | # Interface bind 20 | interface = [ private.lan private.tor private.security private.manage ]; 21 | bind-dynamic = true; 22 | # Bind domain to interface's IPs 23 | interface-name = [ "${domain},${private.lan}" "router.${domain},${private.lan}" ]; 24 | # Cache 25 | cache-size = 8192; 26 | # This will cause a re-request to the upstream every time you resolve the ipv4 single-stack domain name because the ipv6 address is not obtained and cached. 27 | # no-negcache = true; 28 | # Ensure requests for local hostnames (without dots or domain parts) aren't forwarded to upstream DNS servers 29 | domain-needed = true; 30 | # Pervent reverse DNS lookups for local hosts 31 | bogus-priv = true; 32 | # Allows returning different results to different interfaces 33 | # For an authoritative server, when encountering a CNAME, only the corresponding domain name needs to be returned 34 | # For a recursive resolver, when encountering a CNAME, it needs to return both the domain name and the result (or only the result?) 35 | # Since DNSMASQ both acts as an authoritative server/recursive resolver, It needs to be allowed to return different results for different interfaces. 36 | localise-queries = true; # 关闭此选项似乎会导致包含在 auth-zone 的 CNAME 在非 auth-server 绑定的接口也不返回实际 IP, 尚不清楚成因 37 | # DHCP 38 | dhcp-authoritative = true; 39 | dhcp-broadcast = "tag:needs-broadcast"; 40 | dhcp-ignore-names = "tag:dhcp_bogus_hostname"; 41 | dhcp-range = [ 42 | # lan 43 | "set:${private.lan},10.0.1.0,10.0.254.255,24h" # Reserve 10.0.0.0/24 & 10.0.255.0/24 44 | "set:${private.lan},::fff,::ffff,constructor:${private.lan},ra-names" 45 | # tor 46 | "set:${private.tor},10.1.1.0,10.1.254.255,24h" # Reserve 10.1.0.0/24 & 10.1.255.0/24 47 | "set:${private.tor},::fff,::ffff,constructor:${private.tor},ra-names" 48 | # security 49 | "set:${private.security},10.10.1.0,10.10.254.255,24h" # Reserve 10.10.0.0/24 & 10.10.255.0/24 50 | "set:${private.security},::fff,::ffff,constructor:${private.security},ra-names" 51 | # manage 52 | "set:${private.manage},10.100.1.0,10.100.254.255,24h" # Reserve 10.100.0.0/24 & 10.100.255.0/24 53 | "set:${private.manage},::fff,::ffff,constructor:${private.manage},ra-names" 54 | ]; 55 | # Binding 56 | dhcp-host = [ 57 | "54:f6:e2:e6:6e:80,LeaderAP" 58 | "78:60:5b:97:dc:70,TL-ST5008F" 59 | "68:dd:b7:0c:ec:3c,TL-SE2109PB" 60 | ]; 61 | read-ethers = true; 62 | # CNAME 63 | cname = [ 64 | "binarycache.${domain},hydra.${domain}" 65 | "qbittorrent.${domain},emby.${domain},jellyfin.${domain},immich.${domain},ipfs.${domain},api.ipfs.${domain},syncthing.${domain},minio.${domain},console.minio.${domain},attic.${domain},nas.${domain}" 66 | # "qbittorrent.legceynas.${domain},emby.legceynas.${domain},immich.legceynas.${domain},ipfs.legceynas.${domain},api.ipfs.legceynas.${domain},syncthing.legceynas.${domain},nixnas.${domain}" 67 | "authentik.${domain},vaultwarden.${domain},argocd.${domain},hubble.${domain},portainer.${domain},memos.${domain},pdf.${domain},longhorn.${domain},grafana.${domain},uptime.${domain},prometheus.${domain},alertmanager.${domain},loki.${domain},thelounge.${domain},node0-rke.${domain}" 68 | "zabbix.${domain},metrics.${domain}" 69 | "rancher.${domain},harvester.${domain}" 70 | ]; 71 | # AUTHORITATIVE 72 | # 作为权威的范围, 这会使得对应内容变得权威 (+authority), 并且提供 AUTHORITY SECTION (NS记录, 或许也可以是 SOA 记录) 73 | auth-zone = "${domain},${private.lan}/6,exclude:fc00::/7"; 74 | # 需要注意的是这不能适用于需要作为递归 DNS 服务器的接口, 因为会导致 CNAME 不可用, 此外该接口不需要包括在 interface 中 (否则会进行覆盖) 75 | auth-server = "${domain},${builtins.concatStringsSep "," worlds}"; 76 | # ADBlock 77 | conf-file = "/tmp/oisd/dnsmasq2_big.txt"; 78 | }; 79 | }; 80 | 81 | networking.firewall.allowedTCPPorts = [ 53 ]; 82 | networking.firewall.allowedUDPPorts = [ 53 ]; 83 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChaosAttractor's Router Configuration 2 | 3 | ## TODO 4 | - 现在resolved是路由的默认DNS, 并使用 dnsmasq 作为上游, 但好像并没有转发请求给 dnsmasq, 看起来会并发请求networkd提供的上游和dnsmasq,按需采用 5 | - sing-box & tproxy? 6 | - nf_conntrack_acct 7 | - OpenGFW 8 | - 只有源地址是私网地址且目标地址是公网地址的情况下需要NAT,但目前这样根据源接口通过networkd设置NAT似乎也没什么大问题 9 | - 仅三层与内网互通的VLAN? (管理面?) 10 | - 安全(生产)域划分(三层不完全互通?) 11 | - 未来可能直接使用 DoH/DoQ, DAE 不做劫持行为(但监听请求以保证嗅探工作) , DoH/DoQ 的请求由代理节点发出, 并由 fallback 机制保证 DAE 故障时可用 12 | 13 | systemd-resolved目前故障时可用, 或许也可以作为一个fallback的选择, 并且dnsmasq自身就具有fallback机制, 也未必需要起一个 mosdns, 使用一个反代似乎也足以 14 | 15 | 但是这样在不故障时会打环 16 | 17 | ## 地址 18 | ### IPv4 19 | #### 地址 20 | WAN 通过 PPP(IPCP)/DHCP 获得地址 21 | #### 路由 22 | 进行 NAT 23 | 24 | WAN 口获得到的 IP, 上游可以正确路由, 通过 masquerade, 所有连接都从路由本机发出(转发) 25 | 26 | ### IPv6 27 | #### 地址 28 | WAN 接口通过 DHCP-PD 获取前缀, LAN 接口通过前缀获得地址, 同时通过 SLAAC 获得一个地址, 不使用 DHCP 29 | 30 | #### 路由 31 | ##### 下行 32 | DHCP-PD 可以使上游将整个段都路由到我的路由器 33 | ##### 上行 34 | 对于 PPP, 不依赖 default route, 直接 NDP 就能连接, 上游直接是二层网络 35 | 36 | 对于 DHCP,需要 default route, 因为 WAN 和上游的 PPP 不是二层桥接, 上游还需要进行三层转发 37 | 38 | 不过, SLAAC 可以自动添加 default route 39 | 40 | 此外,最好不要关闭AcceptRA, 不接收RA容易导致地址更新不及时(存疑) 41 | 42 | ## DNS 43 | 使用 dnsmasq 作为本地 DNS 服务器, 通过 DHCP 和 SLAAC 提供给 LAN 设备 44 | 45 | systemd-resolved 会和 systemd-networkd 联动, 从而获得接口的上游DNS (通常来说是运营商 DNS) 46 | 47 | 然后他会和 dnsmasq 联动, 提供上游 DNS 信息提供给 dnsmasq, 详见`services.dnsmasq.resolveLocalQueries`: 48 | - https://github.com/NixOS/nixpkgs/blob/nixos-unstable/nixos/modules/system/boot/resolved.nix 49 | - https://github.com/NixOS/nixpkgs/blob/nixos-unstable/nixos/modules/services/networking/dnsmasq.nix 50 | 51 | 此外 dnsmasq 还被手动添加了 alidns 并优先使用, 因为运营商 DNS 可能包含更多的 DNS 污染, 并且同时更慢或无显著速度差别 52 | 53 | systemd-resolved 将作为路由器的默认 DNS, 并默认转发 DNS 请求到 dnsmasq, 亦会在 dnsmasq 故障时使用接口的上游 DNS 进行解析 54 | 55 | 因此 systemd-resolved 设计上在 dae 故障时可用 56 | 57 | 具体信息可以通过 `resolvectl status` 查看 58 | 59 | ### 广告屏蔽 (WIP) 60 | dnsmasq 对广告域名会返回 nxdomain 61 | 62 | ### DAE 63 | DAE 会劫持所有来自 dnsmasq 的 DNS 请求 (向上游查询的请求), 其他请求则只分流, 不劫持 64 | 65 | 劫持后直连所有中国网站的域名, 其余一律从默认节点发到 GoogleDNS, 使用默认节点是必要的, 这保证地域路由/CDN工作 66 | 67 | 并会返回 TTL为0的结果, DAE 进行缓存, 这用于保证嗅探工作 68 | 69 | TODO: https://github.com/daeuniverse/dae/issues/474 70 | 71 | ### DNS 服务器地址 72 | - 127.0.0.1/192.168.8.1: dnsmasq 73 | - 127.0.0.53: systemd-resolved 74 | 75 | ### DHCP/DNS 权威解析 (TODO) 76 | 77 | ## VLAN/SDN 78 | 默认不处理Untagged流量 79 | 80 | | VID | ZONE | IP/CIDR | Wireless | Introduction | 81 | | ----------------------------------------- | ---------------- | -------------- | ------------- | ------------ | 82 | | 1 in downstream | lan -> br-lan | 10.0.0.1/16 | MisakaNetwork | 本地网络 | 83 | | 2 in downstream | direct -> br-lan | 10.0.0.1/16 | MikotoNetwork | 本地网络, 不进行透明代理 | 84 | | 10 in downstream | security | 10.1.0.1/16 | NO SSID | 生成环境, 此VLAN的所有入站都需要单独审计 | 85 | | 100 in downstream | manage | 10.255.0.1/16 | NO SSID | 管理网络, 限制待定 | 86 | | 1000 in ap | ap | 169.254.2.1/24 | ManageNetwork | AP的管理VLAN, AP自行提供DHCP服务, 用于保证AP可管理, 交换机/路由侧不处理(DROP) | 87 | | 4094 in downstream / untagged in upstream | onu | 192.168.1.1/24 | ONUNetwork | 光猫上联 | 88 | | wireguard | vpn | 10.100.0.1/24 | NO SSID | VPN / 可信隧道 | 89 | 90 | ## 防火墙 (TODO) 91 | 92 | ### Filter 93 | 网关的安全性很重要, 所以仅允许的必要的端口可被访问 94 | | Type | Action | 95 | | ------------------------------------------- | ------- | 96 | | ICMP -> Router | Allowed | 97 | | DHCPv6 message from world -> Router | Allowed | 98 | | DHCP message from private -> Router | Allowed | 99 | | SSH/DNS/Wireguard -> Router | Allowed | 100 | | LAN/VPN -> Router | Allowed | 101 | 102 | --- 103 | 104 | 所有网络均可访问外网 105 | 106 | LAN/VPN拥有同等安全性, 并且允许所有IPv6连入和ICMP 107 | 108 | MANAGE仅可由LAN/VPN连入 109 | 110 | SECURITY则需要审计 111 | 112 | | Type | Action | 113 | | ------------------------------------------- | ------- | 114 | | ANY -> World | Allowed | 115 | | LAN <-> VPN | Allowed | 116 | | LAN/VPN -> MANAGE | Allowed (网络内设备自行保证安全) | 117 | | ICMO-ECHO-REQUEST from world -> LAN/VPN | Allowed (Weak Security Zone) | 118 | | Any IPv6 from world -> LAN/VPN | Allowed (Weak Security Zone) | 119 | | ANY -> SECURITY | 需要审计 (Strong Security Zone) | 120 | 121 | ### MSS Clamp 122 | 目前会把所有经过 forward 链的流量的 MTU 都设置成 pMTU 123 | 124 | ## SDN/VPN (Wireguard/TODO) 125 | 126 | ## 硬件转发 (TODO) 127 | - Flowtable Offload 128 | - OVS Offload 129 | 130 | ## 5G CPE 热备(TODO) 131 | 132 | ## CANet 的拓扑 (TODO) 133 | 134 | ## DAE (TODO) 135 | 默认使用日本作为代理出口 136 | 137 | DAE根据GEOIP的地区进行分流, 为了保证流量被分流到默认代理出口, 应当保证DNS查询从默认出口发出 138 | 139 | 对于从默认出口进行DNS查询也返回非日本IP的情况,则使用对应地区的节点发出 140 | 141 | 随着 https://github.com/daeuniverse/dae/issues/47 的解决, 目前DAE已经有了连接追踪, 虽然可能还未经过充分测试, 但如果按预期工作, UDP连入应该可以正常工作 142 | 143 | ## 额外功能 144 | ### DDNS 145 | ### UPNP 146 | 147 | ## Proxmox Image 148 | To generate proxmox image: 149 | ```sh 150 | nix build .#nixosConfigurations.bootstrap.config.system.build.VMA 151 | ``` -------------------------------------------------------------------------------- /configuration/network/nftables.nix: -------------------------------------------------------------------------------- 1 | { network, config, lib, ... }: 2 | 3 | with config.network.interface; 4 | { 5 | networking.firewall.trustedInterfaces = [ private.lan private.wg private.tailscale ]; 6 | networking.firewall.extraInputRules = '' 7 | # Accepting DHCP Message from all local networks 8 | iifname != ${world} udp dport 67 accept comment "DHCPv4 server" 9 | ''; 10 | 11 | networking.nftables = { 12 | enable = true; 13 | 14 | # https://discourse.nixos.org/t/nftables-could-not-process-rule-no-such-file-or-directory/33031 15 | checkRuleset = false; 16 | 17 | tables = { 18 | filter = { 19 | family = "inet"; 20 | content = '' 21 | flowtable f { 22 | # Either physical interface or bridge/layer two encapsulation interface can be used. 23 | # Nftables is started before networkd, so the bridge and layer 2 encapsulated interfaces do not yet exist when nftables is started, 24 | # so it is necessary to bind to the physical interface. 25 | # Also, a physical interface may have multiple Layer 2 encapsulations or bridges above it. 26 | # https://docs.kernel.org/networking/nf_flowtable.html#layer-2-encapsulation 27 | # https://docs.kernel.org/networking/nf_flowtable.html#bridge-and-ip-forwarding 28 | 29 | # TODO: Software-defined interfaces such as wireguard may cause problems due to startup sequence 30 | hook ingress priority 0; devices = { ${network.interface.downstream}, ${network.interface.upstream} }; 31 | # Hardware offload only works on physical hardware 32 | # flags offload; 33 | } 34 | 35 | chain weak_security_zone { 36 | # Accepting ping (icmp-echo-request) for diagnostic purposes. 37 | # However, it also lets probes discover this host is alive. 38 | icmp type echo-request limit rate 5/second accept 39 | icmpv6 type echo-request limit rate 5/second accept 40 | 41 | # Accepting all IPv6 42 | ip6 version 6 accept 43 | } 44 | 45 | chain strong_security_zone { 46 | # WIP 47 | } 48 | 49 | chain forward { 50 | type filter hook forward priority filter; policy drop; 51 | 52 | # Enable flow offloading for better throughput 53 | flow add @f 54 | 55 | # Clamp MSS to pMTU 56 | # Needed for interface such as ppp or vpns 57 | # Here is enabled for all interfaces as this does not cause any side effects 58 | # Also this is helpful when using jumbo frames 59 | tcp flags syn tcp option maxseg size set rt mtu 60 | 61 | # Accepting traffic from established and related packets, drop invalid 62 | ct state vmap { established : accept, related : accept, invalid : drop } 63 | 64 | # any -> worlds/onu: accept 65 | # vpns <-> lan: accept 66 | # lan/vpns -> manage: accept 67 | # worlds -> lan/vpns: jump weak_security_zone 68 | # any -> security: jump strong_security_zone 69 | 70 | ${builtins.concatStringsSep "\n" (map (world: "oifname ${world} accept") worlds)} 71 | ${lib.optionalString (!builtins.elem network.interface.onu worlds) "oifname ${network.interface.onu} accept"} # Allow visit ONU for manage purpose if needed 72 | meta iifname . meta oifname { ${private.lan} . ${private.wg}, ${private.lan} . ${private.tailscale}, ${private.wg} . ${private.lan}, ${private.tailscale} . ${private.lan} } accept 73 | oifname ${private.manage} iifname vmap { ${private.lan} : accept, ${private.wg} : accept, ${private.tailscale} : accept } 74 | ${builtins.concatStringsSep "\n" (map (world: "iifname ${world} oifname vmap { ${private.lan} : jump weak_security_zone, ${private.wg} : jump weak_security_zone, ${private.tailscale} : jump weak_security_zone }") worlds)} 75 | oifname ${private.security} jump strong_security_zone 76 | 77 | # The rest is dropped by the above policy 78 | } 79 | ''; 80 | }; 81 | nat = { 82 | family = "inet"; 83 | content = '' 84 | chain postrouting { 85 | type nat hook postrouting priority srcnat; policy accept; 86 | 87 | # full-cone nat local address 88 | ip saddr 10.0.0.0/8 fullcone 89 | 90 | # full-cone nat ula addresses 91 | ip6 saddr fc00::/7 ip6 daddr != fc00::/7 fullcone 92 | } 93 | 94 | chain prerouting { 95 | type nat hook prerouting priority dstnat; policy accept; 96 | fullcone 97 | } 98 | ''; 99 | }; 100 | }; 101 | }; 102 | 103 | nixpkgs.overlays = [ 104 | (final: prev: { 105 | nftables = prev.nftables.overrideAttrs (oldAttrs: with prev; { 106 | patches = oldAttrs.patches or [ ] ++ [ 107 | (fetchurl { 108 | url = "https://raw.githubusercontent.com/debiansid/nftables-fullcone/vyos/1.1.1-1/0001-nftables-add-fullcone-expression-support.patch"; 109 | hash = "sha256-CD0xGLF8VFKvLD3nTcmtBHHx1HZmAq5SDNDza6wsrJ8="; 110 | }) 111 | ]; 112 | }); 113 | libnftnl = prev.libnftnl.overrideAttrs (oldAttrs: with prev; { 114 | nativeBuildInputs = (oldAttrs.nativeBuildInputs or [ ]) ++ [ autoreconfHook ]; 115 | patches = oldAttrs.patches or [ ] ++ [ 116 | (fetchurl { 117 | url = "https://raw.githubusercontent.com/debiansid/libnftnl-fullcone/vyos/1.2.8/0001-add-fullcone-expression-support.patch"; 118 | hash = "sha256-yQ6UsLFbiEd381OmJQHoYOryrURX1OXYoCThUsUfO0w="; 119 | }) 120 | ]; 121 | }); 122 | }) 123 | ]; 124 | } -------------------------------------------------------------------------------- /configuration/network/mosdns.nix: -------------------------------------------------------------------------------- 1 | { inputs, pkgs, ... }: 2 | { 3 | services.mosdns ={ 4 | enable = true; 5 | config = { 6 | log.level = "info"; 7 | api.http = ":9154"; 8 | 9 | plugins = [ 10 | # Domain/IP set 11 | { 12 | tag = "ads"; type = "domain_set"; 13 | args.files = [ "/tmp/oisd/domainswild2_big.txt"]; 14 | } 15 | { 16 | tag = "geosite-cn"; type = "domain_set"; 17 | args.files = [ "/tmp/v2ray/geosite_cn.txt" ]; 18 | } 19 | { 20 | tag = "geosite-geolocation-!cn"; type = "domain_set"; 21 | args.files = [ "${pkgs.mosdns-geosite}/geosite_geolocation-!cn.txt" ]; 22 | } 23 | # Upstream 24 | { 25 | tag = "upstream_google"; type = "forward"; 26 | args.concurrent = 2; 27 | args.upstreams = [ 28 | { addr = "https://8.8.8.8/dns-query"; /*enable_http3 = true;*/ } 29 | { addr = "https://8.8.4.4/dns-query"; /*enable_http3 = true;*/ } 30 | ]; 31 | } 32 | { 33 | tag = "upstream_alidns"; type = "forward"; 34 | args.concurrent = 2; 35 | args.upstreams = [ 36 | { addr = "https://223.5.5.5/dns-query"; enable_http3 = true; } 37 | { addr = "https://223.6.6.6/dns-query"; enable_http3 = true; } 38 | ]; 39 | } 40 | # Forward 41 | { 42 | tag = "forward_google"; type = "sequence"; 43 | args = [ 44 | { exec = "metrics_collector googledns"; } 45 | { exec = "$upstream_google"; } 46 | { exec = "query_summary googledns"; } 47 | ]; 48 | } 49 | { 50 | tag = "forward_alidns"; type = "sequence"; 51 | args = [ 52 | { exec = "metrics_collector alidns"; } 53 | { exec = "$upstream_alidns"; } 54 | { exec = "query_summary alidns"; } 55 | ]; 56 | } 57 | { 58 | tag = "fallback_alidns"; type = "sequence"; 59 | args = [ 60 | { exec = "metrics_collector fallback"; } 61 | { exec = "$forward_alidns"; } 62 | { exec = "query_summary fallback"; } 63 | ]; 64 | } 65 | { 66 | tag = "fallback"; type = "fallback"; 67 | args = { 68 | primary = "forward_google"; 69 | secondary = "fallback_alidns"; 70 | threshold = 5000; 71 | }; 72 | } 73 | # Main 74 | { 75 | tag = "main"; type = "sequence"; 76 | args = [ 77 | # 对于国内域名, 转发到国内 DNS 以保证速度 78 | { matches = [ "qname $geosite-cn" ]; exec = "$forward_alidns"; } 79 | { matches = [ "has_resp" ]; exec = "query_summary geosite.cn"; } 80 | { matches = [ "has_resp" ]; exec = "accept"; } 81 | # 因为可能没有很好的境外 IPv6 连接能力 82 | { matches = [ "qname $geosite-!cn" ]; exec = "prefer_ipv4"; } 83 | { matches = [ "qname $geosite-!cn" ]; exec = "query_summary perfer ipv4"; } 84 | # 转发到境外 DNS (并 fallback) 85 | { exec = "$fallback"; } 86 | ]; 87 | } 88 | # Server 89 | { 90 | type = "udp_server"; 91 | args = { entry = "main"; listen = "127.0.0.53:53"; }; 92 | } 93 | ]; 94 | }; 95 | }; 96 | 97 | # https://icyleaf.com/2023/08/using-vector-transform-mosdns-logging-to-grafana-via-loki/ 98 | # https://gist.github.com/icyleaf/e98093f673b4b2850226db582447175a 99 | services.vector = { 100 | enable = true; 101 | journaldAccess = true; 102 | settings = { 103 | sources.mosdns-log = { 104 | type = "journald"; 105 | include_units = [ "mosdns" ]; 106 | }; 107 | transforms.mosdns-data = { 108 | type = "remap"; 109 | inputs = [ "mosdns-log" ]; 110 | drop_on_error = true; 111 | source = '' 112 | del(.PRIORITY) 113 | del(.SYSLOG_FACILITY) 114 | del(._BOOT_ID) 115 | del(._CAP_EFFECTIVE) 116 | del(._CMDLINE) 117 | del(._COMM) 118 | del(._EXE) 119 | del(._GID) 120 | del(._MACHINE_ID) 121 | del(._PID) 122 | del(._RUNTIME_SCOPE) 123 | del(._STREAM_ID) 124 | del(._SYSTEMD_CGROUP) 125 | del(._SYSTEMD_INVOCATION_ID) 126 | del(._SYSTEMD_SLICE) 127 | del(._SYSTEMD_UNIT) 128 | del(._UID) 129 | del(.__MONOTONIC_TIMESTAMP) 130 | del(.__REALTIME_TIMESTAMP) 131 | del(.__SEQNUM) 132 | del(.__SEQNUM_ID) 133 | 134 | message_parts = split!(.message, r'\t') 135 | 136 | .timestamp = parse_timestamp!(message_parts[0], format: "%FT%T%.9fZ") 137 | .level = message_parts[1] 138 | 139 | if (length(message_parts) == 6) { 140 | .plugin = message_parts[2] 141 | .processor = message_parts[3] 142 | .message = message_parts[4] 143 | 144 | if (exists(message_parts[5])) { 145 | .metadata = parse_json!(message_parts[5]) 146 | . = merge!(., .metadata) 147 | del(.metadata) 148 | } 149 | } else { 150 | .processor = message_parts[2] 151 | .message = message_parts[3] 152 | 153 | if (exists(message_parts[4])) { 154 | .metadata = parse_json!(message_parts[4]) 155 | . = merge!(., .metadata) 156 | del(.metadata) 157 | } 158 | } 159 | 160 | if (exists(.query)) { 161 | query_parts = split!(.query, r'\s') 162 | .domain = query_parts[0] 163 | .record = query_parts[2] 164 | .address = query_parts[5] 165 | } 166 | ''; 167 | }; 168 | sinks = { 169 | loki = { 170 | type = "loki"; 171 | inputs = [ "mosdns-data" ]; 172 | endpoint = "http://node0-rke.local:30001"; 173 | encoding.codec = "json"; 174 | auth = { 175 | strategy = "basic"; 176 | user = "main"; 177 | password = "\${LOKI_TOKEN:-}"; 178 | }; 179 | labels = { 180 | app = "{{ SYSLOG_IDENTIFIER }}"; 181 | host = "{{ host }}"; 182 | message = "{{ message }}"; 183 | }; 184 | healthcheck.enabled = true; 185 | }; 186 | debug = { 187 | type = "console"; 188 | inputs = [ "mosdns-data" ]; 189 | encoding.codec = "json"; 190 | }; 191 | }; 192 | }; 193 | }; 194 | 195 | systemd.services.vector.serviceConfig.EnvironmentFile = config.sops.secrets.vector.path; 196 | sops.secrets.vector = {}; 197 | } -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "daeuniverse": { 4 | "inputs": { 5 | "flake-parts": "flake-parts", 6 | "nixpkgs": [ 7 | "nixpkgs" 8 | ] 9 | }, 10 | "locked": { 11 | "lastModified": 1736362056, 12 | "narHash": "sha256-MubNngr/XXrzewtJ6q4EgDtubhSluenheJmzJ1sZ7NY=", 13 | "owner": "LostAttractor", 14 | "repo": "flake.nix", 15 | "rev": "380d5508da3bce9df37b247ee74fa6c00ea60b23", 16 | "type": "github" 17 | }, 18 | "original": { 19 | "owner": "daeuniverse", 20 | "ref": "unstable", 21 | "repo": "flake.nix", 22 | "type": "github" 23 | } 24 | }, 25 | "deploy-rs": { 26 | "inputs": { 27 | "flake-compat": "flake-compat", 28 | "nixpkgs": [ 29 | "nixpkgs" 30 | ], 31 | "utils": "utils" 32 | }, 33 | "locked": { 34 | "lastModified": 1727447169, 35 | "narHash": "sha256-3KyjMPUKHkiWhwR91J1YchF6zb6gvckCAY1jOE+ne0U=", 36 | "owner": "serokell", 37 | "repo": "deploy-rs", 38 | "rev": "aa07eb05537d4cd025e2310397a6adcedfe72c76", 39 | "type": "github" 40 | }, 41 | "original": { 42 | "owner": "serokell", 43 | "repo": "deploy-rs", 44 | "type": "github" 45 | } 46 | }, 47 | "flake-compat": { 48 | "flake": false, 49 | "locked": { 50 | "lastModified": 1696426674, 51 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 52 | "owner": "edolstra", 53 | "repo": "flake-compat", 54 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 55 | "type": "github" 56 | }, 57 | "original": { 58 | "owner": "edolstra", 59 | "repo": "flake-compat", 60 | "type": "github" 61 | } 62 | }, 63 | "flake-parts": { 64 | "inputs": { 65 | "nixpkgs-lib": "nixpkgs-lib" 66 | }, 67 | "locked": { 68 | "lastModified": 1730504689, 69 | "narHash": "sha256-hgmguH29K2fvs9szpq2r3pz2/8cJd2LPS+b4tfNFCwE=", 70 | "owner": "hercules-ci", 71 | "repo": "flake-parts", 72 | "rev": "506278e768c2a08bec68eb62932193e341f55c90", 73 | "type": "github" 74 | }, 75 | "original": { 76 | "owner": "hercules-ci", 77 | "repo": "flake-parts", 78 | "type": "github" 79 | } 80 | }, 81 | "flake-utils": { 82 | "inputs": { 83 | "systems": "systems_2" 84 | }, 85 | "locked": { 86 | "lastModified": 1681202837, 87 | "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", 88 | "owner": "numtide", 89 | "repo": "flake-utils", 90 | "rev": "cfacdce06f30d2b68473a46042957675eebb3401", 91 | "type": "github" 92 | }, 93 | "original": { 94 | "owner": "numtide", 95 | "repo": "flake-utils", 96 | "type": "github" 97 | } 98 | }, 99 | "homelab": { 100 | "locked": { 101 | "lastModified": 1736001657, 102 | "narHash": "sha256-HnLr8cvYsgDO6qt2g5eGxfowbaNmk1CChoJZEp56Bjk=", 103 | "owner": "lostattractor", 104 | "repo": "homelab", 105 | "rev": "1e1ee952183726b0a0cae5b28a6a49d44f4d1257", 106 | "type": "github" 107 | }, 108 | "original": { 109 | "owner": "lostattractor", 110 | "repo": "homelab", 111 | "type": "github" 112 | } 113 | }, 114 | "nixpkgs": { 115 | "locked": { 116 | "lastModified": 1736362176, 117 | "narHash": "sha256-tLzfctMvt+ktG5spsJ+LgM45kHg1bb7UPb++yfznJA0=", 118 | "owner": "LostAttractor", 119 | "repo": "nixpkgs", 120 | "rev": "9df5b0cd3a77c69b40d71054d86991f53ef9f24b", 121 | "type": "github" 122 | }, 123 | "original": { 124 | "owner": "NixOS", 125 | "ref": "nixos-unstable", 126 | "repo": "nixpkgs", 127 | "type": "github" 128 | } 129 | }, 130 | "nixpkgs-lib": { 131 | "locked": { 132 | "lastModified": 1730504152, 133 | "narHash": "sha256-lXvH/vOfb4aGYyvFmZK/HlsNsr/0CVWlwYvo2rxJk3s=", 134 | "type": "tarball", 135 | "url": "https://github.com/NixOS/nixpkgs/archive/cc2f28000298e1269cea6612cd06ec9979dd5d7f.tar.gz" 136 | }, 137 | "original": { 138 | "type": "tarball", 139 | "url": "https://github.com/NixOS/nixpkgs/archive/cc2f28000298e1269cea6612cd06ec9979dd5d7f.tar.gz" 140 | } 141 | }, 142 | "oisd": { 143 | "flake": false, 144 | "locked": { 145 | "lastModified": 1736360133, 146 | "narHash": "sha256-CL2JpZYK2P5KUToykzy04SbW9ncH0+xUFL624Jy++LA=", 147 | "owner": "sjhgvr", 148 | "repo": "oisd", 149 | "rev": "b5c7a529dfd7d5967be4db0d7528583ba71a77a1", 150 | "type": "github" 151 | }, 152 | "original": { 153 | "owner": "sjhgvr", 154 | "repo": "oisd", 155 | "type": "github" 156 | } 157 | }, 158 | "root": { 159 | "inputs": { 160 | "daeuniverse": "daeuniverse", 161 | "deploy-rs": "deploy-rs", 162 | "homelab": "homelab", 163 | "nixpkgs": "nixpkgs", 164 | "oisd": "oisd", 165 | "sops-nix": "sops-nix", 166 | "v2ray-rules-dat": "v2ray-rules-dat", 167 | "vscode-server": "vscode-server" 168 | } 169 | }, 170 | "sops-nix": { 171 | "inputs": { 172 | "nixpkgs": [ 173 | "nixpkgs" 174 | ] 175 | }, 176 | "locked": { 177 | "lastModified": 1736203741, 178 | "narHash": "sha256-eSjkBwBdQk+TZWFlLbclF2rAh4JxbGg8az4w/Lfe7f4=", 179 | "owner": "Mic92", 180 | "repo": "sops-nix", 181 | "rev": "c9c88f08e3ee495e888b8d7c8624a0b2519cb773", 182 | "type": "github" 183 | }, 184 | "original": { 185 | "owner": "Mic92", 186 | "repo": "sops-nix", 187 | "type": "github" 188 | } 189 | }, 190 | "systems": { 191 | "locked": { 192 | "lastModified": 1681028828, 193 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 194 | "owner": "nix-systems", 195 | "repo": "default", 196 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 197 | "type": "github" 198 | }, 199 | "original": { 200 | "owner": "nix-systems", 201 | "repo": "default", 202 | "type": "github" 203 | } 204 | }, 205 | "systems_2": { 206 | "locked": { 207 | "lastModified": 1681028828, 208 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 209 | "owner": "nix-systems", 210 | "repo": "default", 211 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 212 | "type": "github" 213 | }, 214 | "original": { 215 | "owner": "nix-systems", 216 | "repo": "default", 217 | "type": "github" 218 | } 219 | }, 220 | "utils": { 221 | "inputs": { 222 | "systems": "systems" 223 | }, 224 | "locked": { 225 | "lastModified": 1701680307, 226 | "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", 227 | "owner": "numtide", 228 | "repo": "flake-utils", 229 | "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", 230 | "type": "github" 231 | }, 232 | "original": { 233 | "owner": "numtide", 234 | "repo": "flake-utils", 235 | "type": "github" 236 | } 237 | }, 238 | "v2ray-rules-dat": { 239 | "flake": false, 240 | "locked": { 241 | "lastModified": 1736287944, 242 | "narHash": "sha256-E9xTE/MdCXPhY8cR58cVM85V/F7kUG4+G2u8gwl4zOc=", 243 | "owner": "Loyalsoldier", 244 | "repo": "v2ray-rules-dat", 245 | "rev": "49d8899193dd75366e2ffaf532d862c0cbc13cf2", 246 | "type": "github" 247 | }, 248 | "original": { 249 | "owner": "Loyalsoldier", 250 | "ref": "release", 251 | "repo": "v2ray-rules-dat", 252 | "type": "github" 253 | } 254 | }, 255 | "vscode-server": { 256 | "inputs": { 257 | "flake-utils": "flake-utils", 258 | "nixpkgs": [ 259 | "nixpkgs" 260 | ] 261 | }, 262 | "locked": { 263 | "lastModified": 1729422940, 264 | "narHash": "sha256-DlvJv33ml5UTKgu4b0HauOfFIoDx6QXtbqUF3vWeRCY=", 265 | "owner": "nix-community", 266 | "repo": "nixos-vscode-server", 267 | "rev": "8b6db451de46ecf9b4ab3d01ef76e59957ff549f", 268 | "type": "github" 269 | }, 270 | "original": { 271 | "owner": "nix-community", 272 | "repo": "nixos-vscode-server", 273 | "type": "github" 274 | } 275 | } 276 | }, 277 | "root": "root", 278 | "version": 7 279 | } 280 | --------------------------------------------------------------------------------