Skip to content

Instantly share code, notes, and snippets.

@Medium1992
Last active July 2, 2026 16:37
Show Gist options
  • Select an option

  • Save Medium1992/b6aab2ff51153185b1c5793fa73713d0 to your computer and use it in GitHub Desktop.

Select an option

Save Medium1992/b6aab2ff51153185b1c5793fa73713d0 to your computer and use it in GitHub Desktop.
Script for Restoring Native WireGuard Functionality in MikroTik RouterOS / Скрипт для восстановления работоспособности нативного WireGuard в MikroTik RouterOS

AmneziaWG UDP knock script for MikroTik RouterOS

Русский

Этот скрипт для MikroTik RouterOS отправляет перед стартом WireGuard/AmneziaWG handshake набор UDP-пакетов на endpoint peer-а:

  • пользовательские signature-пакеты из массива ipackets;
  • затем Jc случайных junk-пакетов размером от Jmin до Jmax;
  • после этого peer включается обратно, и обычный WireGuard handshake уходит уже штатным способом.

Скрипт рассчитан на RouterOS v7 и использует /tool traffic-generator inject.

Что нужно настроить

В начале script.rsc:

:local Jc 4
:local Jmin 40
:local Jmax 70
:local TTL 64
:local UDPChecksumMode "calc"

:local i1 "..."
:local i2 ""
:local i3 ""
:local i4 ""
:local i5 ""

:local ipackets {$i1;$i2;$i3;$i4;$i5}

Параметры:

  • Jc - сколько случайных junk-пакетов отправлять.
  • Jmin, Jmax - минимальный и максимальный размер junk payload в байтах.
  • TTL - TTL в вручную собранном IPv4-пакете.
  • UDPChecksumMode:
    • "calc" - считать UDP checksum. Это основной режим.
    • "zero" - писать UDP checksum 0000. Для IPv4 это разрешено и может помочь в диагностике raw-inject/NAT/offload проблем.
  • i1..i5 - примеры signature-пакетов. Пустое значение пропускается.
  • ipackets - массив пакетов, которые будут отправлены перед junk. Можно расширить его до любого разумного количества элементов:
:local i6 "<r 64>"
:local i7 "<b 0xdeadbeef><t><rd 8>"
:local ipackets {$i1;$i2;$i3;$i4;$i5;$i6;$i7}

Также можно писать пакеты прямо в массив:

:local ipackets {"<b 0xdeadbeef>";"<r 64>";"<b 0xcafe><rc 10>"}

Как выбрать peer

Скрипт обрабатывает только WireGuard peers с:

comment=BYPASS

То есть нужному peer-у надо поставить comment BYPASS.

Форматы signature-пакетов

Поддерживаются два варианта.

Готовый HEX:

:local i1 "c3000000..."

AmneziaWG-like tags:

:local i1 "<b 0xc3000000><t><r 10>"
:local i2 "<b 0xf6ab3267fa><rc 10>"
:local i3 "<b 0xf6ab3267fa><rd 10>"

Теги:

  • <b 0xHEX> - статические bytes.
  • <r N> - N случайных bytes.
  • <rc N> - N случайных ASCII letters [a-zA-Z].
  • <rd N> - N случайных ASCII digits [0-9].
  • <t> - 4-byte Unix timestamp, big-endian, как в amneziawg-go.

Важно: готовый HEX (строка без тегов <...>) минует парсер тегов и идёт прямо в сборку пакета. Это важно для очень больших пакетов: они не прогоняются через посимвольный разбор, а используются как есть.

Для очень больших статических пакетов предпочтительнее raw HEX вместо одиночного <b 0x...>. Если RouterOS/Winbox обрезает слишком длинную строку, разбейте её на части и соберите переменную конкатенацией:

:local i2a "ce0000000108..."
:local i2b "ed38da58ac29..."
:local i2 ($i2a . $i2b)

Скрипт проверяет одиночный <b ...> на закрывающий >; если строка была обрезана, такой пакет не будет отправлен частично.

Как это работает

Для каждого peer-а с comment=BYPASS скрипт:

  1. Проверяет last-handshake.
  2. Резолвит endpoint:
    • если endpoint уже IPv4, использует его напрямую;
    • если это домен, делает :resolve.
  3. Меняет listen-port WireGuard interface на случайный порт.
  4. Пингует endpoint, чтобы определить исходный IPv4 через conntrack.
  5. Проверяет route до endpoint и out-interface.
  6. Временно выключает peer.
  7. Инжектит пакеты из ipackets, затем Jc junk-пакетов.
  8. Включает peer обратно.

Важные замечания

  • Скрипт сейчас IPv4-only.
  • IPv6 пока не реализован: там другой pseudo-header, обязательный UDP checksum и отдельная логика route/neighbor.
  • На ether, bridge, vlan скрипт собирает L2 Ethernet header. Если ARP-записи для шлюза нет, он сначала пингует шлюз, чтобы её создать; если MAC так и не резолвится, peer пропускается с ошибкой No ARP entry for gateway.
  • Сбой на одном peer-е (не срезолвился endpoint, нет conntrack/route/ARP) не прерывает остальных: такой peer логируется как Skipping peer ... и включается обратно, а цикл идёт к следующему.
  • На PPPoE у автора сценарий работает с пустым eth, через inject на out-interface. Если у вас другой тип WAN и пакеты не выходят, проверяйте /tool sniffer.
  • Не ставьте Jmax больше path MTU. Большие UDP-пакеты могут фрагментироваться и выглядеть подозрительно.
  • Если endpoint задан доменом 4-го уровня, он должен корректно обрабатываться через :resolve.

Диагностика

Если не пробивает:

  1. Проверьте, что peer имеет comment=BYPASS.
  2. Посмотрите в логах Endpoint IP, srcip, outInterface.
  3. Убедитесь, что есть строки fullpacket.
  4. Первый fullpacket должен быть большим, если задан большой i1.
  5. Попробуйте временно:
:local UDPChecksumMode "zero"

Если "zero" пробивает, а "calc" нет, значит проблема связана с checksum или изменением пакета на пути raw inject/NAT/offload.


English

This MikroTik RouterOS script sends a set of UDP packets to a WireGuard/AmneziaWG peer endpoint before the real handshake starts:

  • custom signature packets from the ipackets array;
  • then Jc random junk packets sized between Jmin and Jmax;
  • then the peer is enabled again, so the normal WireGuard handshake is sent by RouterOS.

The script targets RouterOS v7 and uses /tool traffic-generator inject.

Configuration

At the top of script.rsc:

:local Jc 4
:local Jmin 40
:local Jmax 70
:local TTL 64
:local UDPChecksumMode "calc"

:local i1 "..."
:local i2 ""
:local i3 ""
:local i4 ""
:local i5 ""

:local ipackets {$i1;$i2;$i3;$i4;$i5}

Parameters:

  • Jc - number of random junk packets.
  • Jmin, Jmax - minimum and maximum junk payload size in bytes.
  • TTL - TTL for the manually built IPv4 packet.
  • UDPChecksumMode:
    • "calc" - calculate UDP checksum. This is the default mode.
    • "zero" - write UDP checksum as 0000. This is valid for IPv4 and useful for diagnosing raw-inject/NAT/offload issues.
  • i1..i5 - example custom signature packets. Empty values are skipped.
  • ipackets - packet array sent before junk packets. It can be extended to any reasonable number of elements:
:local i6 "<r 64>"
:local i7 "<b 0xdeadbeef><t><rd 8>"
:local ipackets {$i1;$i2;$i3;$i4;$i5;$i6;$i7}

You can also put packet definitions directly into the array:

:local ipackets {"<b 0xdeadbeef>";"<r 64>";"<b 0xcafe><rc 10>"}

Peer selection

The script processes only WireGuard peers with:

comment=BYPASS

So the target peer must have comment BYPASS.

Signature Packet Formats

Two formats are supported.

Raw HEX:

:local i1 "c3000000..."

AmneziaWG-like tags:

:local i1 "<b 0xc3000000><t><r 10>"
:local i2 "<b 0xf6ab3267fa><rc 10>"
:local i3 "<b 0xf6ab3267fa><rd 10>"

Tags:

  • <b 0xHEX> - static bytes.
  • <r N> - N random bytes.
  • <rc N> - N random ASCII letters [a-zA-Z].
  • <rd N> - N random ASCII digits [0-9].
  • <t> - 4-byte Unix timestamp, big-endian, matching amneziawg-go.

Important: raw HEX (a string with no <...> tags) bypasses the tag parser and goes straight to packet assembly. This matters for very large packets: they are used as-is instead of being walked character by character.

For very large static packets, prefer raw HEX over a single <b 0x...> tag. If RouterOS/Winbox truncates a very long string literal, split it into chunks and concatenate them:

:local i2a "ce0000000108..."
:local i2b "ed38da58ac29..."
:local i2 ($i2a . $i2b)

The script checks a single <b ...> tag for the closing >; if the string was truncated, the partial packet is not sent.

How it works

For every peer with comment=BYPASS, the script:

  1. Checks last-handshake.
  2. Resolves the endpoint:
    • if it is already an IPv4 address, it is used directly;
    • if it is a hostname, :resolve is used.
  3. Changes the WireGuard interface listen-port to a random port.
  4. Pings the endpoint to discover the local source IPv4 through conntrack.
  5. Checks the route and outgoing interface.
  6. Temporarily disables the peer.
  7. Injects packets from ipackets, then Jc junk packets.
  8. Enables the peer again.

