Copyright © KC Green

Dienste mit TOR und Onioncat über das Internet freigeben

 infrastructure   howto 

Mit Hilfe von TOR und Onioncat interne Dienste über das Internet unter Umgehung von NAT verfügbar machen
TOC

In einem recht restriktivem Netzwerk benötigte ich eine Möglichkeit, um auf diverse interne Dienste über das Internet zugreifen zu können. Diesen Anlass nahm ich als Gelegenheit um etwas mit TOR und Onioncat herumzuspielen. Mein primäres Ziel war es, sicheren Zugriff auf den internen Mailserver zu gewähren, sodass man E-Mails abrufen kann ohne direkt mit dem LAN verbunden zu sein.

TOR dient hier lediglich als Transportebene, so ist keine Portweiterleitung im Router notwendig. Onioncat wird als hidden-service bereitgestellt und erzeugt ein Overlay-Netzwerk, welches IPv6 als Protokoll auf Layer 3 verwendet. Die Dienste sind dann später über eine statische IPv6 Adresse via TOR/Onioncat erreichbar.

Info: Niemand benötigt globale RootCAs denen generell Vertrauen geschenkt wird, wenn die Onion-Adresse des hidden-service Endpunkts gleichzeitig dem Identitätsnachweis dient!

Zusätzlich greife ich mit port knocking auf eine Technologie aus den 90’ern zurück, um SSH-Zugriff auf das Gateway zu gewähren - alt aber purer Spaß!

Es gibt jede Menge sichere port-knocking Implementierungen, aber sie alle haben gemein, dass sie einen speziellen Client voraussetzen. Ein einfaches iptables-basiertes Setup is hingegen mit jedem beliebigen Client wie beispielsweise netcat nutzbar.

Als Server/Gateway Hardware nutze ich einen BeagleBone Black auf dem Archlinux läuft.

Übersicht

Vereinfacht lassen sich die Kommunikationswege folgendermaßen darstellen:

+---------+      +---------+
| VLAN #2 |      | VLAN #1 |
|---------|      |---------|
|         |      |         |
|  +----+ | IPv6 | +----+  |
|  | GW +-+--to--+-+ MX |  |
|  +-++-+ | IPv4 | +----+  |
|    ||   |      |         |
+----++---+      +---------+
     ||
+----++--------------------+
|    || T                  |
| IP || O           T +++++++++ USER
| v4 || R          A ++    |
|    ||           C ++     |
|    ++          N ++      |
|     ++        O ++       |
|      ++      I ++        |
|   IP  ++    N ++         |
|    v6  ++  O ++          |
|         ++  ++           |
|          ++++            |
|           ++             |
|                          |
|--------------------------|
|        INTERNET          |
+--------------------------+

Der Mailserver befindet sich in VLAN #2, welches ausschließlich von VLAN #1 zu erreichen ist. Unsere Gateway-Box wird per Ethernet direkt ins VLAN #1 gehangen und dort via DHCP eine IP-Adresse beziehen. Ausgehender Traffic ins Internet ist auf den relevanten Ports (TOR) ungefiltert.

Der TOR Client auf dem Gateway verbindet sich mit dem Onion-Netzwerk und startet den hidden-service. Darauf hin ist das Gateway unter der IPV6-Adresse für andere Onioncat Teilnehmer erreichbar. Sobald ein weiterer Onioncat-Endpunkt bekannt ist, kann dieser mit dem Gateway über IPv6 auf Port 443 kommunizieren und das socat Relay übersetzt den Datenfluss in IPv4 und leitet ihn weiter an den Mailserver in VLAN #2. Btw. fragt nicht, warum der Mailserver auf Port 443 erreichbar ist.. es handelt sich hier um einen Microsoft Exchange ActiveSync Server.

