Skip to content

Instantly share code, notes, and snippets.

@IIeTp
Last active June 3, 2026 12:21
Show Gist options
  • Select an option

  • Save IIeTp/ff81e34c1a0c3fcaadfe5a0e32201af8 to your computer and use it in GitHub Desktop.

Select an option

Save IIeTp/ff81e34c1a0c3fcaadfe5a0e32201af8 to your computer and use it in GitHub Desktop.
# MikroTik DNS Group Updater

MikroTik DNS Group Updater

Скрипт RouterOS для управляемых DNS static и firewall address-list групп.

Он скачивает списки доменов/IP из URL, парсит YAML payload: или обычные plain-списки, а затем обновляет managed-записи MikroTik по имени группы. Имя группы берется из имени самого /system script через [:jobname].

Что умеет

  • Загружает один или несколько URL.

  • Сортирует URL-источники по имени файла.

  • Поддерживает YAML-формат:

    payload:
      - example.com
      - +.example.org
      - 91.108.4.0/22
      - 2001:67c:4e8::/48
  • Поддерживает plain-списки:

    # comment
    2.16.20.0/23
    2.16.53.0/24
    2001:67c:4e8::/48
    
  • Поддерживает ручные элементы через manualItems.

  • Для доменов создает DNS static FWD.

  • Для wildcard +.example.com создает match-subdomain=yes.

  • Может добавлять AAAA ::ffff выше FWD, чтобы блокировать IPv6-резолв доменов.

  • Для IPv4/CIDR обновляет /ip firewall address-list.

  • Для IPv6/CIDR обновляет /ipv6 firewall address-list.

  • Для address-list использует временные .rsc batch-файлы и /import, а не медленное добавление по одной записи.

  • Перед записью batch-файла проверяет свободное место на диске из tmpDir.

  • Старые managed address-list записи удаляются bulk-командой перед импортом нового набора.

Managed comments

Скрипт управляет только записями со своими комментариями:

managed:dns:<groupName>:AAAA
managed:dns:<groupName>:FWD
managed:addr:<groupName>:IP

Где <groupName> - это имя скрипта в MikroTik.

Например, если скрипт называется jetbrains, комментарии будут:

managed:dns:jetbrains:AAAA
managed:dns:jetbrains:FWD
managed:addr:jetbrains:IP

Важное поведение

Для DNS static используется аккуратное обновление и ремонт порядка, чтобы AAAA находилась выше FWD.

Для address-list используется быстрый replace-режим:

  1. При первом найденном IPv4/IPv6/CIDR удаляются старые managed address-list записи группы.
  2. Новый набор импортируется через временные .rsc batch-файлы.
  3. Каждый batch-файл удаляется сразу после импорта.

Это быстро, но на время обновления address-list может быть короткая "дырка", пока старые записи уже удалены, а новые еще импортируются.

Настройки

Редактируемые переменные находятся вверху скрипта:

:local urls "https://example.com/list.yaml|https://example.com/ip-list.txt"
:local itemSeparator "|"
:local manualItems ""
:local addressList "seedhost"
:local forwardTo ""
:local blockIpv6 "yes"
:local repairOrder "yes"
:local tmpDir "tmpfs01/"

urls

Один или несколько URL. Разделитель задается через itemSeparator.

:local urls "https://example.com/a.yaml|https://example.com/b.yaml"

manualItems

Ручные домены/IP/CIDR. �?спользует тот же itemSeparator.

:local manualItems "example.com|+.example.org|1.2.3.4|2001:db8::/32"

addressList

Имя address-list, куда попадут:

  • A-записи из DNS static;
  • IPv4/CIDR из списков;
  • IPv6/CIDR из списков, но уже в /ipv6 firewall address-list.
:local addressList "seedhost"

forwardTo

DNS-сервер или FWD target для доменов. Если пусто, создается FWD без forward-to.

:local forwardTo ""

blockIpv6

Добавлять ли AAAA ::ffff для DNS-доменов:

:local blockIpv6 "yes"

На IP/CIDR payload это не влияет: IPv6-сети из списка добавляются в /ipv6 firewall address-list.

repairOrder

Чинить порядок DNS static:

:local repairOrder "yes"

Для address-list порядок не чинится: IP/CIDR всегда импортируются через .rsc batch.

tmpDir

Куда сохранять временные файлы:

:local tmpDir "tmpfs01/"

Если пусто, файлы будут создаваться в корне Files:

:local tmpDir ""

Пример для JetBrains

Создайте скрипт с именем jetbrains, вставьте код и оставьте такие URL:

:local urls "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/refs/heads/meta/geo/geosite/jetbrains.yaml|https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/refs/heads/meta/geo/geosite/jetbrains-ai.yaml"
:local addressList "seedhost"

Запуск:

/system script run jetbrains

Проверка DNS static:

/ip dns static print detail where comment~"managed:dns:jetbrains"

Проверка IPv4 address-list:

/ip firewall address-list print detail where comment="managed:addr:jetbrains:IP"

Проверка IPv6 address-list:

/ipv6 firewall address-list print detail where comment="managed:addr:jetbrains:IP"

Логи

При обновлении IP/CIDR в логах будут строки вида:

dns-group jetbrains: IP/CIDR detected, old managed address-list removed before batch import
dns-group jetbrains: importing address-list batch, entries=450, bytes=45120, file=tmpfs01/dns_group_jetbrains_addr_import.rsc
dns-group jetbrains: domains=20, ipv4=1200, ipv6=50, added FWD=20, added AAAA=20, added addr=1250, removed FWD=0, removed AAAA=0, removed addr=1250, moved DNS=0, moved addr=0, streamAddrMode=yes, repairAddrOrder=no, cacheFlushed=yes

Batch-файл создается, импортируется и сразу удаляется, поэтому в WinBox Files его можно не успеть увидеть.

Ограничения

  • .rsc batch используется только для address-list.
  • DNS static обновляется обычной логикой, чтобы сохранить порядок AAAA выше FWD.
  • Для address-list используется быстрый replace-режим, не diff.
  • Diff без внешнего сравнения невозможен: MikroTik пришлось бы искать каждую запись, что медленно.
  • Импорт .rsc создается локально самим скриптом, а не скачивается как готовый исполняемый файл из интернета.

Файлы для Gist

Рекомендуемый набор файлов:

  • README.md - это описание.
  • dns-group.rsc - source скрипта без внешней обертки.
  • install-example.rsc - опционально, пример создания /system script.
