aboutsummaryrefslogtreecommitdiffstats
path: root/netwatch-dns.rsc
blob: 2468d3524148ef42d42d885640d516c4304ce245 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#!rsc by RouterOS
# RouterOS script: netwatch-dns
# Copyright (c) 2022-2024 Christian Hesse <mail@eworm.de>
# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md
#
# requires RouterOS, version=7.12
#
# monitor and manage dns/doh with netwatch
# https://git.eworm.de/cgit/routeros-scripts/about/doc/netwatch-dns.md

:global GlobalFunctionsReady;
:while ($GlobalFunctionsReady != true) do={ :delay 500ms; }

:local Main do={
  :local ScriptName [ :tostr $1 ];

  :global CertificateAvailable;
  :global EitherOr;
  :global LogPrintExit2;
  :global ParseKeyValueStore;
  :global ScriptLock;

  :if ([ $ScriptLock $ScriptName ] = false) do={
    :return false;
  }

  :local SettleTime (5m30s - [ /system/resource/get uptime ]);
  :if ($SettleTime > 0s) do={
    $LogPrintExit2 info $ScriptName ("System just booted, giving netwatch " . $SettleTime . " to settle.") false;
    :return true;
  }

  :local DnsServers ({});
  :local DnsFallback ({});
  :local DnsCurrent [ /ip/dns/get servers ];

  :foreach Host in=[ /tool/netwatch/find where comment~"\\bdns\\b" status="up" ] do={
    :local HostVal [ /tool/netwatch/get $Host ];
    :local HostInfo [ $ParseKeyValueStore ($HostVal->"comment") ];

    :if ($HostInfo->"disabled" != true) do={
      :if ($HostInfo->"dns" = true) do={
        :set DnsServers ($DnsServers, $HostVal->"host");
      }
      :if ($HostInfo->"dns-fallback" = true) do={
        :set DnsFallback ($DnsFallback, $HostVal->"host");
      }
    }
  }

  :if ([ :len $DnsServers ] > 0) do={
    :if ($DnsServers != $DnsCurrent) do={
      $LogPrintExit2 info $ScriptName ("Updating DNS servers: " . [ :tostr $DnsServers ]) false;
      /ip/dns/set servers=$DnsServers;
      /ip/dns/cache/flush;
    }
  } else={
    :if ([ :len $DnsFallback ] > 0) do={
      :if ($DnsFallback != $DnsCurrent) do={
        $LogPrintExit2 info $ScriptName ("Updating DNS servers to fallback: " . \
            [ :tostr $DnsFallback ]) false;
        /ip/dns/set servers=$DnsFallback;
        /ip/dns/cache/flush;
      }
    }
  }

  :local DohCurrent [ /ip/dns/get use-doh-server ];
  :local DohServers ({});

  :foreach Host in=[ /tool/netwatch/find where comment~"\\bdoh\\b" status="up" ] do={
    :local HostVal [ /tool/netwatch/get $Host ];
    :local HostInfo [ $ParseKeyValueStore ($HostVal->"comment") ];
    :local HostName [ /ip/dns/static/find where name address=($HostVal->"host") \
        (!type or type="A" or type="AAAA") !disabled !dynamic ];
    :if ([ :len $HostName ] > 0) do={
      :set HostName [ /ip/dns/static/get ($HostName->0) name ];
    }

    :if ($HostInfo->"doh" = true && $HostInfo->"disabled" != true) do={
      :if ([ :len ($HostInfo->"doh-url") ] = 0) do={
        :set ($HostInfo->"doh-url") ("https://" . [ $EitherOr $HostName ($HostVal->"host") ] . "/dns-query");
      }

      :if ($DohCurrent = $HostInfo->"doh-url") do={
        $LogPrintExit2 debug $ScriptName ("Current DoH server is still up: " . $DohCurrent) false;
        :return true;
      }

      :set ($DohServers->[ :len $DohServers ]) $HostInfo;
    }
  }

  :if ([ :len $DohCurrent ] > 0) do={
    $LogPrintExit2 info $ScriptName ("Current DoH server is down, disabling: " . $DohCurrent) false;
    /ip/dns/set use-doh-server="";
    /ip/dns/cache/flush;
  }

  :foreach DohServer in=$DohServers do={
    :if ([ :len ($DohServer->"doh-cert") ] > 0) do={
      :if ([ $CertificateAvailable ($DohServer->"doh-cert") ] = false) do={
        $LogPrintExit2 warning $ScriptName ("Downloading certificate failed, trying without.") false;
      }
    }

    :local Data false;
    :do {
      :set Data ([ /tool/fetch check-certificate=yes-without-crl output=user \
        http-header-field=({ "accept: application/dns-message" }) \
        url=(($DohServer->"doh-url") . "?dns=" . [ :convert to=base64 ([ :rndstr length=2 ] . \
        "\01\00" . "\00\01" . "\00\00" . "\00\00" . "\00\00" . "\09doh-check\05eworm\02de\00" . \
        "\00\10" . "\00\01") ]) as-value ]->"data");
    } on-error={
      $LogPrintExit2 warning $ScriptName ("Request to DoH server failed (network or certificate issue): " . \
        ($DohServer->"doh-url")) false;
    }

    :if ($Data != false) do={
      :if ([ :typeof [ :find $Data "doh-check-OK" ] ] = "num") do={
        /ip/dns/set use-doh-server=($DohServer->"doh-url") verify-doh-cert=yes;
        /ip/dns/cache/flush;
        $LogPrintExit2 info $ScriptName ("Setting DoH server: " . ($DohServer->"doh-url")) false;
        :return true;
      } else={
        $LogPrintExit2 warning $ScriptName ("Received unexpected response from DoH server: " . \
          ($DohServer->"doh-url")) false;
      }
    }
  }
}

$Main [ :jobname ];