Zur einfacheren Administration des Gateways ist dieses auf zwei Wege per SSH zu erreichen:

  1. Optional via mDNS aus dem lokalen Netzwerk. Dies einfach aus Bequemlichkeit; die IP-Adresse wird per DHCP zugewiesen und wenn das Lease ausläuft, kann sie sich ändern. Dank avahi habe ich einen generischen Hostnamen für das Gateway.

  2. Über TOR/Onioncat.

In beiden Fällen kommt port-knocking zum Tragen. Netcat, vom Client ausgeführt, klopft in bestimmter Reihenfolge an definierte Ports und iptables auf der Gateway-Seite registriert dies und wenn alles passt, wird der SSH Port geöffnet.

Vorzüge dieses Setups

  • Es wird keine statische öffentliche IP-Adresse benötigt
  • Die öffentliche IP-Adresse wird versteckt
  • Es wird via Onioncat eine einzigartige IPv6-Adresse bereitgestellt, die von der hidden-service ID abgeleitet wird
  • DNS ist nicht involviert und leakage sollte damit kein Sicherheitsproblem darstellen

Installation benötigter Pakete

Auf Gateway und Client müssen folgende Pakete installiert werden: tor, nyx, sowie optional avahi aus dem offiziellen Archlinux Paket-Verzeichnis und onioncat aus dem AUR.

$ sudo pacman -S tor nyx
$ yay -S onioncat

Globale Konfiguration

Für Onioncat benötigen wir ein Script, welches von systemd verwendet wird um den Dienst zu starten:

$ sudo vim /usr/local/bin/onioncat
$ sudo chmod 755 /usr/local/bin/onioncat
#!/usr/bin/env bash

set -o errexit
set -o pipefail

__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

if [[ ${EUID} != 0 ]] ; then
  echo "Please run as root!"
 exit 1
fi

tor_path="/var/lib/tor/ocat"

if /bin/systemctl -q is-active tor; then
  while [ ! -f "${tor_path}/hostname" ]; do sleep 1; done
  onion_address=$(cat "${tor_path}/hostname")
  if [[ ! -z "$onion_address" ]]; then
    ocat -B -C $onion_address
  else
    echo "TOR hidden service address still empty. TOR running? Exiting."
    exit 2
  fi
else
  echo "TOR not started / systemd unit not active. Exiting.."
  exit 3
fi

Danach erstellen wir das systemd unitfile:

$ sudo vim /etc/systemd/system/onioncat.service
$ sudo chmod 644 /etc/systemd/system/onioncat.service
Expand to view: /etc/systemd/system/onioncat.service

[Unit]
Description=IPv6 tunnel over TOR
Requires=network-online.target tor.service
After=network-online.target tor.service

[Service] Type=simple ExecStart=/usr/local/bin/onioncat ExecReload=/usr/bin/kill -HUP $MAINPID KillSignal=SIGINT Restart=on-failure RestartSec=10 RestartPreventExitStatus=SIGTERM SIGKILL SyslogIdentifier=onioncat
[Install] WantedBy=multi-user.target

Konfiguration: Gateway

Autostart für TOR und Onioncat aktivieren und Dienste starten:

$ sudo systemctl daemon-reload
$ sudo systemctl enable --now tor
$ sudo systemctl enable --now onioncat

Sobald der TOR Client mit dem Onion-Netzwerk verbunden und der Onioncat hidden-service gestartet ist, kann man sich die einzigartige IPv6-Adresse des Gateways anzeigen lassen:

$ ocat -i $(sudo cat /var/lib/tor/ocat/hostname)

Dieser Befehl wandelt die Onion ID (hidden-service ID) in eine durch Onioncat vergebene IPv6 Adresse um.

IPv6-zu-IPv4 Tunnel

Auf dem Gateway benötigen wir zusätzlich socat und iptables.

$ sudo pacman -S socat iptables

Ein Script welches das socat Relay started und den Datenfluss zum Mailserver weiterleitet.

$ sudo vim /usr/local/bin/ipv6-mx-gate
$ sudo chmod 755 /usr/local/bin/ipv6-mx-gate
Wichtig: fd87:d87e:eb43:b44d:a7ea:73c4:ab0a:2e5b muss durch die jeweilige IPv6-Onioncat-Adresse (s.o.) und 192.168.2.1 durch die IPv4-Adresse des zu erreichenden Dienstes ersetzt werden. Selbstredend müssen ggf. auch eingehender-, bzw. Serviceport angepasst werden.
#!/usr/bin/env bash

ipv6_address="fd87:d87e:eb43:b44d:a7ea:73c4:ab0a:2e5b"
srv_address="192.168.2.1"

while ! ip -6 addr show tun0 | grep -q "${ipv6_address}"; do sleep 1; done
/usr/bin/socat TCP6-LISTEN:443,reuseaddr,fork,bind=[${ipv6_address}] TCP4:${srv_address}:443

Ein weiteres unitfile für den IPv6-zu-IPv4 Tunnel:

$ sudo vim /etc/systemd/system/ipv6-mx-gate.service
$ sudo chmod 644 /etc/systemd/system/ipv6-mx-gate.service
Expand to view: /etc/systemd/system/ipv6-mx-gate.service

[Unit]
Description=IPv6 <-> IPv4 tunnel (for access to mx)
After=onioncat.service

[Service] ExecStart=/usr/local/bin/ipv6-mx-gate
[Install] WantedBy=multi-user.target

Autostart für den Tunnel aktivieren und Dienst starten:

$ sudo systemctl daemon-reload
$ sudo systemctl enable --now ipv6-mx-gate

iptables

Für die Konfiguration der iptables Regeln setze ich folgendes Script ein:

#!/usr/bin/env bash

set -o errexit
set -o pipefail

__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

if [[ ${EUID} != 0 ]] ; then
  echo "Please run as root!"
 exit 1
fi

function flush_rules() {
  $ipt -F               # Delete all rules (flush chains)
  $ipt -X               # Delete all chains
  $ipt -Z               # Zero all packet and byte counters
  $ipt -t nat -F        # Flush and
  $ipt -t nat -X        # delete NAT table
  $ipt -t mangle -F     # Flush and
  $ipt -t mangle -X     # delete MANGLE table
  $ipt -t raw -F        # Flush and
  $ipt -t raw -X        # delete RAW table
  $ipt -t security -F   # Flush and
  $ipt -t security -X   # delete SECURITY table
}

# VARIABLES
# ---------
ipt="$(which iptables)"
ipt_save="$(which iptables-save)"
ipt_restore="$(which iptables-restore)"
rules="/etc/iptables/iptables.rules"
log="LOG --log-level debug --log-tcp-sequence --log-tcp-options --log-ip-options"
rlimit="-m limit --limit 5/m --limit-burst 10"

