diff --git a/examples/l2tp.nix b/examples/l2tp.nix index 169e62f..12441b2 100644 --- a/examples/l2tp.nix +++ b/examples/l2tp.nix @@ -6,7 +6,26 @@ }: let secrets = import ./extneder-secrets.nix; rsecrets = import ./rotuer-secrets.nix; - lns = "l2tp.aaisp.net.uk"; + + # https://support.aa.net.uk/Category:Incoming_L2TP says: + # "Please use the DNS name (l2tp.aa.net.uk) instead of hardcoding an + # IP address; IP addresses can and do change. If you have to use an + # IP, use 194.4.172.12, but do check the DNS for l2tp.aa.net.uk in + # case it changes." + + # but (1) we don't want to use the wwan stick's dns as our main + # resolver: it's provided by some mobile ISP and they aren't + # necessarily the best at providing unfettered services without + # deciding to do something weird; (2) it's not simple to arrange + # that xl2tpd gets a different resolver than every other process; + # (3) there's no way to specify an lns address to xl2tpd at runtime + # except by rewriting its config file. So what we will do is lookup + # the lns hostname using the mobile ISP's dns server and then refuse + # to start l2tp unless the expected lns address is one of the + # addresses returned. I think this satisfies "do check the DNS" + + lns = { hostname = "l2tp.aaisp.net.uk"; address = "194.4.172.12"; }; + inherit (pkgs.liminix.services) oneshot longrun bundle target; inherit (pkgs.pseudofile) dir symlink; inherit (pkgs) writeText dropbear ifwait serviceFns; @@ -46,46 +65,75 @@ in rec { services.sshd = svc.ssh.build { }; services.resolvconf = oneshot rec { - dependencies = [ services.dhcpc ]; + dependencies = [ services.l2tp ]; name = "resolvconf"; up = '' . ${serviceFns} - ( in_outputs ${name} - for i in $(output ${services.dhcpc} dns); do - echo "nameserver $i" > resolv.conf - done - ) + ( in_outputs ${name} + for i in ns1 ns2 ; do + ns=$(output ${services.l2tp} $i) + echo "nameserver $ns" >> resolv.conf + done + ) ''; }; filesystem = dir { etc = dir { "resolv.conf" = symlink "${services.resolvconf}/.outputs/resolv.conf"; }; - srv = dir {}; }; - services.lnsroute = svc.network.route.build { - via = "$(output ${services.dhcpc} router)"; - target = lns; - dependencies = [services.dhcpc]; + services.lns-address = let + ns = "$(output_word ${services.dhcpc} dns 1)"; + route-to-bootstrap-nameserver = svc.network.route.build { + via = "$(output ${services.dhcpc} router)"; + target = ns; + dependencies = [services.dhcpc]; + }; + in oneshot rec { + name = "resolve-l2tp-server"; + dependencies = [ services.dhcpc route-to-bootstrap-nameserver ]; + up = '' + (in_outputs ${name} + DNSCACHEIP="${ns}" ${pkgs.s6-dns}/bin/s6-dnsip4 ${lns.hostname} \ + > addresses + ) + ''; }; - services.l2tp = svc.l2tp.build { - inherit lns; - ppp-options = [ - "debug" "+ipv6" "noauth" - "name" rsecrets.l2tp.name - "password" rsecrets.l2tp.password - ]; - dependencies = [ services.lnsroute ]; + services.l2tp = + let + check-address = oneshot rec { + name = "check-lns-address"; + up = '' + grep -Fx ${lns.address} $(output_path ${services.lns-address} addresses) + ''; + dependencies = [ services.lns-address ]; + }; + route = svc.network.route.build { + via = "$(output ${services.dhcpc} router)"; + target = lns.address; + dependencies = [services.dhcpc check-address]; + }; + in svc.l2tp.build { + lns = lns.address; + ppp-options = [ + "debug" "+ipv6" "noauth" + "name" rsecrets.l2tp.name + "connect-delay" "5000" + "password" rsecrets.l2tp.password + ]; + dependencies = [config.services.lns-address route check-address]; }; services.defaultroute4 = svc.network.route.build { - via = "$(output ${services.l2tp} router)"; + via = "$(output ${services.l2tp} peer-address)"; target = "default"; dependencies = [services.l2tp]; }; +# defaultProfile.packages = [ pkgs.go-l2tp ]; + users.root = { passwd = lib.mkForce secrets.root.passwd; openssh.authorizedKeys.keys = secrets.root.keys; diff --git a/modules/network/route.nix b/modules/network/route.nix index 0c93313..6a6644a 100644 --- a/modules/network/route.nix +++ b/modules/network/route.nix @@ -8,8 +8,10 @@ let inherit (liminix.services) oneshot; with_dev = if interface != null then "dev $(output ${interface} ifname)" else ""; + target_hash = builtins.substring 0 12 (builtins.hashString "sha256" target); + via_hash = builtins.substring 0 12 (builtins.hashString "sha256" via); in oneshot { - name = "route-${target}-${builtins.substring 0 12 (builtins.hashString "sha256" "${via}-${if interface!=null then interface.name else ""}")}"; + name = "route-${target_hash}-${builtins.substring 0 12 (builtins.hashString "sha256" "${via_hash}-${if interface!=null then interface.name else ""}")}"; up = '' ip route add ${target} via ${via} metric ${toString metric} ${with_dev} ''; diff --git a/pkgs/service-fns/default.nix b/pkgs/service-fns/default.nix index 1966396..b2c1ad8 100644 --- a/pkgs/service-fns/default.nix +++ b/pkgs/service-fns/default.nix @@ -1,6 +1,18 @@ {writeText}: writeText "service-fns.sh" '' output() { cat $1/.outputs/$2; } + output_word() { + set -f + local i=1 + for var in $(cat $1/.outputs/$2); do + if test "$i" == "$3" ; then + echo $var + fi + i=$(expr $i + 1) + done + set +f + } + output_path() { echo $(realpath $1/.outputs)/$2; } SERVICE_OUTPUTS=/run/services/outputs SERVICE_STATE=/run/services/state