# =========================
# Настройки пользователя
# =========================
# Ссылки на YAML payload. Несколько ссылок разделяй символом из itemSeparator.
# Источники будут обработаны по алфавиту имени файла из URL.
:local urls "https://www.iwik.org/ipcountry/RU.cidr"
:local itemSeparator "|"
# Ручной список доменов, URL, IPv4/IPv6 и CIDR.
# Работает вместе с urls: всё объединяется в одну managed-группу.
# Несколько элементов разделяй символом из itemSeparator.
:local manualItems ""
# В какой address-list добавлять A-записи из DNS static и IPv4/CIDR из IP payload.
:local addressList "seedhost"
# В какой DNS форвардить FWD-запросы.
# Если пусто, FWD будет без forward-to и использует обычные DNS MikroTik.
:local forwardTo ""
# Добавлять AAAA-заглушку для блокировки IPv6 у DNS-доменов: yes/no.
# На IP payload это не влияет: IPv4 попадает в /ip firewall address-list,
# IPv6 попадает в /ipv6 firewall address-list.
:local blockIpv6 "yes"
# Исправлять порядок уже существующих managed-записей: yes/no.
# DNS-записи добавляются сразу в нужное место через place-before.
# Address-list записи из IP/CIDR добавляются/удаляются через diff и .rsc batch-файлы.
# Если в группе есть IP/CIDR, порядок address-list автоматически не чинится.
:local repairOrder "yes"
# Удалять лишние старые IP/CIDR managed-записи: yes/no.
# yes = address-list приводится к новому списку из urls/manualItems.
:local removeMissing "no"
# Сколько хранить IP/CIDR записи в address-list.
# Например "365d" = динамические записи на 365 дней, без записи в flash.
# Если пусто, timeout не добавляется и записи будут бессрочными/статическими.
:local addressListTimeout "365d"
# Убирать из системного лога account-события, где RouterOS печатает contents batch .rsc: yes/no.
# yes добавляет !account в существующие /system logging rules один раз и оставляет это правило дальше.
:local suppressAccountLog "yes"
# Куда сохранять временный файл.
# Например: "tmpfs01/" если на роутере есть RAM-диск.
# Если пусто, файл будет создан в корне Files.
:local tmpDir "tmpfs01/"
# =========================
# Служебная часть ниже
# =========================
# Имя группы берётся из имени скрипта.
:local groupName [:jobname]
:local startedAt [:timestamp]
:local ipv6Block "::ffff"
:local aaaaComment ("managed:dns:" . $groupName . ":AAAA")
:local fwdComment ("managed:dns:" . $groupName . ":FWD")
:local addrComment ("managed:addr:" . $groupName . ":IP")
:local addrTimeoutPart ""
:if ([:len $addressListTimeout] > 0) do={ :set addrTimeoutPart (" timeout=" . $addressListTimeout) }
:local parsedDomains 0
:local parsedIpv4 0
:local parsedIpv6 0
:local addedFwd 0
:local addedAaaa 0
:local addedAddr 0
:local removedFwd 0
:local removedAaaa 0
:local removedAddr 0
:local movedDns 0
:local movedAddr 0
:local changedCount 0
:local cacheFlushed "no"
:if ([:len $groupName] = 0) do={ :error "dns-group: groupName is empty" }
:if ([:len $addressList] = 0) do={ :error ("dns-group " . $groupName . ": addressList is empty") }
:if ([:len $itemSeparator] = 0) do={ :error ("dns-group " . $groupName . ": itemSeparator is empty") }
:if (([:len $urls] = 0) and ([:len $manualItems] = 0)) do={ :error ("dns-group " . $groupName . ": urls and manualItems are empty") }
:if ($suppressAccountLog = "yes") do={
:foreach logId in=[/system logging find] do={
:local logTopics [/system logging get $logId topics]
:if (([:len $logTopics] > 0) and ([:typeof [:find $logTopics "!account"]] = "nil")) do={
:do { /system logging set $logId topics=($logTopics . ",!account") } on-error={}
}
}
}
:local sourceLines ""
:local rawUrl ""
:local sourceInput ($urls . $itemSeparator)
:for i from=0 to=([:len $sourceInput] - 1) do={
:local char [:pick $sourceInput $i]
:if ($char != $itemSeparator) do={
:set rawUrl ($rawUrl . $char)
} else={
:local cleanUrl $rawUrl
:while (([:len $cleanUrl] > 0) and (([:pick $cleanUrl 0 1] = " ") or ([:pick $cleanUrl 0 1] = "\t"))) do={
:set cleanUrl [:pick $cleanUrl 1 [:len $cleanUrl]]
}
:while (([:len $cleanUrl] > 0) and (([:pick $cleanUrl ([:len $cleanUrl] - 1) [:len $cleanUrl]] = " ") or ([:pick $cleanUrl ([:len $cleanUrl] - 1) [:len $cleanUrl]] = "\t"))) do={
:set cleanUrl [:pick $cleanUrl 0 ([:len $cleanUrl] - 1)]
}
:if ([:len $cleanUrl] > 0) do={
:local fileName ""
:for j from=0 to=([:len $cleanUrl] - 1) do={
:local urlChar [:pick $cleanUrl $j]
:if ($urlChar = "/") do={
:set fileName ""
} else={
:if ($urlChar = "?") do={
:set j [:len $cleanUrl]
} else={
:set fileName ($fileName . $urlChar)
}
}
}
:if ([:len $fileName] = 0) do={ :set fileName $cleanUrl }
:local newLine ($fileName . "|" . $cleanUrl)
:local rebuiltSources ""
:local inserted false
:local sourceLine ""
:for j from=0 to=([:len $sourceLines] - 1) do={
:local sourceChar [:pick $sourceLines $j]
:if ($sourceChar != "\n") do={
:set sourceLine ($sourceLine . $sourceChar)
} else={
:if ([:len $sourceLine] > 0) do={
:local pipePos [:find $sourceLine "|"]
:local oldFile $sourceLine
:if ([:typeof $pipePos] != "nil") do={ :set oldFile [:pick $sourceLine 0 $pipePos] }
:local newBefore false
:if ($inserted = false) do={
:local alphabet "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.~"
:local decided false
:local maxLen [:len $fileName]
:if ([:len $oldFile] > $maxLen) do={ :set maxLen [:len $oldFile] }
:for k from=0 to=($maxLen - 1) do={
:if ($decided = false) do={
:if ($k >= [:len $fileName]) do={
:if ($k < [:len $oldFile]) do={ :set newBefore true }
:set decided true
} else={
:if ($k >= [:len $oldFile]) do={
:set decided true
} else={
:local leftChar [:pick $fileName $k ($k + 1)]
:local rightChar [:pick $oldFile $k ($k + 1)]
:if ($leftChar != $rightChar) do={
:local leftIndex [:find $alphabet $leftChar]
:local rightIndex [:find $alphabet $rightChar]
:if ([:typeof $leftIndex] = "nil") do={ :set leftIndex 10000 }
:if ([:typeof $rightIndex] = "nil") do={ :set rightIndex 10000 }
:if ($leftIndex < $rightIndex) do={ :set newBefore true }
:set decided true
}
}
}
}
}
}
:if (($inserted = false) and ($newBefore = true)) do={
:set rebuiltSources ($rebuiltSources . $newLine . "\n")
:set inserted true
}
:set rebuiltSources ($rebuiltSources . $sourceLine . "\n")
}
:set sourceLine ""
}
}
:if ($inserted = false) do={ :set rebuiltSources ($rebuiltSources . $newLine . "\n") }
:set sourceLines $rebuiltSources
}
:set rawUrl ""
}
}
:local seenDomains "|"
:local orderedDnsItems ""
:local old4Ids ""
:local old6Ids ""
:local old4Set "|"
:local old6Set "|"
:local new4Set "|"
:local new6Set "|"
:local oldIpv4 0
:local oldIpv6 0
:local matchedAddr 0
:local skippedAddr 0
:local debugSampleLimit 12
:local addSampleCount 0
:local removeSampleCount 0
:local addSamples ""
:local removeSamples ""
:local importBatchBytes 45000
# Перед записью batch.rsc проверяем свободное место в tmpDir.
# Нужно место на сам батч + запас 3 размера батча.
:local importBatchFile ($tmpDir . "dns_group_" . $groupName . "_addr_import.rsc")
:local importBatchDisk ""
:local slashInTmpDir [:find $tmpDir "/"]
:if ([:typeof $slashInTmpDir] != "nil") do={ :set importBatchDisk [:pick $tmpDir 0 $slashInTmpDir] }
:local importBatch ""
:local importBatchCount 0
:local streamAddrMode "no"
:local repairAddrOrder $repairOrder
:local sourceIndex 0
:local currentSource ""
# Индексируем старые IP/CIDR managed-записи один раз.
# Быстрый вариант: print as-value и строковый индекс.
:foreach row in=[/ip firewall address-list print as-value where list=$addressList comment=$addrComment] do={
:local id ($row->".id")
:local addr ($row->"address")
:local slash32 [:find $addr "/32"]
:if ([:typeof $slash32] != "nil") do={
:if (($slash32 + 3) = [:len $addr]) do={ :set addr [:pick $addr 0 $slash32] }
}
:set old4Ids ($old4Ids . $id . "|" . $addr . "\n")
:set old4Set ($old4Set . $addr . "|")
:set oldIpv4 ($oldIpv4 + 1)
}
:foreach row in=[/ipv6 firewall address-list print as-value where list=$addressList comment=$addrComment] do={
:local id ($row->".id")
:local addr ($row->"address")
:local slash128 [:find $addr "/128"]
:if ([:typeof $slash128] != "nil") do={
:if (($slash128 + 4) = [:len $addr]) do={ :set addr [:pick $addr 0 $slash128] }
}
:set old6Ids ($old6Ids . $id . "|" . $addr . "\n")
:set old6Set ($old6Set . $addr . "|")
:set oldIpv6 ($oldIpv6 + 1)
}
:for sourcePos from=0 to=([:len $sourceLines] - 1) do={
:local sourceChar [:pick $sourceLines $sourcePos]
:if ($sourceChar != "\n") do={
:set currentSource ($currentSource . $sourceChar)
} else={
:if ([:len $currentSource] > 0) do={
:local pipePos [:find $currentSource "|"]
:local sourceName $currentSource
:local url ""
:if ([:typeof $pipePos] != "nil") do={
:set sourceName [:pick $currentSource 0 $pipePos]
:set url [:pick $currentSource ($pipePos + 1) [:len $currentSource]]
}
:if ([:len $url] = 0) do={ :error ("dns-group " . $groupName . ": bad source line " . $currentSource) }
:set sourceIndex ($sourceIndex + 1)
:local tmpFile ($tmpDir . "dns_group_" . $groupName . "_" . $sourceIndex . ".tmp")
:do { /file remove $tmpFile } on-error={}
:do {
/tool fetch url=$url mode=https dst-path=$tmpFile
} on-error={
:error ("dns-group " . $groupName . ": fetch failed: " . $url)
}
:local fileSize 0
:do {
:set fileSize [/file get $tmpFile size]
} on-error={
:do { /file remove $tmpFile } on-error={}
:error ("dns-group " . $groupName . ": cannot read fetched file size: " . $url)
}
:if ($fileSize = 0) do={
:do { /file remove $tmpFile } on-error={}
:error ("dns-group " . $groupName . ": fetched file is empty: " . $url)
}
:log info ("dns-group " . $groupName . ": download finished from " . $url)
:local chunkSize 32768
:local hasPayload false
:local scanOffset 0
:while ($scanOffset < $fileSize) do={
:local scanChunk ""
:do {
:set scanChunk (([/file/read file=$tmpFile offset=$scanOffset chunk-size=$chunkSize as-value])->"data")
} on-error={
:do { /file remove $tmpFile } on-error={}
:error ("dns-group " . $groupName . ": cannot read fetched file chunk: " . $url)
}
:if ([:typeof [:find $scanChunk "payload:"]] != "nil") do={ :set hasPayload true }
:set scanOffset ($scanOffset + $chunkSize)
}
:local inPayload false
:local line ""
:local parseOffset 0
:while ($parseOffset < $fileSize) do={
:local content ""
:do {
:set content (([/file/read file=$tmpFile offset=$parseOffset chunk-size=$chunkSize as-value])->"data")
} on-error={
:do { /file remove $tmpFile } on-error={}
:error ("dns-group " . $groupName . ": cannot parse fetched file chunk: " . $url)
}
:local payloadInline [:find $content "payload: -"]
:if ([:typeof $payloadInline] != "nil") do={
:set content ([:pick $content 0 ($payloadInline + 8)] . "\n" . [:pick $content ($payloadInline + 9) [:len $content]])
:local normalized ""
:for normIndex from=0 to=([:len $content] - 1) do={
:local normChar [:pick $content $normIndex]
:if (($normChar = "-") and ($normIndex > 0) and ([:pick $content ($normIndex - 1) $normIndex] = " ")) do={
:set normalized ($normalized . "\n-")
} else={
:set normalized ($normalized . $normChar)
}
}
:set content $normalized
}
:if (($parseOffset + $chunkSize) >= $fileSize) do={
:set content ($content . "\n")
}
:for i from=0 to=([:len $content] - 1) do={
:local char [:pick $content $i]
:if (($char != "\r") and ($char != "\n")) do={
:set line ($line . $char)
} else={
:local work $line
:while (([:len $work] > 0) and (([:pick $work 0 1] = " ") or ([:pick $work 0 1] = "\t"))) do={
:set work [:pick $work 1 [:len $work]]
}
:while (([:len $work] > 0) and (([:pick $work ([:len $work] - 1) [:len $work]] = " ") or ([:pick $work ([:len $work] - 1) [:len $work]] = "\t"))) do={
:set work [:pick $work 0 ([:len $work] - 1)]
}
:if ($work = "payload:") do={
:set inPayload true
} else={
:local shouldParse false
:local item $work
:if (($inPayload = true) and ([:len $work] > 1) and ([:pick $work 0 1] = "-")) do={
:set shouldParse true
:set item [:pick $work 1 [:len $work]]
:while (([:len $item] > 0) and (([:pick $item 0 1] = " ") or ([:pick $item 0 1] = "\t"))) do={
:set item [:pick $item 1 [:len $item]]
}
}
:if (($hasPayload = false) and ([:len $work] > 0) and ([:pick $work 0 1] != "#")) do={
:set shouldParse true
:set item $work
}
:if ($shouldParse = true) do={
:local hashPos [:find $item "#"]
:if ([:typeof $hashPos] != "nil") do={ :set item [:pick $item 0 $hashPos] }
:while (([:len $item] > 0) and (([:pick $item ([:len $item] - 1) [:len $item]] = " ") or ([:pick $item ([:len $item] - 1) [:len $item]] = "\t"))) do={
:set item [:pick $item 0 ([:len $item] - 1)]
}
:if ([:len $item] > 0) do={
:local wildcard false
:if (([:len $item] > 2) and ([:pick $item 0 2] = "+.")) do={
:set wildcard true
:set item [:pick $item 2 [:len $item]]
}
:local schemePos [:find $item "://"]
:if ([:typeof $schemePos] != "nil") do={
:set wildcard false
:set item [:pick $item ($schemePos + 3) [:len $item]]
:local atPos [:find $item "@"]
:if ([:typeof $atPos] != "nil") do={ :set item [:pick $item ($atPos + 1) [:len $item]] }
:local slashPos [:find $item "/"]
:if ([:typeof $slashPos] != "nil") do={ :set item [:pick $item 0 $slashPos] }
:local queryPos [:find $item "?"]
:if ([:typeof $queryPos] != "nil") do={ :set item [:pick $item 0 $queryPos] }
:local hashPos2 [:find $item "#"]
:if ([:typeof $hashPos2] != "nil") do={ :set item [:pick $item 0 $hashPos2] }
:if (([:len $item] > 1) and ([:pick $item 0 1] = "[")) do={
:local closeBracket [:find $item "]"]
:if ([:typeof $closeBracket] != "nil") do={ :set item [:pick $item 1 $closeBracket] }
} else={
:local portPos [:find $item ":"]
:if ([:typeof $portPos] != "nil") do={ :set item [:pick $item 0 $portPos] }
}
} else={
:local slashPos [:find $item "/"]
:if ([:typeof $slashPos] != "nil") do={
:local firstCharForSlash [:pick $item 0 1]
:if (([:typeof [:find "0123456789" $firstCharForSlash]] = "nil") and ([:typeof [:find $item ":"]] = "nil")) do={
:set item [:pick $item 0 $slashPos]
:set wildcard false
}
}
}
:local isIp false
:local isIpv6 false
:if ([:typeof [:find $item ":"]] != "nil") do={
:set isIp true
:set isIpv6 true
}
:if ([:typeof [:find $item "/"]] != "nil") do={ :set isIp true }
:local firstChar [:pick $item 0 1]
:if ([:typeof [:find "0123456789" $firstChar]] != "nil") do={
:if ([:typeof [:find $item "."]] != "nil") do={ :set isIp true }
}
:if ($isIp = true) do={
:set streamAddrMode "diff"
:set repairAddrOrder "no"
:local itemKey $item
:local slash32 [:find $itemKey "/32"]
:if ([:typeof $slash32] != "nil") do={
:if (($slash32 + 3) = [:len $itemKey]) do={ :set itemKey [:pick $itemKey 0 $slash32] }
}
:local slash128 [:find $itemKey "/128"]
:if ([:typeof $slash128] != "nil") do={
:if (($slash128 + 4) = [:len $itemKey]) do={ :set itemKey [:pick $itemKey 0 $slash128] }
}
:if ($isIpv6 = true) do={
:local key ("|" . $itemKey . "|")
:if ([:typeof [:find $new6Set $key]] != "nil") do={
:set skippedAddr ($skippedAddr + 1)
} else={
:set new6Set ($new6Set . $itemKey . "|")
:set parsedIpv6 ($parsedIpv6 + 1)
:if ([:typeof [:find $old6Set $key]] != "nil") do={
:set matchedAddr ($matchedAddr + 1)
} else={
:set importBatch ($importBatch . ":do { /ipv6 firewall address-list add list=\"" . $addressList . "\" address=\"" . $item . "\" comment=\"" . $addrComment . "\"" . $addrTimeoutPart . " } on-error={}\n")
:set importBatchCount ($importBatchCount + 1)
:if ($addSampleCount < $debugSampleLimit) do={
:set addSampleCount ($addSampleCount + 1)
:set addSamples ($addSamples . $item . "; ")
}
}
}
} else={
:local key ("|" . $itemKey . "|")
:if ([:typeof [:find $new4Set $key]] != "nil") do={
:set skippedAddr ($skippedAddr + 1)
} else={
:set new4Set ($new4Set . $itemKey . "|")
:set parsedIpv4 ($parsedIpv4 + 1)
:if ([:typeof [:find $old4Set $key]] != "nil") do={
:set matchedAddr ($matchedAddr + 1)
} else={
:set importBatch ($importBatch . ":do { /ip firewall address-list add list=\"" . $addressList . "\" address=\"" . $item . "\" comment=\"" . $addrComment . "\"" . $addrTimeoutPart . " } on-error={}\n")
:set importBatchCount ($importBatchCount + 1)
:if ($addSampleCount < $debugSampleLimit) do={
:set addSampleCount ($addSampleCount + 1)
:set addSamples ($addSamples . $item . "; ")
}
}
}
}
:if ([:len $importBatch] > $importBatchBytes) do={
:local batchSize [:len $importBatch]
:local requiredFree ($batchSize * 4)
:local freeBytes 0
:local freeFound "no"
:if ([:len $importBatchDisk] > 0) do={
:foreach diskId in=[/disk find where slot=$importBatchDisk] do={
:if ($freeFound = "no") do={
:set freeBytes [/disk get $diskId free]
:set freeFound "yes"
}
}
}
:if ($freeFound = "no") do={ :set freeBytes [/system resource get free-hdd-space] }
:if ($freeBytes < $requiredFree) do={
:error ("dns-group " . $groupName . ": not enough free space for address-list add batch on " . $tmpDir . ", free=" . $freeBytes . ", need=" . $requiredFree)
}
:do { /file remove $importBatchFile } on-error={}
:do {
/file add name=$importBatchFile type=file contents=$importBatch
} on-error={
:do { /file remove $importBatchFile } on-error={}
/file add name=$importBatchFile type=file
/file set $importBatchFile contents=$importBatch
}
:do {
/import file-name=$importBatchFile
} on-error={
:do { /file remove $importBatchFile } on-error={}
:error ("dns-group " . $groupName . ": address-list add batch import failed")
}
:do { /file remove $importBatchFile } on-error={}
:set addedAddr ($addedAddr + $importBatchCount)
:set importBatch ""
:set importBatchCount 0
}
} else={
:local key ("|" . $item . "|")
:if ([:typeof [:find $seenDomains $key]] = "nil") do={
:set seenDomains ($seenDomains . $item . "|")
:set parsedDomains ($parsedDomains + 1)
:if ($wildcard = true) do={
:set orderedDnsItems ($orderedDnsItems . "W|" . $item . "\n")
} else={
:set orderedDnsItems ($orderedDnsItems . "E|" . $item . "\n")
}
}
}
}
}
}
:set line ""
}
}
:set parseOffset ($parseOffset + $chunkSize)
}
:do { /file remove $tmpFile } on-error={}
}
:set currentSource ""
}
}
# Ручные элементы добавляются после URL-источников.
# DNS-домены дедуплицируются, IP/CIDR проходят через тот же diff.
:local manualInput ($manualItems . $itemSeparator)
:local manualItem ""
:for manualIndex from=0 to=([:len $manualInput] - 1) do={
:local manualChar [:pick $manualInput $manualIndex]
:if ($manualChar != $itemSeparator) do={
:set manualItem ($manualItem . $manualChar)
} else={
:local item $manualItem
:while (([:len $item] > 0) and (([:pick $item 0 1] = " ") or ([:pick $item 0 1] = "\t"))) do={
:set item [:pick $item 1 [:len $item]]
}
:while (([:len $item] > 0) and (([:pick $item ([:len $item] - 1) [:len $item]] = " ") or ([:pick $item ([:len $item] - 1) [:len $item]] = "\t"))) do={
:set item [:pick $item 0 ([:len $item] - 1)]
}
:if ([:len $item] > 0) do={
:local wildcard false
:if (([:len $item] > 2) and ([:pick $item 0 2] = "+.")) do={
:set wildcard true
:set item [:pick $item 2 [:len $item]]
}
:local schemePos [:find $item "://"]
:if ([:typeof $schemePos] != "nil") do={
:set wildcard false
:set item [:pick $item ($schemePos + 3) [:len $item]]
:local atPos [:find $item "@"]
:if ([:typeof $atPos] != "nil") do={ :set item [:pick $item ($atPos + 1) [:len $item]] }
:local slashPos [:find $item "/"]
:if ([:typeof $slashPos] != "nil") do={ :set item [:pick $item 0 $slashPos] }
:local queryPos [:find $item "?"]
:if ([:typeof $queryPos] != "nil") do={ :set item [:pick $item 0 $queryPos] }
:local hashPos [:find $item "#"]
:if ([:typeof $hashPos] != "nil") do={ :set item [:pick $item 0 $hashPos] }
:if (([:len $item] > 1) and ([:pick $item 0 1] = "[")) do={
:local closeBracket [:find $item "]"]
:if ([:typeof $closeBracket] != "nil") do={ :set item [:pick $item 1 $closeBracket] }
} else={
:local portPos [:find $item ":"]
:if ([:typeof $portPos] != "nil") do={ :set item [:pick $item 0 $portPos] }
}
} else={
:local slashPos [:find $item "/"]
:if ([:typeof $slashPos] != "nil") do={
:local firstCharForSlash [:pick $item 0 1]
:if (([:typeof [:find "0123456789" $firstCharForSlash]] = "nil") and ([:typeof [:find $item ":"]] = "nil")) do={
:set item [:pick $item 0 $slashPos]
:set wildcard false
}
}
}
:local isIp false
:local isIpv6 false
:if ([:typeof [:find $item ":"]] != "nil") do={
:set isIp true
:set isIpv6 true
}
:if ([:typeof [:find $item "/"]] != "nil") do={ :set isIp true }
:local firstChar [:pick $item 0 1]
:if ([:typeof [:find "0123456789" $firstChar]] != "nil") do={
:if ([:typeof [:find $item "."]] != "nil") do={ :set isIp true }
}
:if ($isIp = true) do={
:set streamAddrMode "diff"
:set repairAddrOrder "no"
:local itemKey $item
:local slash32 [:find $itemKey "/32"]
:if ([:typeof $slash32] != "nil") do={
:if (($slash32 + 3) = [:len $itemKey]) do={ :set itemKey [:pick $itemKey 0 $slash32] }
}
:local slash128 [:find $itemKey "/128"]
:if ([:typeof $slash128] != "nil") do={
:if (($slash128 + 4) = [:len $itemKey]) do={ :set itemKey [:pick $itemKey 0 $slash128] }
}
:if ($isIpv6 = true) do={
:local key ("|" . $itemKey . "|")
:if ([:typeof [:find $new6Set $key]] != "nil") do={
:set skippedAddr ($skippedAddr + 1)
} else={
:set new6Set ($new6Set . $itemKey . "|")
:set parsedIpv6 ($parsedIpv6 + 1)
:if ([:typeof [:find $old6Set $key]] != "nil") do={
:set matchedAddr ($matchedAddr + 1)
} else={
:set importBatch ($importBatch . ":do { /ipv6 firewall address-list add list=\"" . $addressList . "\" address=\"" . $item . "\" comment=\"" . $addrComment . "\"" . $addrTimeoutPart . " } on-error={}\n")
:set importBatchCount ($importBatchCount + 1)
:if ($addSampleCount < $debugSampleLimit) do={
:set addSampleCount ($addSampleCount + 1)
:set addSamples ($addSamples . $item . "; ")
}
}
}
} else={
:local key ("|" . $itemKey . "|")
:if ([:typeof [:find $new4Set $key]] != "nil") do={
:set skippedAddr ($skippedAddr + 1)
} else={
:set new4Set ($new4Set . $itemKey . "|")
:set parsedIpv4 ($parsedIpv4 + 1)
:if ([:typeof [:find $old4Set $key]] != "nil") do={
:set matchedAddr ($matchedAddr + 1)
} else={
:set importBatch ($importBatch . ":do { /ip firewall address-list add list=\"" . $addressList . "\" address=\"" . $item . "\" comment=\"" . $addrComment . "\"" . $addrTimeoutPart . " } on-error={}\n")
:set importBatchCount ($importBatchCount + 1)
:if ($addSampleCount < $debugSampleLimit) do={
:set addSampleCount ($addSampleCount + 1)
:set addSamples ($addSamples . $item . "; ")
}
}
}
}
:if ([:len $importBatch] > $importBatchBytes) do={
:local batchSize [:len $importBatch]
:local requiredFree ($batchSize * 4)
:local freeBytes 0
:local freeFound "no"
:if ([:len $importBatchDisk] > 0) do={
:foreach diskId in=[/disk find where slot=$importBatchDisk] do={
:if ($freeFound = "no") do={
:set freeBytes [/disk get $diskId free]
:set freeFound "yes"
}
}
}
:if ($freeFound = "no") do={ :set freeBytes [/system resource get free-hdd-space] }
:if ($freeBytes < $requiredFree) do={
:error ("dns-group " . $groupName . ": not enough free space for address-list add batch on " . $tmpDir . ", free=" . $freeBytes . ", need=" . $requiredFree)
}
:do { /file remove $importBatchFile } on-error={}
:do {
/file add name=$importBatchFile type=file contents=$importBatch
} on-error={
:do { /file remove $importBatchFile } on-error={}
/file add name=$importBatchFile type=file
/file set $importBatchFile contents=$importBatch
}
:do {
/import file-name=$importBatchFile
} on-error={
:do { /file remove $importBatchFile } on-error={}
:error ("dns-group " . $groupName . ": address-list add batch import failed")
}
:do { /file remove $importBatchFile } on-error={}
:set addedAddr ($addedAddr + $importBatchCount)
:set importBatch ""
:set importBatchCount 0
}
} else={
:local key ("|" . $item . "|")
:if ([:typeof [:find $seenDomains $key]] = "nil") do={
:set seenDomains ($seenDomains . $item . "|")
:set parsedDomains ($parsedDomains + 1)
:if ($wildcard = true) do={
:set orderedDnsItems ($orderedDnsItems . "W|" . $item . "\n")
} else={
:set orderedDnsItems ($orderedDnsItems . "E|" . $item . "\n")
}
}
}
}
:set manualItem ""
}
}
:if (($parsedDomains = 0) and ($parsedIpv4 = 0) and ($parsedIpv6 = 0)) do={
:error ("dns-group " . $groupName . ": no domains or IP addresses found in sources")
}
# Удаляем managed DNS-записи, которых больше нет в новом payload.
:foreach id in=[/ip dns static find where comment=$fwdComment] do={
:local n [/ip dns static get $id name]
:local k ("|" . $n . "|")
:if ([:typeof [:find $seenDomains $k]] = "nil") do={
:do {
/ip dns static remove $id
:set removedFwd ($removedFwd + 1)
} on-error={}
}
}
:foreach id in=[/ip dns static find where comment=$aaaaComment] do={
:local n [/ip dns static get $id name]
:local k ("|" . $n . "|")
:if (($blockIpv6 != "yes") or ([:typeof [:find $seenDomains $k]] = "nil")) do={
:do {
/ip dns static remove $id
:set removedAaaa ($removedAaaa + 1)
} on-error={}
}
}
# Финальная пачка добавлений IP/CIDR.
:if ([:len $importBatch] > 0) do={
:local batchSize [:len $importBatch]
:local requiredFree ($batchSize * 4)
:local freeBytes 0
:local freeFound "no"
:if ([:len $importBatchDisk] > 0) do={
:foreach diskId in=[/disk find where slot=$importBatchDisk] do={
:if ($freeFound = "no") do={
:set freeBytes [/disk get $diskId free]
:set freeFound "yes"
}
}
}
:if ($freeFound = "no") do={ :set freeBytes [/system resource get free-hdd-space] }
:if ($freeBytes < $requiredFree) do={
:error ("dns-group " . $groupName . ": not enough free space for final address-list add batch on " . $tmpDir . ", free=" . $freeBytes . ", need=" . $requiredFree)
}
:do { /file remove $importBatchFile } on-error={}
:do {
/file add name=$importBatchFile type=file contents=$importBatch
} on-error={
:do { /file remove $importBatchFile } on-error={}
/file add name=$importBatchFile type=file
/file set $importBatchFile contents=$importBatch
}
:do {
/import file-name=$importBatchFile
} on-error={
:do { /file remove $importBatchFile } on-error={}
:error ("dns-group " . $groupName . ": final address-list add batch import failed")
}
:do { /file remove $importBatchFile } on-error={}
:set addedAddr ($addedAddr + $importBatchCount)
:set importBatch ""
:set importBatchCount 0
}
# Удаляем managed IP/CIDR записи, которых нет в новом списке.
:if ($removeMissing = "yes") do={
:if ($streamAddrMode = "diff") do={
:local expectedRemove (($oldIpv4 + $oldIpv6) - $matchedAddr)
:if ($expectedRemove > 0) do={
:local oldLine ""
:for oldIndex from=0 to=([:len $old4Ids] - 1) do={
:local oldChar [:pick $old4Ids $oldIndex]
:if ($oldChar != "\n") do={
:set oldLine ($oldLine . $oldChar)
} else={
:if ([:len $oldLine] > 0) do={
:local sep [:find $oldLine "|"]
:if ([:typeof $sep] != "nil") do={
:local id [:pick $oldLine 0 $sep]
:local addr [:pick $oldLine ($sep + 1) [:len $oldLine]]
:local key ("|" . $addr . "|")
:if ([:typeof [:find $new4Set $key]] = "nil") do={
:set importBatch ($importBatch . ":do { /ip firewall address-list remove " . $id . " } on-error={}\n")
:set importBatchCount ($importBatchCount + 1)
:if ($removeSampleCount < $debugSampleLimit) do={
:set removeSampleCount ($removeSampleCount + 1)
:set removeSamples ($removeSamples . $addr . "; ")
}
}
}
}
:set oldLine ""
}
:if ([:len $importBatch] > $importBatchBytes) do={
:do { /file remove $importBatchFile } on-error={}
/file add name=$importBatchFile type=file contents=$importBatch
:do { /import file-name=$importBatchFile } on-error={
:do { /file remove $importBatchFile } on-error={}
:error ("dns-group " . $groupName . ": address-list remove batch import failed")
}
:do { /file remove $importBatchFile } on-error={}
:set removedAddr ($removedAddr + $importBatchCount)
:set importBatch ""
:set importBatchCount 0
}
}
:set oldLine ""
:for oldIndex from=0 to=([:len $old6Ids] - 1) do={
:local oldChar [:pick $old6Ids $oldIndex]
:if ($oldChar != "\n") do={
:set oldLine ($oldLine . $oldChar)
} else={
:if ([:len $oldLine] > 0) do={
:local sep [:find $oldLine "|"]
:if ([:typeof $sep] != "nil") do={
:local id [:pick $oldLine 0 $sep]
:local addr [:pick $oldLine ($sep + 1) [:len $oldLine]]
:local key ("|" . $addr . "|")
:if ([:typeof [:find $new6Set $key]] = "nil") do={
:set importBatch ($importBatch . ":do { /ipv6 firewall address-list remove " . $id . " } on-error={}\n")
:set importBatchCount ($importBatchCount + 1)
:if ($removeSampleCount < $debugSampleLimit) do={
:set removeSampleCount ($removeSampleCount + 1)
:set removeSamples ($removeSamples . $addr . "; ")
}
}
}
}
:set oldLine ""
}
:if ([:len $importBatch] > $importBatchBytes) do={
:do { /file remove $importBatchFile } on-error={}
/file add name=$importBatchFile type=file contents=$importBatch
:do { /import file-name=$importBatchFile } on-error={
:do { /file remove $importBatchFile } on-error={}
:error ("dns-group " . $groupName . ": address-list remove batch import failed")
}
:do { /file remove $importBatchFile } on-error={}
:set removedAddr ($removedAddr + $importBatchCount)
:set importBatch ""
:set importBatchCount 0
}
}
}
} else={
:foreach id in=[/ip firewall address-list find where comment=$addrComment] do={
:do { /ip firewall address-list remove $id; :set removedAddr ($removedAddr + 1) } on-error={}
}
:foreach id in=[/ipv6 firewall address-list find where comment=$addrComment] do={
:do { /ipv6 firewall address-list remove $id; :set removedAddr ($removedAddr + 1) } on-error={}
}
}
:foreach id in=[/ip firewall address-list find where comment=$addrComment] do={
:local listName [/ip firewall address-list get $id list]
:if ($listName != $addressList) do={
:do { /ip firewall address-list remove $id; :set removedAddr ($removedAddr + 1) } on-error={}
}
}
:foreach id in=[/ipv6 firewall address-list find where comment=$addrComment] do={
:local listName [/ipv6 firewall address-list get $id list]
:if ($listName != $addressList) do={
:do { /ipv6 firewall address-list remove $id; :set removedAddr ($removedAddr + 1) } on-error={}
}
}
}
:if ([:len $importBatch] > 0) do={
:do { /file remove $importBatchFile } on-error={}
/file add name=$importBatchFile type=file contents=$importBatch
:do { /import file-name=$importBatchFile } on-error={
:do { /file remove $importBatchFile } on-error={}
:error ("dns-group " . $groupName . ": final address-list remove batch import failed")
}
:do { /file remove $importBatchFile } on-error={}
:set removedAddr ($removedAddr + $importBatchCount)
:set importBatch ""
:set importBatchCount 0
}
# DNS: создаём недостающие записи сразу в нужном месте через place-before.
:local dnsTargetAddressList $addressList
:local reverseDnsItems ""
:local current ""
:for i from=0 to=([:len $orderedDnsItems] - 1) do={
:local char [:pick $orderedDnsItems $i]
:if ($char != "\n") do={
:set current ($current . $char)
} else={
:if ([:len $current] > 2) do={ :set reverseDnsItems ($current . "\n" . $reverseDnsItems) }
:set current ""
}
}
:local reverseDnsIds ""
:local dnsAnchor ""
:set current ""
:for i from=0 to=([:len $reverseDnsItems] - 1) do={
:local char [:pick $reverseDnsItems $i]
:if ($char != "\n") do={
:set current ($current . $char)
} else={
:if ([:len $current] > 2) do={
:local kind [:pick $current 0 1]
:local domain [:pick $current 2 [:len $current]]
:local wildcard false
:if ($kind = "W") do={ :set wildcard true }
:local fwdIds [/ip dns static find where comment=$fwdComment and name=$domain and type=FWD]
:if ([:len $fwdIds] = 0) do={
:if ($wildcard = true) do={
:if ([:len $forwardTo] > 0) do={
:if ([:len $dnsAnchor] > 0) do={
/ip dns static add name=$domain type=FWD address-list=$dnsTargetAddressList forward-to=$forwardTo match-subdomain=yes comment=$fwdComment place-before=$dnsAnchor
} else={
/ip dns static add name=$domain type=FWD address-list=$dnsTargetAddressList forward-to=$forwardTo match-subdomain=yes comment=$fwdComment
}
} else={
:if ([:len $dnsAnchor] > 0) do={
/ip dns static add name=$domain type=FWD address-list=$dnsTargetAddressList match-subdomain=yes comment=$fwdComment place-before=$dnsAnchor
} else={
/ip dns static add name=$domain type=FWD address-list=$dnsTargetAddressList match-subdomain=yes comment=$fwdComment
}
}
} else={
:if ([:len $forwardTo] > 0) do={
:if ([:len $dnsAnchor] > 0) do={
/ip dns static add name=$domain type=FWD address-list=$dnsTargetAddressList forward-to=$forwardTo comment=$fwdComment place-before=$dnsAnchor
} else={
/ip dns static add name=$domain type=FWD address-list=$dnsTargetAddressList forward-to=$forwardTo comment=$fwdComment
}
} else={
:if ([:len $dnsAnchor] > 0) do={
/ip dns static add name=$domain type=FWD address-list=$dnsTargetAddressList comment=$fwdComment place-before=$dnsAnchor
} else={
/ip dns static add name=$domain type=FWD address-list=$dnsTargetAddressList comment=$fwdComment
}
}
}
:set addedFwd ($addedFwd + 1)
:set fwdIds [/ip dns static find where comment=$fwdComment and name=$domain and type=FWD]
}
:local fwdFirst ""
:local fwdReverse ""
:foreach fwdId in=$fwdIds do={
:if ([:len $fwdFirst] = 0) do={ :set fwdFirst $fwdId }
:set fwdReverse ($fwdId . "\n" . $fwdReverse)
}
:local aaaaFirst ""
:local aaaaReverse ""
:if ($blockIpv6 = "yes") do={
:local aaaaIds [/ip dns static find where comment=$aaaaComment and name=$domain and type=AAAA]
:if ([:len $aaaaIds] = 0) do={
:local beforeId $fwdFirst
:if ([:len $beforeId] = 0) do={ :set beforeId $dnsAnchor }
:if ($wildcard = true) do={
:if ([:len $beforeId] > 0) do={
/ip dns static add name=$domain type=AAAA address=$ipv6Block match-subdomain=yes comment=$aaaaComment place-before=$beforeId
} else={
/ip dns static add name=$domain type=AAAA address=$ipv6Block match-subdomain=yes comment=$aaaaComment
}
} else={
:if ([:len $beforeId] > 0) do={
/ip dns static add name=$domain type=AAAA address=$ipv6Block comment=$aaaaComment place-before=$beforeId
} else={
/ip dns static add name=$domain type=AAAA address=$ipv6Block comment=$aaaaComment
}
}
:set addedAaaa ($addedAaaa + 1)
:set aaaaIds [/ip dns static find where comment=$aaaaComment and name=$domain and type=AAAA]
}
:foreach aaaaId in=$aaaaIds do={
:if ([:len $aaaaFirst] = 0) do={ :set aaaaFirst $aaaaId }
:set aaaaReverse ($aaaaId . "\n" . $aaaaReverse)
}
:set reverseDnsIds ($reverseDnsIds . $fwdReverse . $aaaaReverse)
:if ([:len $aaaaFirst] > 0) do={
:set dnsAnchor $aaaaFirst
} else={
:if ([:len $fwdFirst] > 0) do={ :set dnsAnchor $fwdFirst }
}
} else={
:set reverseDnsIds ($reverseDnsIds . $fwdReverse)
:if ([:len $fwdFirst] > 0) do={ :set dnsAnchor $fwdFirst }
}
}
:set current ""
}
}
# Общий быстрый ремонт порядка для DNS static.
:if ($repairOrder = "yes") do={
:local desiredIds ""
:local itemId ""
:for i from=0 to=([:len $reverseDnsIds] - 1) do={
:local char [:pick $reverseDnsIds $i]
:if ($char != "\n") do={
:set itemId ($itemId . $char)
} else={
:if ([:len $itemId] > 0) do={ :set desiredIds ($itemId . "\n" . $desiredIds) }
:set itemId ""
}
}
:local desiredSet "|"
:set itemId ""
:for i from=0 to=([:len $desiredIds] - 1) do={
:local char [:pick $desiredIds $i]
:if ($char != "\n") do={
:set itemId ($itemId . $char)
} else={
:if ([:len $itemId] > 0) do={ :set desiredSet ($desiredSet . $itemId . "|") }
:set itemId ""
}
}
:local currentManagedIds ""
:foreach id in=[/ip dns static find] do={
:local key ("|" . $id . "|")
:if ([:typeof [:find $desiredSet $key]] != "nil") do={ :set currentManagedIds ($currentManagedIds . $id . "\n") }
}
:if ($currentManagedIds != $desiredIds) do={
:local anchor ""
:local currentId ""
:for i from=0 to=([:len $reverseDnsIds] - 1) do={
:local char [:pick $reverseDnsIds $i]
:if ($char != "\n") do={
:set currentId ($currentId . $char)
} else={
:if ([:len $currentId] > 0) do={
:local nextId ""
:local found false
:local scanId ""
:for j from=0 to=([:len $currentManagedIds] - 1) do={
:local scanChar [:pick $currentManagedIds $j]
:if ($scanChar != "\n") do={
:set scanId ($scanId . $scanChar)
} else={
:if ([:len $scanId] > 0) do={
:if (($found = true) and ([:len $nextId] = 0)) do={ :set nextId $scanId }
:if ($scanId = $currentId) do={ :set found true }
}
:set scanId ""
}
}
:local needMove false
:if ([:len $anchor] > 0) do={
:if ($nextId != $anchor) do={ :set needMove true }
} else={
:if ([:len $nextId] > 0) do={ :set needMove true }
}
:if ($needMove = true) do={
:if ([:len $anchor] > 0) do={
:do {
/ip dns static move $currentId destination=$anchor
:set movedDns ($movedDns + 1)
} on-error={}
} else={
:do {
/ip dns static move $currentId
:set movedDns ($movedDns + 1)
} on-error={}
}
:local withoutCurrent ""
:set scanId ""
:for j from=0 to=([:len $currentManagedIds] - 1) do={
:local scanChar [:pick $currentManagedIds $j]
:if ($scanChar != "\n") do={
:set scanId ($scanId . $scanChar)
} else={
:if (([:len $scanId] > 0) and ($scanId != $currentId)) do={ :set withoutCurrent ($withoutCurrent . $scanId . "\n") }
:set scanId ""
}
}
:local rebuilt ""
:local inserted false
:set scanId ""
:for j from=0 to=([:len $withoutCurrent] - 1) do={
:local scanChar [:pick $withoutCurrent $j]
:if ($scanChar != "\n") do={
:set scanId ($scanId . $scanChar)
} else={
:if ([:len $scanId] > 0) do={
:if (($inserted = false) and ([:len $anchor] > 0) and ($scanId = $anchor)) do={
:set rebuilt ($rebuilt . $currentId . "\n")
:set inserted true
}
:set rebuilt ($rebuilt . $scanId . "\n")
}
:set scanId ""
}
}
:if ($inserted = false) do={ :set rebuilt ($rebuilt . $currentId . "\n") }
:set currentManagedIds $rebuilt
}
:set anchor $currentId
}
:set currentId ""
}
}
}
}
:set changedCount ($addedFwd + $addedAaaa + $addedAddr + $removedFwd + $removedAaaa + $removedAddr + $movedDns + $movedAddr)
:if ($changedCount > 0) do={
/ip dns cache flush
:set cacheFlushed "yes"
}
:log info ("dns-group " . $groupName . ": elapsed=" . ([:timestamp] - $startedAt) . ", domains=" . $parsedDomains . ", ipv4=" . $parsedIpv4 . ", ipv6=" . $parsedIpv6 . ", old ipv4=" . $oldIpv4 . ", old ipv6=" . $oldIpv6 . ", matched old=" . $matchedAddr . ", added FWD=" . $addedFwd . ", added AAAA=" . $addedAaaa . ", added addr=" . $addedAddr . ", removed FWD=" . $removedFwd . ", removed AAAA=" . $removedAaaa . ", removed addr=" . $removedAddr . ", moved DNS=" . $movedDns . ", moved addr=" . $movedAddr . ", skipped duplicates=" . $skippedAddr . ", removeMissing=" . $removeMissing . ", streamAddrMode=" . $streamAddrMode . ", repairAddrOrder=" . $repairAddrOrder . ", cacheFlushed=" . $cacheFlushed)
:if ([:len $addSamples] > 0) do={ :log info ("dns-group " . $groupName . ": add samples: " . $addSamples) }
:if ([:len $removeSamples] > 0) do={ :log info ("dns-group " . $groupName . ": remove samples: " . $removeSamples) }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment