|
# ========================= |
|
# Настройки пользователя |
|
# ========================= |
|
|
|
# Ссылки на 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) } |