Notes

  • The current script is IPv4-only.
  • IPv6 is not implemented yet: it needs a different pseudo-header, mandatory UDP checksum, and separate route/neighbor logic.
  • For ether, bridge, and vlan, the script builds an Ethernet L2 header. If there is no ARP entry for the gateway, it first pings the gateway to create one; if the MAC still cannot be resolved, the peer is skipped with a No ARP entry for gateway error.
  • A failure on one peer (endpoint not resolved, missing conntrack/route/ARP) does not abort the others: that peer is logged as Skipping peer ... and re-enabled, and the loop moves on to the next one.
  • PPPoE is known to work in the author's setup with an empty eth prefix and injection on the outgoing interface. For other WAN types, verify actual packet egress with /tool sniffer.
  • Avoid setting Jmax above path MTU. Large UDP packets may fragment and look suspicious.
  • Hostnames with multiple labels, including 4th-level domains, should work through :resolve.

Troubleshooting

If it does not work:

  1. Check that the peer has comment=BYPASS.
  2. Check log values for Endpoint IP, srcip, and outInterface.
  3. Make sure fullpacket log entries appear.
  4. If i1 is large, the first fullpacket should also be large.
  5. Temporarily try:
:local UDPChecksumMode "zero"

If "zero" works but "calc" does not, the issue is likely related to checksum calculation or packet mutation along the raw inject/NAT/offload path.

📄 Script / Скрипт

Check whether traffic-gen is enabled / Проверка включен ли traffic-gen

/system/device-mode/print

Enable traffic-gen / Включение traffic-gen

/system/device-mode/update traffic-gen=yes

Peers that should be skipped must be marked with the BYPASS comment / Нужные пиры комментировать BYPASS

:local Jc 4
:local Jmin 40
:local Jmax 70
:local TTL 64
:local UDPChecksumMode "calc"
:local DebugIparser false

:local i1 "c3000000010870ac9c05f49d2bff0341d26000421578ace2b50e80d3a3e8b2c2e2e5f50aebf6f7364c9fbed6be8c14606445db7e5c0f75b825ffc3d872b3c463422f0c6334b45a1d1297ee2abda6150110864de45ade52b8a2e33a7c4db399678ccb0501ce14696ae1de40c350293d31db073976e3eae493500358df59b6e16867d4c39ff670168bf0ab50e43aa0fc0814c0762227ff93f334522d9562142dcdef7241b554bfe2c27a3ab066d516f4d31a47526318c644e15d90e98899e25c0ce8a67e14df149769c3d14833d27a25e25fde8afd68f587cc573e8c88e502793b50626f4c5267a5786b2903172a0ef4eea2fa282a02e3d3385d598baa9cacb9395d6c43c5ccbbdce9845a39ded847779f00c44cf5df34f3ad2a22e63504316b748eabacb3b03a1cc3df9c8d6ab60a0255b7f8d433d6d0a671b5cf30a0af2c04a7138cc1b264382e164ebbbcc290176ac9d6672e57cac55effa9df991a0ec1b4ed63910432ff03b187c3a22206c6a4914e16d59e36f011a08f03f3ac7baed06a884f9fa3ee84ab2d097d4863f84edc87b624ca9aeafcec920339d3addc7b5fae21e59cc47c58147b244300ad857e71b8cb9772c4fed8a7a775744f0d8448c70a491e3a7fa5a98c0997be9319a32495011cafb4c2f9b3ade1ef1a5efbc00dd7374e5ea0226d62934a2847c55c0d524337d4073557e96b9ff177414ef03945503fb7c6149db4c3f4a449e70363fe259360de0df0d194f43a44dd364acadb6683262927e1b3dbcbb8e8a610ab0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
:local i2 "<b 0xce000000010897a2>"
:local i3 "<b 0xf6ab3267fa><t><r 10>"
:local i4 "<b 0xf6ab3267fa><rc 12>"
:local i5 "<b 0xf6ab3267fa><rd 12>"

:local IpacketCount 5

:local tohex do={
  :local n $1
  :local h ""
  :while ($n>0) do={
    :local r ($n % 16)
    :set h ([:pick "0123456789abcdef" $r ($r+1)] . $h)
    :set n ($n / 16)
  }
  :return $h
}

:local mac2hex do={
    :local m $1
    :set m ([:pick $m 0 2].[:pick $m 3 5].[:pick $m 6 8].[:pick $m 9 11].[:pick $m 12 14].[:pick $m 15 17])
    :return [:convert transform=lc $m]
}

:local hex16 do={
    :local n [:tonum $1]
    :local hi ($n >> 8)
    :local lo ($n & 255)

    :local digits "0123456789abcdef"

    :local h1 [:pick $digits ($hi >> 4) (($hi >> 4) + 1)]
    :local h2 [:pick $digits ($hi & 15) (($hi & 15) + 1)]
    :local h3 [:pick $digits ($lo >> 4) (($lo >> 4) + 1)]
    :local h4 [:pick $digits ($lo & 15) (($lo & 15) + 1)]

    :return ($h1 . $h2 . $h3 . $h4)
}

:local hex8 do={
    :local n [:tonum $1]
    :local digits "0123456789abcdef"

    :local h1 [:pick $digits ($n >> 4) (($n >> 4) + 1)]
    :local h2 [:pick $digits ($n & 15) (($n & 15) + 1)]

    :return ($h1 . $h2)
}

:local ip2hex do={
    :local ip [:tostr $1]
    :local digits "0123456789abcdef"
    :local out ""
    
    :local p1 [:find $ip "."]
    :local o1 [:tonum [:pick $ip 0 $p1]]
    :set out ([:pick $digits ($o1 >> 4) (($o1 >> 4)+1)] . [:pick $digits ($o1 & 15) (($o1 & 15)+1)])
    
    :local p2 [:find $ip "." ($p1 + 1)]
    :local o2 [:tonum [:pick $ip ($p1 + 1) $p2]]
    :set out ($out . [:pick $digits ($o2 >> 4) (($o2 >> 4)+1)] . [:pick $digits ($o2 & 15) (($o2 & 15)+1)])
    
    :local p3 [:find $ip "." ($p2 + 1)]
    :local o3 [:tonum [:pick $ip ($p2 + 1) $p3]]
    :set out ($out . [:pick $digits ($o3 >> 4) (($o3 >> 4)+1)] . [:pick $digits ($o3 & 15) (($o3 & 15)+1)])
    
    :local o4 [:tonum [:pick $ip ($p3 + 1) [:len $ip]]]
    :set out ($out . [:pick $digits ($o4 >> 4) (($o4 >> 4)+1)] . [:pick $digits ($o4 & 15) (($o4 & 15)+1)])
    
    :return $out
}

:local ipchecksum do={
    :local header $1
    :local sum 0

    :for j from=0 to=9 do={
        :local offset ($j * 4)
        :local word [:pick $header $offset ($offset + 4)]
        :local val [:tonum ("0x" . $word)]
        :set sum ($sum + $val)
    }

    :while ($sum > 65535) do={
        :set sum (($sum & 65535) + ($sum >> 16))
    }

    :local checksum (65535 - $sum)

    :local hi ($checksum >> 8)
    :local lo ($checksum & 255)

    :local digits "0123456789abcdef"

    :local h1 [:pick $digits ($hi >> 4) (($hi >> 4) + 1)]
    :local h2 [:pick $digits ($hi & 15) (($hi & 15) + 1)]
    :local h3 [:pick $digits ($lo >> 4) (($lo >> 4) + 1)]
    :local h4 [:pick $digits ($lo & 15) (($lo & 15) + 1)]

    :return ($h1 . $h2 . $h3 . $h4)
}

:local udpchecksum do={
    :local srcHex $1
    :local dstHex $2
    :local udpLenNum $3 
    :local udpTmp $4
    :local pay $5

    :local pseudo ($srcHex . $dstHex . "00" . "11" . [$hex16 $udpLenNum])

    :local checkdata ($pseudo . $udpTmp . $pay)

    :if (([:len $checkdata] % 4) != 0) do={
        :set checkdata ($checkdata . "00")
    }

    :local sum 0
    :local wordCount ([:len $checkdata] / 4)
    :for j from=0 to=($wordCount - 1) do={
        :local offset ($j * 4)
        :local word [:pick $checkdata $offset ($offset + 4)]
        :local val [:tonum ("0x" . $word)]
        :set sum ($sum + $val)
    }

    :while ($sum > 65535) do={
        :set sum (($sum & 65535) + ($sum >> 16))
    }

    :local checksum (65535 - $sum)
    :if ($checksum = 0) do={ :set checksum 0 }

    :local hi ($checksum >> 8)
    :local lo ($checksum & 255)
    :local digits "0123456789abcdef"
    :local h1 [:pick $digits ($hi >> 4) (($hi >> 4) + 1)]
    :local h2 [:pick $digits ($hi & 15) (($hi & 15) + 1)]
    :local h3 [:pick $digits ($lo >> 4) (($lo >> 4) + 1)]
    :local h4 [:pick $digits ($lo & 15) (($lo & 15) + 1)]

    :return ($h1 . $h2 . $h3 . $h4)
}

:local randhex do={

    :local bytes $1
    :local out ""
    :local digits "0123456789abcdef"

    :for i from=1 to=$bytes do={

        :local r [:rndnum from=0 to=255]

        :local h1 [:pick $digits ($r >> 4) (($r >> 4) + 1)]
        :local h2 [:pick $digits ($r & 15) (($r & 15) + 1)]

        :set out ($out . $h1 . $h2)
    }

    :return $out
}

:local randdigitshex do={
    :local bytes $1
    :local out ""
    :local digits "0123456789abcdef"
    :for i from=1 to=$bytes do={
        :local n (48 + [:rndnum from=0 to=9])
        :set out ($out . [:pick $digits ($n >> 4) (($n >> 4) + 1)] . [:pick $digits ($n & 15) (($n & 15) + 1)])
    }
    :return $out
}