if [[ $# > 0 ]]; then
  if [[ "$1" =~ ^(-d|--disable)$ ]]; then
    # Accept everything
    $ipt -P INPUT   ACCEPT
    $ipt -P FORWARD ACCEPT
    $ipt -P OUTPUT  ACCEPT
    ip6tables -P INPUT   ACCEPT
    ip6tables -P FORWARD ACCEPT
    ip6tables -P OUTPUT  ACCEPT

    flush_rules
    ip6tables-restore < /etc/iptables/empty.rules # Cleanup ip6tables

    # CONFIGURATION
    #--------------
    echo 0 > /proc/sys/net/ipv4/ip_forward                  # Disable IP forward
    echo 0 > /proc/sys/net/ipv4/icmp_echo_ignore_all        # Enable Ping (echo replies)
    echo 0 > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts # Enable echo reply on broadcasts

    exit 0
  fi
fi

# IPv4 CONFIGURATION
# ==================

# Flushing old rules
flush_rules 'DROP'

# DEFAULT CHAINS
#---------------
$ipt -N DROPLOG
$ipt -A DROPLOG -j $log $rlimit --log-prefix "[IPv4]: "
$ipt -A DROPLOG -j DROP

# CONFIGURATION
#--------------
echo 0 > /proc/sys/net/ipv4/ip_forward                    # Disable IP forward
echo 0 > /proc/sys/net/ipv4/icmp_echo_ignore_all          # Disable Ping (echo replies)
echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts   # Disable echo reply on broadcasts

# DEFAULT RULES
#--------------
$ipt -P OUTPUT ACCEPT                                                        # Allow complete outbound traffic
$ipt -A INPUT -i lo -j ACCEPT                                                # Allows all incoming loopback traffic
$ipt -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT           # Allow incoming connections related to existing allowed connections
$ipt -A INPUT -m conntrack --ctstate INVALID -j DROP                         # Explicitly drop invalid incoming traffic
$ipt -A INPUT -m conntrack --ctstate NEW -p tcp --tcp-flags ALL ALL  -j DROP # Make portscanns
$ipt -A INPUT -m conntrack --ctstate NEW -p tcp --tcp-flags ALL NONE -j DROP # a bit harder.

# INBOUND RULES
#--------------
$ipt -A INPUT -i eth0 -m conntrack --ctstate NEW -p udp  --dport 5353                -j ACCEPT # DNS (Avahi)
$ipt -A INPUT -i eth0 -m conntrack --ctstate NEW -p udp  --dport 67:68 --sport 67:68 -j ACCEPT # DHCP
$ipt -A INPUT -i eth0 -p tcp --dport 63879 -m recent --set --rsource --name MDNS_KNOCK1 -m limit --limit 5/min -j LOG --log-prefix "SSH PORT KNOCK STATE0 (MDNS) " --log-level 7
$ipt -A INPUT -i eth0 -p tcp --dport 36978 -m recent --rcheck --rsource --seconds 5 --name MDNS_KNOCK1 -m recent --set --rsource --name MDNS_OPEN_DOOR -m limit --limit 5/min -j LOG --log-prefix "SSH PORT KNOCK STATE1 (MDNS) " --log-level 6
$ipt -A INPUT -i eth0 -p tcp --dport 22 -m state --state NEW -m recent --rcheck --rsource --seconds 15 --name MDNS_OPEN_DOOR -j ACCEPT

#$ipt -A INPUT -i wg+ -p icmp --icmp-type 8 -j ACCEPT # ICMP on Wireguard interface
#$ipt -A INPUT -i wg+ -d 192.168.28.1 -p tcp --dport 42341 -m recent --set --rsource --name WG_KNOCK1 -m limit --limit 5/min -j LOG --log-prefix "SSH PORT KNOCK STATE0 (WG) " --log-level 7
#$ipt -A INPUT -i wg+ -d 192.168.28.1 -p tcp --dport 24143 -m recent --rcheck --rsource --seconds 5 --name WG_KNOCK1 -m recent --set --rsource --name WG_OPEN_DOOR -m limit --limit 5/min -j LOG --log-prefix "SSH PORT KNOCK STATE1 (WG) " --log-level 6
#$ipt -A INPUT -i wg+ -d 192.168.28.1 -p tcp --dport 22 -m state --state NEW -m recent --rcheck --rsource --seconds 15 --name WG_OPEN_DOOR -j ACCEPT # Port knocking (open)

# EXPLICITLY DROP EVERYTHING ELSE
#--------------------------------
$ipt -A INPUT -m conntrack --ctstate NEW -p 2 -j DROP                #
$ipt -A INPUT -m conntrack --ctstate NEW -p udp --dport 137  -j DROP # Drop some very
$ipt -A INPUT -m conntrack --ctstate NEW -p udp --dport 138  -j DROP # annoying packets
$ipt -A INPUT -m conntrack --ctstate NEW -p udp --dport 5355 -j DROP # without logging
$ipt -A INPUT -m conntrack --ctstate NEW -p udp --dport 1900 -j DROP #
$ipt -A INPUT   -j DROPLOG # Log dropped packets
$ipt -P INPUT      DROP    # Drop logged packets
$ipt -A FORWARD -j DROPLOG
$ipt -P FORWARD    DROP

# Save IPv4 configuration
$ipt_save > $rules

# IPv6 CONFIGURATION
# ==================

# VARIABLES
# ---------
ipt="$(which ip6tables)"
ipt_save="$(which ip6tables-save)"
ipt_restore="$(which ip6tables-restore)"
rules="/etc/iptables/ip6tables.rules"

# CLEANUP
#--------
flush_rules 'DROP'

# DEFAULT CHAINS
#---------------
$ipt -N DROPLOG
$ipt -A DROPLOG -j $log $rlimit --log-prefix "[IPv6]: "
$ipt -A DROPLOG -j DROP

# DEFAULT RULES
#--------------
$ipt -P OUTPUT ACCEPT                                                        # Allow complete outbound traffic
$ipt -A INPUT -i lo -j ACCEPT                                                # Allows all incoming loopback traffic
$ipt -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT           # Allow incoming connections related to existing allowed connections
$ipt -A INPUT -m conntrack --ctstate INVALID -j DROP                         # Explicitly drop invalid incoming traffic
$ipt -A INPUT -m conntrack --ctstate NEW -p tcp --tcp-flags ALL ALL  -j DROP # Make portscanns
$ipt -A INPUT -m conntrack --ctstate NEW -p tcp --tcp-flags ALL NONE -j DROP # a bit harder.

# INBOUND RULES
#--------------
$ipt -A INPUT -p icmpv6 -d fd87:d87e:eb43:b44d:a7ea:73c4:ab0a:2e5b --icmpv6-type 128 -j ACCEPT # ICMPv6 Ping
$ipt -A INPUT -i tun+ -d fd87:d87e:eb43:b44d:a7ea:73c4:ab0a:2e5b -p tcp --dport 443  -j ACCEPT # Forwarded to MX using socat

$ipt -A INPUT -i tun+ -d fd87:d87e:eb43:b44d:a7ea:73c4:ab0a:2e5b -p tcp --dport 63725 -m recent --set --rsource --name OCAT_KNOCK1 -m limit --limit 5/min -j LOG --log-prefix "SSH PORT KNOCK STATE0 (OCAT) " --log-level 7
$ipt -A INPUT -i tun+ -d fd87:d87e:eb43:b44d:a7ea:73c4:ab0a:2e5b -p tcp --dport 36527 -m recent --rcheck --rsource --seconds 5 --name OCAT_KNOCK1 -m recent --set --rsource --name OCAT_OPEN_DOOR -m limit --limit 5/min -j LOG --log-prefix "SSH PORT KNOCK STATE1 (OCAT) " --log-level 6
$ipt -A INPUT -i tun+ -d fd87:d87e:eb43:b44d:a7ea:73c4:ab0a:2e5b -p tcp --dport 22 -m state --state NEW -m recent --rcheck --rsource --seconds 15 --name OCAT_OPEN_DOOR -j ACCEPT
#$ipt -A INPUT -i tun+ -d fd87:d87e:eb43:b44d:a7ea:73c4:ab0a:2e5b -p tcp --dport 5555 --sport 5555 -m state --state NEW -m recent --rcheck --rsource --seconds 15 --name OCAT_OPEN_DOOR -j ACCEPT # Wireguard - Port knocking (open)

# EXPLICITLY DROP EVERYTHING ELSE
#--------------------------------
$ipt -A INPUT -p icmpv6 --icmpv6-type 136 -j DROP # Drop
$ipt -A INPUT -p icmpv6 --icmpv6-type 135 -j DROP # some 
$ipt -A INPUT -p icmpv6 --icmpv6-type 133 -j DROP # very
$ipt -A INPUT -p udp --dport 546 -j DROP          # annoying
$ipt -A INPUT -p udp --dport 5353 -j DROP         # pakets silently
$ipt -A INPUT -j DROPLOG   # Log dropped packets
$ipt -P INPUT    DROP      # Drop logged packets
$ipt -A FORWARD -j DROPLOG
$ipt -P FORWARD    DROP

# Save IPv6 configuration and exit gracefully:
$ipt_save > $rules && exit 0

Konfiguration: Client

Ein Autostart von TOR ist auf dem Client sicherlich nicht zwingend erforderlich, hier genügt es die Dienste manuell zu starten:

$ sudo systemctl start tor
$ sudo systemctl start onioncat

Sobald TOR verbunden ist, sollte kurz darauf auch der Onioncat-Dienst verfügbar und somit die IPv6-Adresse des Gateways erreichbar sein.

Bei einem initialen Verbindungsaufbau dürfte in etwa folgendes im systemd journal zu lesen sein.

Die hidden-service ID meines Gateways lautet hier wrg2p2ttysvquls3.onion und die Onioncat Adresse fd87:d87e:eb43:b44d:a7ea:73c4:ab0a:2e5b.

$ sudo journalctl -f -u onioncat

[4:connector :  info] trying to connect to "wrg2p2ttysvquls3.onion" [fd87:d87e:eb43:b44d:a7ea:73c4:ab0a:2e5b] on 11
[4:connector :  info] SOCKS connection successfully opened on fd 11
[4:connector :  info] inserting peer fd 11 to active peer list
[3:acceptor  :  info] connection 12 [0] accepted on listener 6 from 127.0.0.1 port 57330
[3:acceptor  :  info] inserting peer fd 12 to active peer list

Der Verbindungsaufbau kann je nach Bandbreite beider Parteien (bzw. Qualität der Onion-Netzwerkverbindung) mehrere Sekunden (mitunter sogar Minuten) in Anspruch nehmen. Danach werden Ping-Anfragen normal beantwortet:

$ ping fd87:d87e:eb43:b44d:a7ea:73c4:ab0a:2e5b

PING fd87:d87e:eb43:b44d:a7ea:73c4:ab0a:2e5b(fd87:d87e:eb43:b44d:a7ea:73c4:ab0a:2e5b) 56 data bytes
64 bytes from fd87:d87e:eb43:b44d:a7ea:73c4:ab0a:2e5b: icmp_seq=3 ttl=64 time=367 ms
64 bytes from fd87:d87e:eb43:b44d:a7ea:73c4:ab0a:2e5b: icmp_seq=4 ttl=64 time=306 ms
64 bytes from fd87:d87e:eb43:b44d:a7ea:73c4:ab0a:2e5b: icmp_seq=5 ttl=64 time=367 ms

Wenn eine Weile keine Daten ausgetauscht wurden, kann es bei erneutem Verbindungsaufbau wieder etwas dauern. Hier hilft es u.U. das Journal im Auge zu behalten, um andere Fehler auszuschließen.

Wenn der Onioncat-Tunnel besteht sollte der freigegebene, interne Dienst - in meinem Fall - auf Port 443 erreichbar sein:

$ curl -ks https://[fd87:d87e:eb43:b44d:a7ea:73c4:ab0a:2e5b]:443

port-knocking

Um sich per SSH zum Gateway verbinden zu können, habe ich ein kleines Script geschrieben, dass mir die ganze manuelle Arbeit abnimmt. Zuerst wird versucht eine Verbindung via mDNS aufzubauen, hierfür wird der avahi-daemon gestartet. Wenn der Avahi-Hostname aoe.local innerhalb des angegebenen Timeout-Wertes nicht aufgelöst werden kann, werden die TOR und Onioncat Dienste ausgeführt. Sobald ein Onioncat-Tunnel zustande gekommen ist, wird versucht eine SSH-Verbindung zu öffnen. Optional kann zusätzlich noch ein VPN (Wireguard) verbunden werden, das weitere Konfigurationen auf Gateway und Client voraussetzt.

knock.sh

#!/usr/bin/env bash
# shellcheck disable=SC2034

declare -A MDNS=( [addr]='aoe.local' [ports]='63879,36978' [timeout]='5' [deps]='avahi-daemon.service' )
declare -A ONIONCAT=( [addr]='fd87:d87e:eb43:b44d:a7ea:73c4:ab0a:2e5b' [ports]='63725,36527' [timeout]='300' [deps]='tor,onioncat' )
declare -A WIREGUARD=( [addr]='192.168.28.1' [ports]='42341,24143' [timeout]='100' [deps]='wireguard-client@wg0' )

SSH='ssh -qo UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
PROTOCOLS=('MDNS' 'ONIONCAT' 'WIREGUARD')

function check_deps() {
  declare -n _proto="$1" # MDNS|ONIONCAT|WIREGUARD
  IFS=',' read -ra _services <<< "${_proto[deps]}"

  for service in "${_services[@]}"; do
    if ! systemctl -q is-active "$service"; then
      sudo systemctl start "$service"
    fi
  done
}

function knock() {
  local _proto="$1" # 4|6
  local _ip="$2"
  IFS=',' read -ra _ports <<< "$3"

  echo -n "Knocking on ${_ip}: "
  i=0
  for port in "${_ports[@]}"; do
    nc "-${_proto}" -w 1 "$_ip" "$port" >/dev/null 2>&1
    ((i++))
    if [[ "$i" -ne "${#_ports[@]}" ]]; then
      echo -n "${port}, "
    else
      echo "${port}"
    fi
  done
}

function connect() {
  local _proto="$1" # MDNS|ONIONCAT|WIREGUARD
  declare -n _params="$_proto"
  local _t

  case "$_proto" in
    MDNS)
      echo -n 'Trying to resolve hostname using mDNS..'
      res=$(timeout "${_params[timeout]}" avahi-resolve-host-name -4 -n "${_params[addr]}" 2>&1 | awk '{print $2}')
      if [[ "$res" == *to* ]] || [[ "$res" == *SIGTERM,* ]]; then
        echo 'failed'
        return 1
      else
        echo 'resolved'
        knock 4 "$res" "${_params[ports]}"

        $SSH "$USER@${res}"
        exit 0
      fi
    ;;
    ONIONCAT)
      echo -n 'Initialising onioncat tunnel..'
      _t=0
      until ping -6 -w1 -c1 "${_params[addr]}" >/dev/null 2>&1; do
        echo -n '.' && ((_t++))
        if [[ "$_t" -eq "${_params[timeout]}" ]]; then
          echo 'failed'
          echo "Error: Unable to establish IPv6 (ocat) tunnel"
          return 2
        fi
      done && echo 'done'

      knock 6 "${_params[addr]}" "${_params[ports]}"

      $SSH "$USER@${_params[addr]}"
      exit 0
    ;;
    WIREGUARD)
      echo -n 'Initialising VPN..'
      _t=0
      until ping -w1 -c1 "${_params[addr]}" >/dev/null 2>&1; do
        echo -n '.' && ((t++))
        if [[ "$_t" -eq "${_params[timeout]}" ]]; then
          echo 'failed'
          echo "Error: Unable to establish VPN (WireGuard) connection"
          return 3
        fi
      done && echo 'done'

      knock 6 "${_params[addr]}" "${_params[ports]}"

      $SSH "$USER@${_params[addr]}"
      exit 0
    ;;
  esac
}

for proto in "${PROTOCOLS[@]}"; do
  check_deps "$proto"
  connect "$proto"
done

Um sich per SSH zum Gateway zu verbinden, genügt es das Script auszuführen:

Bzw. wenn man sich im gleichen Netzwerk wie das Gateway befindet:

Das ganze Setup ist natürlich etwas over-engineered, aber es ging hierbei um einen POC zur Machbarkeit (wobei natürlich der Spaß im Vordergrund stand).