:local randcharshex do={
    :local bytes $1
    :local out ""
    :local digits "0123456789abcdef"
    :for i from=1 to=$bytes do={
        :local r [:rndnum from=0 to=51]
        :local n 0
        :if ($r < 26) do={
            :set n (97 + $r)
        } else={
            :set n (65 + ($r - 26))
        }
        :set out ($out . [:pick $digits ($n >> 4) (($n >> 4) + 1)] . [:pick $digits ($n & 15) (($n & 15) + 1)])
    }
    :return $out
}

:local timestamphex do={
    :local unix ([:tonsec [:timestamp]] / 1000000000)
    :local digits "0123456789abcdef"
    :local out ""
    :local n (($unix >> 16) & 65535)
    :local hi ($n >> 8)
    :local lo ($n & 255)
    :set out ($out . [:pick $digits ($hi >> 4) (($hi >> 4) + 1)] . [:pick $digits ($hi & 15) (($hi & 15) + 1)] . [:pick $digits ($lo >> 4) (($lo >> 4) + 1)] . [:pick $digits ($lo & 15) (($lo & 15) + 1)])
    :set n ($unix & 65535)
    :set hi ($n >> 8)
    :set lo ($n & 255)
    :set out ($out . [:pick $digits ($hi >> 4) (($hi >> 4) + 1)] . [:pick $digits ($hi & 15) (($hi & 15) + 1)] . [:pick $digits ($lo >> 4) (($lo >> 4) + 1)] . [:pick $digits ($lo & 15) (($lo & 15) + 1)])
    :return $out
}

:local normalizehex do={
    :local value [:tostr $1]
    :if ([:pick $value 0 2] = "0x") do={ :set value [:pick $value 2 [:len $value]] }
    :if ([:pick $value 0 2] = "0X") do={ :set value [:pick $value 2 [:len $value]] }
    :return $value
}

# Собирает IPv4/UDP-пакет из payload-hex и инжектит его.
# do={} не видит внешние :local, поэтому контекст и хелперы передаём аргументами.
:local buildinject do={
    :local partLen ([:len $pay] / 2)
    :local udpLen ($partLen + 8)
    :local ipLen ($udpLen + 20)
    :local ipidHex [$fnHex16 [:rndnum from=0 to=65535]]
    :local udpHeaderTmp ($sPortHex . $dPortHex . [$fnHex16 $udpLen] . "0000")
    :local udpCsum [$fnUdpCsum $sIpHex $dIpHex $udpLen $udpHeaderTmp $pay]
    :if ($csMode = "zero") do={ :set udpCsum "0000" }
    :local udpHeader ($sPortHex . $dPortHex . [$fnHex16 $udpLen] . $udpCsum)
    :local ipHeaderTmp ("4500" . [$fnHex16 $ipLen] . $ipidHex . "0000" . $tHex . "11" . "0000" . $sIpHex . $dIpHex)
    :local ipCsum [$fnIpCsum $ipHeaderTmp]
    :local ipHeader ("4500" . [$fnHex16 $ipLen] . $ipidHex . "0000" . $tHex . "11" . $ipCsum . $sIpHex . $dIpHex)
    :local fullpacket ($ethHdr . $ipHeader . $udpHeader . $pay)
    :log warning ("fullpacket: " . $fullpacket)
    /tool/traffic-generator/inject interface="$outIfName" data=$fullpacket
}

:global Tx
:global Rx
:if ([:typeof $Tx] != "array") do={ :set Tx ({}) }
:if ([:typeof $Rx] != "array") do={ :set Rx ({}) }
/interface wireguard peers
:foreach i in=[find where comment=BYPASS] do={
    :local LocalTx [get $i tx]
    :local LocalRx [get $i rx]
    :local LastHandshake [get $i last-handshake]
    :local isDisabled [get $i disabled]
    :if (([:tostr $LastHandshake] = "") or ($LastHandshake > [:totime "3m20s"]) or ($isDisabled = yes)) do={
        :local PeerName [get $i name]
        # Обёртка: сбой одного пира не должен прерывать обработку остальных.
        :do {
        :local Interface [get $i interface]
        :local EndpointAddress [get $i endpoint-address]
        :local EndpointIP ""
        :do {
            :set EndpointIP [:toip $EndpointAddress]
        } on-error={
            :set EndpointIP ""
        }
        :if ([:len [:tostr $EndpointIP]] = 0) do={
            :do {
                :set EndpointIP [:resolve $EndpointAddress]
            } on-error={
                :log error ("Could not resolve endpoint: " . $EndpointAddress)
                :error "resolve failed"
            }
        }

        :local DstPort [get $i current-endpoint-port]
        :if (([:tostr $DstPort] = "") or ($DstPort = 0)) do={
            :set DstPort [get $i endpoint-port]
        }
        :local rndport [:rndnum from=49000 to=59999]
        /interface wireguard set $Interface listen-port=$rndport
        :local SrcPort [/interface wireguard get $Interface listen-port]

        /tool ping address=$EndpointIP count=1 interval=200ms
        :delay 1s        
        :local conn [/ip firewall connection find where dst-address="$EndpointIP" and protocol=icmp]
        :if ([:len $conn]=0) do={
            :log error "No WG conntrack entry for $EndpointIP:$DstPort"
            :error "no conntrack"
        }
        :local cid [:pick $conn 0]
        :local srcip [/ip firewall connection get $cid reply-dst-address]

        :local route [/ip route check dst-ip=$EndpointIP once as-value]
        :if ([:len $route]=0) do={
            :log error "Route check failed"
            :error "route check failed"
        }
        :local outIf ($route->"interface")
        :local ifType [/interface get $outIf type]

        :local eth ""
        :local gw ""
        :if ($ifType = "ether" or $ifType = "bridge") do={
            :set gw ($route->"nexthop")
            :local srcmacRaw [/interface get $outIf mac-address]
            :local srcmac [$mac2hex $srcmacRaw]
            :local arpId [/ip arp find where address=$gw and interface=$outIf]
            :if ([:len $arpId] = 0) do={
                /tool ping address=$gw count=1 interval=200ms
                :delay 500ms
                :set arpId [/ip arp find where address=$gw and interface=$outIf]
            }
            :if ([:len $arpId] = 0) do={
                :log error ("No ARP entry for gateway " . $gw . " on " . $outIf)
                :error "no arp"
            }
            :local dstmacRaw [/ip arp get $arpId mac-address]
            :local dstmac [$mac2hex $dstmacRaw]
            :set eth ($dstmac.$srcmac."0800")
        }

        :if ($ifType = "vlan") do={
            :set gw ($route->"nexthop")
            :local srcmacRaw [/interface get $outIf mac-address]
            :local srcmac [$mac2hex $srcmacRaw]
            :local arpId [/ip arp find where address=$gw and interface=$outIf]
            :if ([:len $arpId] = 0) do={
                /tool ping address=$gw count=1 interval=200ms
                :delay 500ms
                :set arpId [/ip arp find where address=$gw and interface=$outIf]
            }
            :if ([:len $arpId] = 0) do={
                :log error ("No ARP entry for gateway " . $gw . " on " . $outIf)
                :error "no arp"
            }
            :local dstmacRaw [/ip arp get $arpId mac-address]
            :local dstmac [$mac2hex $dstmacRaw]
            :local vlanId [/interface/vlan/get $outIf vlan-id]
            :local vlanIdHex [$hex16 $vlanId]
            :set outIf [/interface/vlan/get $outIf interface]
            :set eth ($dstmac.$srcmac."8100".$vlanIdHex."0800")
        }

        :local srcipHex [$ip2hex $srcip]
        :local dstipHex [$ip2hex $EndpointIP]
        :local ttlHex [$hex8 $TTL]

        :local srcPortHex [$hex16 $SrcPort]
        :local dstPortHex [$hex16 $DstPort]

        #Log peer info
        :log warning ("Peer: $PeerName, Interface: $Interface")
        :log warning ("Endpoint Address: $EndpointAddress, Endpoint IP: $EndpointIP")
        :log warning ("Src Port: $SrcPort, Dst Port: $DstPort, Last Handshake: $LastHandshake")
        :log warning ("Last Rx: " . $Rx->[:tostr $i] . ", Current Rx: $LocalRx")
        :log warning ("Last Tx: " . $Tx->[:tostr $i] . ", Current Tx: $LocalTx")
        
        #Disable peer
        :log warning ("Disable peer: $PeerName")
        set $i disabled=yes
        :delay 100ms
        
        #Generating spam
        :log warning ("Generating spam")
        :delay 1

        :log warning ("gateway: $gw")
        :log warning ("eth: $eth")
        :log warning ("srcip: $srcip")
        :log warning ("srcipHex: $srcipHex")
        :log warning ("dstip: $EndpointIP")
        :log warning ("dstipHex: $dstipHex")
        :log warning ("outInterface: $outIf")
        :log warning ("I-spec lengths: i1=" . [:len $i1] . ", i2=" . [:len $i2] . ", i3=" . [:len $i3] . ", i4=" . [:len $i4] . ", i5=" . [:len $i5])

        :for packetNo from=1 to=$IpacketCount do={
            :local part ""
            :if ($packetNo = 1) do={ :set part $i1 }
            :if ($packetNo = 2) do={ :set part $i2 }
            :if ($packetNo = 3) do={ :set part $i3 }
            :if ($packetNo = 4) do={ :set part $i4 }
            :if ($packetNo = 5) do={ :set part $i5 }

            :if ($DebugIparser = true) do={
                :log warning ("DBG I" . $packetNo . " raw len=" . [:len $part] . " raw=" . $part)
            }

            :if ([:pick $part 0 2] = "0x") do={ :set part [:pick $part 2 [:len $part]] }
            :if ([:pick $part 0 2] = "0X") do={ :set part [:pick $part 2 [:len $part]] }

            :local nextTag [:find $part "<" 1]
            :if ([:typeof $nextTag] = "nil") do={
                :if ([:pick $part 0 5] = "<b 0x") do={
                    :if ([:pick $part ([:len $part] - 1) [:len $part]] = ">") do={
                        :set part [:pick $part 5 ([:len $part] - 1)]
                    } else={
                        :log error ("I" . $packetNo . " static <b> tag is missing closing >, probably truncated")
                        :set part ""
                    }
                } else={
                    :if ([:pick $part 0 3] = "<b ") do={
                        :if ([:pick $part ([:len $part] - 1) [:len $part]] = ">") do={
                            :set part [:pick $part 3 ([:len $part] - 1)]
                        } else={
                            :log error ("I" . $packetNo . " static <b> tag is missing closing >, probably truncated")
                            :set part ""
                        }
                    }
                }
            }
            :if ([:pick $part 0 1] = "<") do={
                :local spec $part
                :local out ""
                :local pos 0
                :if ($DebugIparser = true) do={
                    :log warning ("DBG I" . $packetNo . " parse spec len=" . [:len $spec] . " spec=" . $spec)
                }

                :if (([:pick $spec 0 5] = "<b 0x") or ([:pick $spec 0 3] = "<b ")) do={
                    :local firstEnd [:find $spec ">"]
                    :if ($DebugIparser = true) do={
                        :log warning ("DBG I" . $packetNo . " firstEnd=" . $firstEnd . " firstPrefix5=" . [:pick $spec 0 5] . " firstPrefix3=" . [:pick $spec 0 3])
                    }
                    :if ([:typeof $firstEnd] = "nil") do={
                        :log error ("I" . $packetNo . " leading <b> tag is missing >: " . $spec)
                        :set spec ""
                    } else={
                        :local firstTag [:pick $spec 1 $firstEnd]
                        :local firstSp [:find $firstTag " "]
                        :if ($DebugIparser = true) do={
                            :log warning ("DBG I" . $packetNo . " firstTag=" . $firstTag . " firstSp=" . $firstSp)
                        }
                        :if ([:typeof $firstSp] != "nil") do={
                            :local firstVal [:pick $firstTag ($firstSp + 1) [:len $firstTag]]
                            :if ([:pick $firstVal 0 2] = "0x") do={ :set firstVal [:pick $firstVal 2 [:len $firstVal]] }
                            :if ([:pick $firstVal 0 2] = "0X") do={ :set firstVal [:pick $firstVal 2 [:len $firstVal]] }
                            :set out ($out . $firstVal)
                            :if ($DebugIparser = true) do={
                                :log warning ("DBG I" . $packetNo . " firstVal len=" . [:len $firstVal] . " outHexLen=" . [:len $out])
                            }
                        }
                        :set pos ($firstEnd + 1)
                        :if ($DebugIparser = true) do={
                            :log warning ("DBG I" . $packetNo . " pos after leading b=" . $pos)
                        }
                    }
                }

                :while ($pos < [:len $spec]) do={
                    :local start $pos
                    :if ([:pick $spec $pos ($pos + 1)] != "<") do={
                        :set start [:find $spec "<" $pos]
                    }
                    :if ($DebugIparser = true) do={
                        :log warning ("DBG I" . $packetNo . " loop pos=" . $pos . " start=" . $start . " specLen=" . [:len $spec])
                    }
                    :if ([:typeof $start] = "nil") do={
                        :set pos [:len $spec]
                    } else={
                        :local end [:find $spec ">" $start]
                        :if ($DebugIparser = true) do={
                            :log warning ("DBG I" . $packetNo . " end=" . $end . " at start=" . $start)
                        }
                        :if ([:typeof $end] = "nil") do={
                            :log error ("I" . $packetNo . " invalid tag, missing >: " . $spec)
                            :set out ""
                            :set pos [:len $spec]
                        } else={
                            :local tag [:pick $spec ($start + 1) $end]
                            :local handled false
                            :if ($DebugIparser = true) do={
                                :log warning ("DBG I" . $packetNo . " tag=[" . $tag . "] len=" . [:len $tag] . " p2=[" . [:pick $tag 0 2] . "] p3=[" . [:pick $tag 0 3] . "]")
                            }

                            :if ([:pick $tag 0 2] = "b ") do={
                                :local val [:pick $tag 2 [:len $tag]]
                                :if ([:pick $val 0 2] = "0x") do={ :set val [:pick $val 2 [:len $val]] }
                                :if ([:pick $val 0 2] = "0X") do={ :set val [:pick $val 2 [:len $val]] }
                                :set out ($out . $val)
                                :set handled true
                                :if ($DebugIparser = true) do={
                                    :log warning ("DBG I" . $packetNo . " handled b valHexLen=" . [:len $val] . " outHexLen=" . [:len $out])
                                }
                            }

                            :if ([:pick $tag 0 2] = "r ") do={
                                :local count [:tonum [:pick $tag 2 [:len $tag]]]
                                :local gen [$randhex $count]
                                :set out ($out . $gen)
                                :set handled true
                                :if ($DebugIparser = true) do={
                                    :log warning ("DBG I" . $packetNo . " handled r count=" . $count . " genHexLen=" . [:len $gen] . " outHexLen=" . [:len $out])
                                }
                            }

                            :if ([:pick $tag 0 3] = "rc ") do={
                                :local count [:tonum [:pick $tag 3 [:len $tag]]]
                                :local gen [$randcharshex $count]
                                :set out ($out . $gen)
                                :set handled true
                                :if ($DebugIparser = true) do={
                                    :log warning ("DBG I" . $packetNo . " handled rc count=" . $count . " genHexLen=" . [:len $gen] . " outHexLen=" . [:len $out])
                                }
                            }

                            :if ([:pick $tag 0 3] = "rd ") do={
                                :local count [:tonum [:pick $tag 3 [:len $tag]]]
                                :local gen [$randdigitshex $count]
                                :set out ($out . $gen)
                                :set handled true
                                :if ($DebugIparser = true) do={
                                    :log warning ("DBG I" . $packetNo . " handled rd count=" . $count . " genHexLen=" . [:len $gen] . " outHexLen=" . [:len $out])
                                }
                            }

                            :if ($tag = "t") do={
                                :local gen [$timestamphex]
                                :set out ($out . $gen)
                                :set handled true
                                :if ($DebugIparser = true) do={
                                    :log warning ("DBG I" . $packetNo . " handled t genHexLen=" . [:len $gen] . " outHexLen=" . [:len $out])
                                }
                            }

                            :if ($handled = false) do={
                                :log error ("I" . $packetNo . " unsupported tag: <" . $tag . ">")
                                :set out ""
                                :set pos [:len $spec]
                            }

                            :set pos ($end + 1)
                            :if ($DebugIparser = true) do={
                                :log warning ("DBG I" . $packetNo . " next pos=" . $pos . " handled=" . $handled . " outHexLen=" . [:len $out])
                            }
                        }
                    }
                }

                :set part $out
                :if ($DebugIparser = true) do={
                    :log warning ("DBG I" . $packetNo . " final partHexLen=" . [:len $part] . " payloadBytes=" . ([:len $part] / 2))
                }
            }

            :if ([:len $part] = 0) do={
                :log warning ("I-packet #" . $packetNo . " skipped: empty")
            }

            :if ([:len $part] > 0) do={
                :if (([:len $part] % 2) != 0) do={
                    :log error ("I" . $packetNo . " UDP payload HEX has odd length: " . [:len $part] . " hex chars")
                    :if ([:len $part] > 16) do={
                        :log error ("I" . $packetNo . " head/tail: " . [:pick $part 0 16] . "..." . [:pick $part ([:len $part] - 16) [:len $part]])
                    } else={
                        :log error ("I" . $packetNo . " value: " . $part)
                    }
                    :set part ""
                } else={
                    :log warning ("I-packet #" . $packetNo . " payload bytes: " . ([:len $part] / 2))
                    $buildinject pay=$part sPortHex=$srcPortHex dPortHex=$dstPortHex \
                        sIpHex=$srcipHex dIpHex=$dstipHex tHex=$ttlHex ethHdr=$eth \
                        csMode=$UDPChecksumMode outIfName=$outIf \
                        fnHex16=$hex16 fnUdpCsum=$udpchecksum fnIpCsum=$ipchecksum
                    :delay 5ms
                }
            }
        }

        :for j from=1 to=$Jc do={
            :local size [:rndnum from=$Jmin to=$Jmax]
            :local part [$randhex $size]
            :log warning ("Junk packet #" . $j . " payload bytes: " . ([:len $part] / 2))
            $buildinject pay=$part sPortHex=$srcPortHex dPortHex=$dstPortHex \
                sIpHex=$srcipHex dIpHex=$dstipHex tHex=$ttlHex ethHdr=$eth \
                csMode=$UDPChecksumMode outIfName=$outIf \
                fnHex16=$hex16 fnUdpCsum=$udpchecksum fnIpCsum=$ipchecksum
            :delay 5ms
        }
        
        #Enable peer
        :log warning ("Enable peer: $PeerName")
        set $i disabled=no
        } on-error={
            :log warning ("Skipping peer " . $PeerName . ", see errors above")
            # Страховка: не оставляем пир выключенным, если сбой пришёлся после disable.
            set $i disabled=no
        }
    }
    :set ($Tx->[:tostr $i]) $LocalTx
    :set ($Rx->[:tostr $i]) $LocalRx
}
@alexusbbb

Copy link
Copy Markdown

НЕ работает если домен 4 уровня с 3 точками (

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment