Скрипт, который пишет другой скрипт и настраивает роутеры
Производители сетевого оборудования потихоньку двигаются в сторону универсального API для настройки и сбора показателей: есть NETCONF, OpenConfig, существует ПО для импорта MIB, та же настройка с помощью SNMP существует давно. Но я не буду касаться этих высоких материй, а просто поделюсь способом автоматизировать настройку сетевого оборудования — на случай массового открытия филиалов, например,
Для иллюстрации использую D-Link DFL-800 в воображаемом центральном офисе и MikroTik RB951UI-2HnD на периферии. В частности, настроим между ними туннель IPsec, раз уж речь про сценарий с новым филиалом.
Настройка MikroTik
Проще всего настраивать MikroTik с помощью конфигурационного файла, который представляет собой скрипт с набором консольных команд. Описываемый принцип настройки подойдет и для других маршрутизаторов с аналогичным механизмом задания параметров.
Прежде всего получим конфигурационный файл уже настроенного и работающего MikroTik, с помощью команды /export. Файл будет практически идентичен бэкапу, но в нем не будет имен пользователей и MAC-адресов.
Чтобы применить конфигурационный файл на другом роутере, часть параметров нужно изменить на уникальные. Для этого подойдут возможности встроенного в прошивку скриптового языка — в частности, вынос индивидуальных настроек в переменные. Вот пример скрипта с назначением шлюза по умолчанию:
local Gate "10.1.3.1"
/ip route
:if ([:len [/ip route find distance=1]] = 0) do={add distance=1 gateway=$Gate;
} else={set [find distance=1] gateway=$Gate}
Изначально была идея создать универсальный скрипт, который можно применять без сброса конфигурации. Поэтому в коде проводится проверка на наличие маршрута по умолчанию, после чего он создается с нуля или меняется. Но от этой идеи пришлось отказаться, поскольку в свежих прошивках в конфигурацию по умолчанию постоянно добавляются изменения. Предусмотреть все нереально, а при постоянном обновлении скрипта теряется смысл автоматизации.
Индивидуальные параметры роутера удобнее задавать не в текстовом файле, а в GUI — тогда с настройкой маршрутизатора справятся даже незнакомые со спецификой устройства люди. То что нужно для удаленного офиса, который могут обслуживать сотрудники разной квалификации.
GUI я отрисовываю в скрипте, разработанном на AutoIT. В этот скрипт я также добавил функцию пересчета масок сети (255.255.255.0 = /24) и построчное заполнение конфигурационного файла. Сам инструмент разработки для решения задачи не принципиален — можно использовать и что-то более привычное лично вам.
Получившийся интерфейс настройки
Разберем подробнее функцию, которая формирует окно.
Для работы всех функций будущего конфигуратора потребуются следующие библиотеки, в основном из комплекта AutoIT:
RandomString для генерации ключа IPsec (в комплект не входит, автор Altlans);
GuiIPAddress для формы ввода IP-адресов;
GUIConstantsEx для работы GUI;
- File для работы с файлом.
С принципами работы с графическим интерфейсом в AutoIT можно ознакомиться в официальной документации, я же сразу перейду к результату:
$hgui = GUICreate("МикроТик" , 300, 600)
GUICtrlCreateLabel("введите локальный адрес роутера",2,2)
$iploc1 = _GUICtrlIpAddress_Create($hgui, 10, 20)
GUICtrlCreateLabel("введите локальную маску",2,55)
$iplocnet1 = _GUICtrlIpAddress_Create($hgui, 10, 75)
GUICtrlCreateLabel("введите внешний адрес",2,115)
$ipext1 = _GUICtrlIpAddress_Create($hgui, 10, 135)
GUICtrlCreateLabel("введите внешнюю маску",2,170)
$ipextnet1 = _GUICtrlIpAddress_Create($hgui, 10, 190)
GUICtrlCreateLabel("введите шлюз",2,225)
$ipgate1 = _GUICtrlIpAddress_Create($hgui, 10, 245)
GUICtrlCreateLabel("введите ДНС1",2,280)
$DNS11 = _GUICtrlIpAddress_Create($hgui, 10, 300)
GUICtrlCreateLabel("введите ДНС2",2,335)
$DNS21 = _GUICtrlIpAddress_Create($hgui, 10, 355)
GUICtrlCreateLabel("введите ключ на ипсек",2,390)
$secret1 = GUICtrlCreateInput("",10, 410)
GUICtrlCreateLabel("введите порты для банка через запятую",2,445)
$ports1 = GUICtrlCreateInput("5000,8000",10, 480)
GUICtrlCreateLabel("введите мак кассы через:",2,515)
$mac1 = GUICtrlCreateInput("00:00:00:00:00:00",10, 535)
$OK_Btn = GUICtrlCreateButton("Поехали", 75, 570, 70, 25)
_GUICtrlIpAddress_Set($iploc1, $iploc8)
_GUICtrlIpAddress_Set($ipext1, $ipext8)
_GUICtrlIpAddress_Set($iplocnet1, "255.255.255.0")
_GUICtrlIpAddress_Set($ipextnet1, "255.255.255.252")
if $secret8 = "" Then
GUICtrlSetData($secret1,_Crypto_GetRandomString(12,7))
Else
GUICtrlSetData($secret1,$secret8)
EndIf
GUISetState(@SW_SHOW)
While 1
$msg = GUIGetMsg()
Select
Case $msg = $GUI_EVENT_CLOSE
Exit
Case $msg = $OK_Btn
;при нажатии кнопки "Поехали" считываем значения в переменные
$iploc=_GUICtrlIpAddress_Get($iploc1)
$iplocnet=_GUICtrlIpAddress_Get($iplocnet1)
$DNS1=_GUICtrlIpAddress_Get($DNS11)
$DNS2=_GUICtrlIpAddress_Get($DNS21)
$ipext=_GUICtrlIpAddress_Get($ipext1)
$ipextnet=_GUICtrlIpAddress_Get($ipextnet1)
$ipgate=_GUICtrlIpAddress_Get($ipgate1)
$secret=GUICtrlRead($secret1)
$ports=GUICtrlRead($ports1)
$mac=GUICtrlRead($mac1)
$iploc2=_GUICtrlIpAddress_GetArray($iploc1)
;в организации адреса банковских терминалов статические
$ipterm1 = $iploc2[0] & "." &$iploc2[1] & "." &$iploc2[2] & ".210"
$ipterm2 = $iploc2[0] & "." &$iploc2[1] & "." &$iploc2[2] & ".220"
if $iploc = 0 or $iplocnet=0 or $DNS1=0 or $DNS2=0 or $ipext=0 or $ipextnet=0 or $ipgate=0 or StringLen($secret)=0 then
MsgBox(4160, "Information", "поля не заполнены")
else
GUIDelete()
ExitLoop
EndIf
EndSelect
WEnd
Дальше логика работы программы следующая: новый текстовый файл построчно заполняется полученными значениями с помощью FileWriteLine. Полученный скрипт-конфиг можно переносить на MikroTik.
#include
#include
#include
#include
_mikrot()
func _mikrot ($iploc8="", $secret8="", $ipext8="")
local $file = @TempDir &"\script.txt"
$hgui = GUICreate("МикроТик" , 300, 600)
GUICtrlCreateLabel("введите локальный адрес роутера",2,2)
$iploc1 = _GUICtrlIpAddress_Create($hgui, 10, 20)
GUICtrlCreateLabel("введите локальную маску",2,55)
$iplocnet1 = _GUICtrlIpAddress_Create($hgui, 10, 75)
GUICtrlCreateLabel("введите внешний адрес",2,115)
$ipext1 = _GUICtrlIpAddress_Create($hgui, 10, 135)
GUICtrlCreateLabel("введите внешнюю маску",2,170)
$ipextnet1 = _GUICtrlIpAddress_Create($hgui, 10, 190)
GUICtrlCreateLabel("введите шлюз",2,225)
$ipgate1 = _GUICtrlIpAddress_Create($hgui, 10, 245)
GUICtrlCreateLabel("введите ДНС1",2,280)
$DNS11 = _GUICtrlIpAddress_Create($hgui, 10, 300)
GUICtrlCreateLabel("введите ДНС2",2,335)
$DNS21 = _GUICtrlIpAddress_Create($hgui, 10, 355)
GUICtrlCreateLabel("введите ключ на ипсек",2,390)
$secret1 = GUICtrlCreateInput("",10, 410)
GUICtrlCreateLabel("введите порты для банка через запятую",2,445)
$ports1 = GUICtrlCreateInput("5000,8000",10, 480)
GUICtrlCreateLabel("введите мак кассы через:",2,515)
$mac1 = GUICtrlCreateInput("00:00:00:00:00:00",10, 535)
$OK_Btn = GUICtrlCreateButton("Поехали", 75, 570, 70, 25)
_GUICtrlIpAddress_Set($iploc1, $iploc8)
_GUICtrlIpAddress_Set($ipext1, $ipext8)
_GUICtrlIpAddress_Set($iplocnet1, "255.255.255.0")
_GUICtrlIpAddress_Set($ipextnet1, "255.255.255.252")
if $secret8 = "" Then
GUICtrlSetData($secret1,_Crypto_GetRandomString(12,7))
Else
GUICtrlSetData($secret1,$secret8)
EndIf
GUISetState(@SW_SHOW)
While 1
$msg = GUIGetMsg()
Select
Case $msg = $GUI_EVENT_CLOSE
Exit
Case $msg = $OK_Btn
$iploc=_GUICtrlIpAddress_Get($iploc1)
$iplocnet=_GUICtrlIpAddress_Get($iplocnet1)
$DNS1=_GUICtrlIpAddress_Get($DNS11)
$DNS2=_GUICtrlIpAddress_Get($DNS21)
$ipext=_GUICtrlIpAddress_Get($ipext1)
$ipextnet=_GUICtrlIpAddress_Get($ipextnet1)
$ipgate=_GUICtrlIpAddress_Get($ipgate1)
$secret=GUICtrlRead($secret1)
$ports=GUICtrlRead($ports1)
$mac=GUICtrlRead($mac1)
$iploc2=_GUICtrlIpAddress_GetArray($iploc1)
;в организации адреса банковских терминалов статические
$ipterm1 = $iploc2[0] & "." &$iploc2[1] & "." &$iploc2[2] & ".210"
$ipterm2 = $iploc2[0] & "." &$iploc2[1] & "." &$iploc2[2] & ".220"
if $iploc = 0 or $iplocnet=0 or $DNS1=0 or $DNS2=0 or $ipext=0 or $ipextnet=0 or $ipgate=0 or StringLen($secret)=0 then
MsgBox(4160, "Information", "поля не заполнены")
else
GUIDelete()
ExitLoop
EndIf
EndSelect
WEnd
$temp1=_Subnet($iploc, $iplocnet)
$iplocnet=$temp1[1]&"/"&$temp1[6]
$temp1=_Subnet($ipext, $ipextnet)
$ipext=$ipext&"/"&$temp1[6]
If Not _FileCreate($file) Then
MsgBox(4096, "Error", "ошибка создания временного файла:" & @error)
EndIf
FileOpen ( $file, 2)
FileWriteLine ( $file, "local locIP """&$iploc&""";")
FileWriteLine ( $file, "local Net """&$iplocnet&""";")
FileWriteLine ( $file, "local DNS1 """& $DNS1 & """;")
FileWriteLine ( $file, "local DNS2 """&$DNS2&""";")
FileWriteLine ( $file, "local IP """&$ipext&"""")
FileWriteLine ( $file, "local Gate """&$ipgate&"""")
FileWriteLine ( $file, "local MAC """&$mac&"""")
FileWriteLine ( $file, "local secret """&$secret&"""")
FileWriteLine ( $file, "local BankPorts """&$ports&""" ")
FileWriteLine ( $file, "local term1 """&$ipterm1&"""")
FileWriteLine ( $file, "local term2 """&$ipterm2&""" ")
FileWriteLine ( $file, '/interface set ether1 name=ether1-gateway;')
FileWriteLine ( $file, '/interface set ether2 name=ether2-master-local;')
FileWriteLine ( $file, '/interface set ether3 name=ether3-slave-local;')
FileWriteLine ( $file, '/interface set ether4 name=ether4-slave-local;')
FileWriteLine ( $file, '/interface set ether5 name=ether5-slave-local;')
FileWriteLine ( $file, '/interface ethernet set ether3-slave-local master-port=ether2-master-local;')
FileWriteLine ( $file, '/interface ethernet set ether4-slave-local master-port=ether2-master-local;')
FileWriteLine ( $file, '/interface ethernet set ether5-slave-local master-port=ether2-master-local;')
FileWriteLine ( $file, '/interface bridge add name=bridge-local disabled=no auto-mac=no protocol-mode=rstp;')
FileWriteLine ( $file, ':local bMACIsSet 0;')
FileWriteLine ( $file, ':foreach k in=[/interface find] do={:local tmpPort [/interface get $k name];')
FileWriteLine ( $file, ':if ($bMACIsSet = 0) do={:if ([/interface get $k type] = "ether") do={/interface bridge set "bridge-local" admin-mac=[/interface ethernet get $tmpPort mac-address];')
FileWriteLine ( $file, ':set bMACIsSet 1;}}')
FileWriteLine ( $file, ':if (!($tmpPort~"bridge" || $tmpPort~"ether1" || $tmpPort~"slave")) do={')
FileWriteLine ( $file, '/interface bridge port add bridge=bridge-local interface=$tmpPort;}} ')
FileWriteLine ( $file, '/interface wireless set [ find name=wlan1 ] band=2ghz-b/g/n channel-width=20/40mhz-Ce country=japan disabled=no distance=indoors mode=ap-bridge ssid=MikroTik wireless-protocol=802.11;')
FileWriteLine ( $file, '/interface wireless security-profiles')
FileWriteLine ( $file, 'set [ find default=yes ] authentication-types=wpa-psk group-ciphers=tkip \')
FileWriteLine ( $file, ' mode=dynamic-keys unicast-ciphers=tkip wpa-pre-shared-key=\')
FileWriteLine ( $file, ' MegaWiFiKey wpa2-pre-shared-key=MegaWiFiKey')
FileWriteLine ( $file, '/ip ipsec proposal set [ find default=yes ] auth-algorithms=md5 lifetime=1h')
FileWriteLine ( $file, '/ip pool')
FileWriteLine ( $file, 'add name=default-dhcp ranges=("$locIP". "1". "-". "$locIP". "00");')
FileWriteLine ( $file, '/ip dhcp-server add address-pool=default-dhcp disabled=no interface=bridge-local name=default')
FileWriteLine ( $file, '/ip dhcp-server network add address=$Net comment="default configuration" dns-server="$locIP,8.8.8.8" gateway=$locIP netmask=24')
FileWriteLine ( $file, '/ip dns')
FileWriteLine ( $file, 'set allow-remote-requests=yes servers=("$DNS1". ",". "$DNS2")')
FileWriteLine ( $file, '/ip firewall address-list')
FileWriteLine ( $file, ':if ([:len [/ip firewall address-list find list=local]] = 0) do={add address=$Net list=local;')
FileWriteLine ( $file, '} else={set [find list=local] address=$Net;}')
FileWriteLine ( $file, ':if ([:len [/ip firewall address-list find list=office]] = 0) do={add address=10.0.0.0/24 list=office;}') ;локальная сеть офиса
FileWriteLine ( $file, ':if ([:len [/ip firewall address-list find list=remote]] = 0) do={add address=1.2.3.4 list=remote comment=office1.domain.ru;')
FileWriteLine ( $file, 'add address=1.2.3.5 list=remote comment=office2.domain.ru;')
FileWriteLine ( $file, 'add address=10.0.0.0/24 list=remote;}')
FileWriteLine ( $file, '/ip address')
FileWriteLine ( $file, 'add address="$locIP/24" interface=bridge-local')
FileWriteLine ( $file, ':if ([:len [/ip address find interface=ether1-gateway]] = 0) do={add address=$IP interface=ether1-gateway;')
FileWriteLine ( $file, '} else={set [find interface=ether1-gateway] address=$IP;}')
FileWriteLine ( $file, '/ip route')
FileWriteLine ( $file, ':if ([:len [/ip route find distance=1]] = 0) do={add distance=1 gateway=$Gate;')
FileWriteLine ( $file, '} else={set [find distance=1] gateway=$Gate}')
FileWriteLine ( $file, ':delay 1000ms;')
FileWriteLine ( $file, '/ip ipsec peer')
FileWriteLine ( $file, '/ip ipsec proposal set default auth-algorithms=md5 enc-algorithms=3des')
FileWriteLine ( $file, ':if ([:len [/ip ipsec peer find lifetime=8h]] = 0) do={add address=1.2.3.4/32 lifetime=8h nat-traversal=yes secret=$secret enc-algorithm=3des hash-algorithm=md5 comment=office;')
FileWriteLine ( $file, '} else={set [/ip ipsec peer find lifetime=8h] address=1.2.3.5/32 nat-traversal=yes secret=$secret enc-algorithm=3des hash-algorithm=md5 comment=office;}')
FileWriteLine ( $file, '/ip ipsec policy')
FileWriteLine ( $file, '/ip ipsec proposal set default auth-algorithms=md5 enc-algorithms=3des')
FileWriteLine ( $file, ':if ([:len [/ip ipsec policy find dst-address=10.0.0.0/24]] = 0) do={add dst-address=10.0.0.0/24 sa-dst-address=188.93.243.170 sa-src-address=[/ip route get [find gateway=ether1-gateway] pref-src] src-address=$Net tunnel=yes comment=office proposal=default;')
FileWriteLine ( $file, '} else={set [find dst-address=10.0.0.0/24] sa-dst-address=1.2.3.4 sa-src-address =[/ip route get [find gateway=ether1-gateway] pref-src] src-address=$Net tunnel=yes comment=office;} ')
FileWriteLine ( $file, '/ip service')
FileWriteLine ( $file, 'set telnet disabled=yes')
FileWriteLine ( $file, 'set ftp disabled=yes')
FileWriteLine ( $file, 'set www port=80')
FileWriteLine ( $file, 'set api disabled=yes')
FileWriteLine ( $file, 'set api-ssl disabled=yes')
FileWriteLine ( $file, 'set www-ssl disabled=yes')
FileWriteLine ( $file, '/system clock manual')
FileWriteLine ( $file, 'set time-zone=+03:00')
FileWriteLine ( $file, '/system leds')
FileWriteLine ( $file, 'set 0 interface=wlan1')
FileWriteLine ( $file, '/system ntp client')
FileWriteLine ( $file, 'set enabled=yes primary-ntp=61.67.210.241 secondary-ntp=205.171.76.135')
FileWriteLine ( $file, '/system script')
FileWriteLine ( $file, ':if ([:len [/system script find name=ipsec]] != 0) do={')
FileWriteLine ( $file, 'remove ipsec')
FileWriteLine ( $file, '}')
;скрипт переключения IPsec на резервного провайдера.
;заодно keepalive и резолвер офисных адресов
;до кучи проверка на ошибки
FileWriteLine ( $file, 'add name=ipsec policy=reboot,read,write,policy,test,password,sniff,sensitive \')
FileWriteLine ( $file, ' source=":local RemoteGw \"локальный адрес DFL\";\r\')
FileWriteLine ( $file, ' \n:local LocalGw [/ip route get [find gateway=\"bridge-local\"] pref-src];\')
FileWriteLine ( $file, ' \r\')
FileWriteLine ( $file, ' \n:local currentIP [/ip route get [find gateway=\"ether1-gateway\"] pref-s\')
FileWriteLine ( $file, ' rc];\r\')
FileWriteLine ( $file, ' \n:local Ip1 [:resolve office1.domain.ru];\r\')
FileWriteLine ( $file, ' \n:local Ip2 [:resolve office2.domain.ru];\r\')
FileWriteLine ( $file, ' \n### Check office IPs ###\r\')
FileWriteLine ( $file, ' \n:local Ip [/ip firewall address-list get [find comment=office1.dom\')
FileWriteLine ( $file, ' ain.ru] address];\r\')
FileWriteLine ( $file, ' \n:if (\$Ip!=\$Ip2) do={\r\')
FileWriteLine ( $file, ' \n/ip firewall address-list set [find comment=office1.domain.ru] addr\')
FileWriteLine ( $file, ' ess=\$Ip2;\r\')
FileWriteLine ( $file, ' \n}\r\')
FileWriteLine ( $file, ' \n:set Ip [/ip firewall address-list get [find comment=office2.domain\')
FileWriteLine ( $file, ' .ru] address];\r\')
FileWriteLine ( $file, ' \n:if (\$Ip!=\$Ip1) do={\r\')
FileWriteLine ( $file, ' \n/ip firewall address-list set [find comment=office2.domain.ru] addr\')
FileWriteLine ( $file, ' ess=\$Ip1;\r\')
FileWriteLine ( $file, ' \n}\r\')
FileWriteLine ( $file, ' \n### Check ipsec status ###\r\')
FileWriteLine ( $file, ' \n:local Status [/ping \$RemoteGw count=1 src-address=\$LocalGw];\r\')
FileWriteLine ( $file, ' \n### ipsec is down ###\r\')
FileWriteLine ( $file, ' \n:if (\$Status=0) do={\r\')
FileWriteLine ( $file, ' \n### ping Ip2 and Ip1 ###\r\')
FileWriteLine ( $file, ' \n:local StatusIp1 [/ping \$Ip1 count=2 src-address=\$currentIP];\r\')
FileWriteLine ( $file, ' \n:local StatusIp2 [/ping \$Ip2 count=2 src-address=\$currentIP];\r\')
FileWriteLine ( $file, ' \n:if (\$StatusIp1>0 or \$StatusIp2>0) do={\r\')
FileWriteLine ( $file, ' \n:if (\$StatusIp1>0) do={\r\')
FileWriteLine ( $file, ' \n### Ping Ip1 ok ###\r\')
FileWriteLine ( $file, ' \n/ip ipsec policy set [find sa-dst-address=\$Ip2] sa-dst-address \$Ip1;\r\')
FileWriteLine ( $file, ' \n/ip ipsec peer set [find address=\"\$Ip2/32\"] address \"\$Ip1/32\";\r\')
FileWriteLine ( $file, ' \n:log info (\"Reset tunnel: Status IP Office1: \$StatusIp1\");\r\')
FileWriteLine ( $file, ' \n} else={\r\')
FileWriteLine ( $file, ' \n### Ping Ip1 NOT ok ###\r\')
FileWriteLine ( $file, ' \n/ip ipsec policy set [find sa-dst-address=\$Ip1] sa-dst-address \$Ip2;\r\')
FileWriteLine ( $file, ' \n/ip ipsec peer set [find address=\"\$Ip1/32\"] address \"\$Ip2/32\";\r\')
FileWriteLine ( $file, ' \n:log info (\"Reset tunnel: Status IP Office2: \$StatusIp2\");\r\')
FileWriteLine ( $file, ' \n}\r\')
FileWriteLine ( $file, ' \n/ip ipsec installed-sa flush;\r\')
FileWriteLine ( $file, ' \n/ip ipsec remote-peers kill-connections;\r\')
FileWriteLine ( $file, ' \n:local Status [/ping \$RemoteGw count=2 src-address=\$LocalGw];\r\')
FileWriteLine ( $file, ' \n:if (\$Status=0) do={\r\')
FileWriteLine ( $file, ' \n:log info (\"check for misconfiguration\");\r\')
FileWriteLine ( $file, ' \n### Check local IP ###\r\')
FileWriteLine ( $file, ' \n:set Ip [/ip ipsec policy get [find comment=office] sa-src-address];\r\')
FileWriteLine ( $file, ' \n:if (\$Ip!=\$currentIP) do={\r\')
FileWriteLine ( $file, ' \n/ip ipsec policy set [find comment=office] sa-src-address=\$currentIP;\r\')
FileWriteLine ( $file, ' \n:log info (\"misconfiguration: policy, local address\");\r\')
FileWriteLine ( $file, ' \n}\r\')
FileWriteLine ( $file, ' \n### Check remote IP ###\r\')
FileWriteLine ( $file, ' \n:set Ip [/ip ipsec policy get [find comment=office] sa-dst-address];\r\')
FileWriteLine ( $file, ' \n:if (\$Ip!=\$Ip1 and \$Ip!=\$Ip2) do={\r\')
FileWriteLine ( $file, ' \n:if (\$StatusIp1>0) do={\r\')
FileWriteLine ( $file, ' \n/ip ipsec policy set [find comment=office] sa-dst-address=\$Ip1;\r\')
FileWriteLine ( $file, ' \n} else={/ip ipsec policy set [find comment=office] sa-dst-address=\$Ip2;\')
FileWriteLine ( $file, ' }\r\')
FileWriteLine ( $file, ' \n:log info (\"misconfiguration: policy, remote address\");\r\')
FileWriteLine ( $file, ' \n}\r\')
FileWriteLine ( $file, ' \n:set Ip [/ip ipsec peer get [find comment=office] address];\r\')
FileWriteLine ( $file, ' \n:if (\$Ip!=\$Ip1.\"/32\" and \$Ip!=\$Ip2.\"/32\") do={\r\')
FileWriteLine ( $file, ' \n:if (\$StatusIp1>0) do={\r\')
FileWriteLine ( $file, ' \n/ip ipsec peer set [find comment=office] address=\$Ip1;\r\')
FileWriteLine ( $file, ' \n} else={/ip ipsec peer set [find comment=office] address=\"\$Ip1/32\";}\r\')
FileWriteLine ( $file, ' \n:log info (\"misconfiguration: peer\");\r\')
FileWriteLine ( $file, ' \n}\r\')
FileWriteLine ( $file, ' \n} else={:log info (\"Ipsec tunnel status: OK\");}\r\')
FileWriteLine ( $file, ' \n} else={:log info (\"Ipsec tunnel status: Office is DOWN!\");}\r\')
FileWriteLine ( $file, ' \n} else={:log info (\"Ipsec tunnel status: OK\");}"')
;резолвер ntp, в новых прошивках неактуален
FileWriteLine ( $file, ':if ([:len [/system script find name=ntp]] != 0) do={')
FileWriteLine ( $file, 'remove ntp')
FileWriteLine ( $file, '}')
FileWriteLine ( $file, 'add name=ntp policy=\')
FileWriteLine ( $file, 'ftp,reboot,read,write,policy,test,password,sniff,sensitive source=":local \')
FileWriteLine ( $file, 'ntp1 [:resolve pool.ntp.org];\r\')
FileWriteLine ( $file, '\n:local ntp2 [:resolve time.windows.com];\r\')
FileWriteLine ( $file, '\n:local Ip [/system ntp client get primary-ntp];\r\')
FileWriteLine ( $file, '\n:if (\$Ip!=\$ntp1) do={\r\')
FileWriteLine ( $file, '\n/system ntp client set primary-ntp=\$ntp1;\r\')
FileWriteLine ( $file, '\n}\r\')
FileWriteLine ( $file, '\n:set Ip [/system ntp client get secondary-ntp];\r\')
FileWriteLine ( $file, '\n:if (\$Ip!=\$ntp2) do={\r\')
FileWriteLine ( $file, '\n/system ntp client set secondary-ntp=\$ntp2;\r\')
FileWriteLine ( $file, '\n}"')
FileWriteLine ( $file, '/system scheduler')
FileWriteLine ( $file, ':if ([:len [/system scheduler find name=ipsec-test]] = 0) do={')
FileWriteLine ( $file, 'add interval=3m name=ipsec-test on-event="/system script run ipsec" policy=\')
FileWriteLine ( $file, ' reboot,read,write,policy,test,password,sniff,sensitive')
FileWriteLine ( $file, ' }')
FileWriteLine ( $file, ':if ([:len [/system scheduler find name=ntp]] = 0) do={')
FileWriteLine ( $file, 'add interval=1d name=ntp on-event="/system script run ntp" policy=\')
FileWriteLine ( $file, ' reboot,read,write,policy,test,password,sniff,sensitive')
FileWriteLine ( $file, ' }')
FileWriteLine ( $file, '/ip firewall nat')
FileWriteLine ( $file, 'add chain=srcnat comment=ipsec dst-address-list=office out-interface=\')
FileWriteLine ( $file, ' ether1-gateway src-address-list=local')
FileWriteLine ( $file, 'add action=masquerade chain=srcnat comment=bank1 src-address=$term1 dst-port=$BankPorts out-interface=ether1-gateway protocol=tcp disable=yes')
FileWriteLine ( $file, 'add action=masquerade chain=srcnat comment=bank2 src-address=$term2 dst-port=$BankPorts out-interface=ether1-gateway protocol=tcp disable=yes')
FileWriteLine ( $file, 'add action=masquerade chain=srcnat comment="outbound icmp" out-interface=\')
FileWriteLine ( $file, ' ether1-gateway protocol=icmp')
FileWriteLine ( $file, 'add action=masquerade chain=srcnat comment="default configuration" disabled=\')
FileWriteLine ( $file, ' yes out-interface=ether1-gateway')
FileWriteLine ( $file, '/ip firewall filter')
FileWriteLine ( $file, 'add chain=input comment="remote admin" dst-port=80,22,8291 src-address-list=remote in-interface=\')
FileWriteLine ( $file, ' ether1-gateway protocol=tcp')
FileWriteLine ( $file, 'add chain=input comment=icmp protocol=icmp')
FileWriteLine ( $file, 'add chain=input comment="established, related" connection-state=established')
FileWriteLine ( $file, 'add chain=input connection-state=related')
FileWriteLine ( $file, 'add action=drop chain=input comment="drop other" in-interface=ether1-gateway')
FileWriteLine ( $file, 'add chain=forward comment="established, related" connection-state=established')
FileWriteLine ( $file, 'add chain=forward connection-state=related')
FileWriteLine ( $file, 'add action=drop chain=forward comment="drop other" connection-state=invalid')
FileWriteLine ( $file, '/ip dhcp-server lease')
FileWriteLine ( $file, ':if ([:len [/ip dhcp-server lease find address=("$locIP". "1")]]>0) do={')
FileWriteLine ( $file, 'set [/ip dhcp-server lease find address=("$locIP". "1")] address=("$locIP". "1") mac-address=$MAC;}')
FileWriteLine ( $file, ':if ([:len [/ip dhcp-server lease find address=("$locIP". "1")]] = 0) do={add address=("$locIP". "1") mac-address=$MAC;}')
FileWriteLine ( $file, '/user')
FileWriteLine ( $file, 'set [find name=admin] password=MegaPassW0rd')
fileclose($file)
run("notepad "&$file)
EndFunc
;функция для пересчета подсетей.
Func _Subnet($sIp, $sNetmask)
Dim $netmaskbinary
Dim $subnetaddarray[5]
Dim $invmaskarray[5]
Dim $broadcastaddarray[5]
Dim $sSubnetinfo[7]
Dim $subnetadd
Dim $invmask
Dim $broadcastadd
; Reads IP and Netmask to an array
$iparray = StringSplit($sIp, ".")
$netmaskarray = StringSplit($sNetmask, ".")
; Validates IP address
For $i = 1 To 4
If Number($iparray[$i]) < 0 Or Number($iparray[$i]) > 255 Then
SetError(1)
Return (-1)
EndIf
Next
; Converts netmask into a decimal
$netmaskdec = ($netmaskarray[1] * 16777216) + ($netmaskarray[2] * 65536) + ($netmaskarray[3] * 256) + $netmaskarray[4]
; Converts decimal netmask into binary (ex. 11111111111111111100000000000000)
While $netmaskdec <> 0
$binmod = Mod($netmaskdec, 2)
$netmaskbinary = $binmod & $netmaskbinary
$netmaskdec = Int($netmaskdec / 2)
WEnd
; Determines the "slash" value of the netmask
$maskslash = StringInStr($netmaskbinary, "0", 1) - 1
; Validates the "slash" value and netmask value
If StringInStr(StringRight($netmaskbinary, 32 - $maskslash), "1") Then
If $netmaskarray[4] = "255" Then
$maskslash = 32
Else
SetError(1)
Return (-1)
EndIf
EndIf
; Creates arrays conatining subnet address, wilcard, and broadcast addresses
For $i = 1 To $iparray[0]
$subnetaddarray[$i] = BitAND($iparray[$i], $netmaskarray[$i])
$invmaskarray[$i] = BitNOT($netmaskarray[$i] - 256)
$broadcastaddarray[$i] = BitOR($subnetaddarray[$i], $invmaskarray[$i])
Next
; Creates strings conatining subnet address, wilcard, and broadcast addresses
$subnetadd = $subnetaddarray[1] & "." & $subnetaddarray[2] & "." & $subnetaddarray[3] & "." &$subnetaddarray[4]
$invmask = $invmaskarray[1] & "." & $invmaskarray[2] & "." & $invmaskarray[3] & "." & $invmaskarray[4]
$broadcastadd = $broadcastaddarray[1] & "." & $broadcastaddarray[2] & "." & $broadcastaddarray[3] & "." & $broadcastaddarray[4]
If $maskslash = 32 Then
$iprange = $iparray[1] & "." & $iparray[2] & "." & $iparray[3] & "." & $iparray[4]
$hosts = 1
Else
; Determines the IP range for this subnet
$iprange = $subnetaddarray[1] & "." & $subnetaddarray[2] & "." & $subnetaddarray[3] & "." & $subnetaddarray[4] + 1 & _
"-" & $broadcastaddarray[1] & "." & $broadcastaddarray[2] & "." & $broadcastaddarray[3] & "." & $broadcastaddarray[4] - 1
; Calculates number of available hosts on this subnet
$hosts = ($invmaskarray[4] + 1) * ($invmaskarray[3] + 1) * ($invmaskarray[2] + 1) * ($invmaskarray[1] + 1) - 2
EndIf
$sSubnetinfo[1] = $subnetadd
$sSubnetinfo[2] = $broadcastadd
$sSubnetinfo[3] = $invmask
$sSubnetinfo[4] = $iprange
$sSubnetinfo[5] = $hosts
$sSubnetinfo[6] = $maskslash
Return ($sSubnetinfo)
EndFunc
Func _SameSub($sIp, $sSubadd, $sBroadadd)
Dim $iparray[5]
Dim $subaddarray[5]
Dim $broadaddarray[5]
$iparray = StringSplit($sIp, ".")
$subaddarray = StringSplit($sSubadd, ".")
$broadaddarray = StringSplit($sBroadadd, ".")
For $i = 1 To 4
If Number($iparray[$i]) < 0 Or Number($iparray[$i]) > 255 Then
SetError(1)
Return (-1)
EndIf
If Number($subaddarray[$i]) < 0 Or Number($subaddarray[$i]) > 255 Then
SetError(1)
Return (-2)
EndIf
If Number($broadaddarray[$i]) < 0 Or Number($broadaddarray[$i]) > 255 Then
SetError(1)
Return (-3)
EndIf
Next
$ipint = ($iparray[1] * 16777216) + ($iparray[2] * 65536) + ($iparray[3] * 256) + $iparray[4]
$subaddint = ($subaddarray[1] * 16777216) + ($subaddarray[2] * 65536) + ($subaddarray[3] * 256) + $subaddarray[4]
$broadaddint = ($broadaddarray[1] * 16777216) + ($broadaddarray[2] * 65536) + ($broadaddarray[3] * 256) + $broadaddarray[4]
If $ipint > $subaddint And $ipint < $broadaddint Then
Return (1)
Else
Return (0)
EndIf
EndFunc
Осталось выполнить на MikroTik получившийся скрипт после сброса конфигурации, любым удобным способом: через /system script, использовать сохраненный файл и сбросить конфигурацию так, как показано на картинке:
Теперь для настройки нового MikroTik достаточно запустить программу, указать индивидуальные настройки в GUI, получить скрипт настройки и передать его маршрутизатору. Осталось довести до ума настройку маршрутизатора в предполагаемом центральном офисе.
Настройка D-Link DFL
Настройку DFL удобнее производить через консоль. Такой тип настройки тоже поддается автоматизации, например с использованием инструмента Plink от создателя Putty.
Концепцию передачи команд в открытое консольное нельзя назвать идеальной, но в случае с D-Link DFL можно использовать его собственные «скрипты». В качестве примера разберем функцию, которую использую в паре с ранее описанной. Если MikroTik нужно настраивать с нуля, то на DFL достаточно создать туннель IPsec и включить его в группу интерфейсов для применения правил встроенного брандмауэра.
Окно настроек заметно проще, ведь DFL в нашем случае не нужно настраивать с нуля
Для доступа к маршрутизатору по SSH понадобится Plink, а также Pscp для передачи файлов — достаточно просто прописать в скрипте пути к исполняемым файлам.
local $_plink_loc = "\\server\share\plink.exe"
local $scploc="\\server\share\pscp.exe"
При работе Plink и Pscp есть особенность: если используется парольная авторизация, то при первом подключении или замене оборудования появится запрос о внесении отпечатка в реестр. Обойти запрос можно передачей «Y» в консольное окно.
$_plinkhandle = Run(@comspec & " /c " & $_plink_loc & " -ssh admin@" & $_plinkserver &" -pw MegaPass","",@SW_HIDE,7)
sleep (500)
StdinWrite($_plinkhandle, "y"& @CR)
DFL работает с объектами на уровне групп. Так как нельзя добавить объект в группу без перечисления всех ее членов, я генерирую на DFL создающий группу скрипт, забираю его в виде файла и изменяю. Создать скрипт можно следующей командой:
script -create InterfaceGroup VPNs -name=vpns.sgs
Результат можно забрать по SCP. Файл я переименовываю в script.sgs и добавляю в него другие команды для создания новых объектов, после чего импортирую результат на D-Link по SCP и применяю конфигурацию.
script -execute -name=script.sgs
После применения конфигурации уже ненужный скрипт можно удалить командой
script -remove -name=script.sgs
func _dfl($lanip8="",$PSK8="",$ipext8="")
local $file = @TempDir &"\script.sgs"
local $file1 = @TempDir &"\script1.sgs"
if FileExists($file) then
FileDelete($file)
EndIf
if FileExists($file1) then
FileDelete($file1)
EndIf
;пути к программам. при желании можно закомпилировать внутрь
local $_plink_loc = "\\server\share\plink.exe"
local $scploc="\\server\share\pscp.exe"
Local $_plinkserver = "адрес DFL"
local $magazname
local $magaznumber
local $lanip
local $ipext
local $PSK
$hgui = GUICreate("настройка DFL" , 300, 270)
GUICtrlCreateLabel("введите локальный адрес роутера магаза",2,2)
$lanip1 = _GUICtrlIpAddress_Create($hgui, 10, 20)
GUICtrlCreateLabel("введите внешний адрес",2,45)
$ipext1 = _GUICtrlIpAddress_Create($hgui, 10, 65)
GUICtrlCreateLabel("введите ключ на ипсек",2,90)
$secret1 = GUICtrlCreateInput("",10, 110)
GUICtrlCreateLabel("введите имя магазина транслитом",2,130)
$name1=GUICtrlCreateInput("",10, 150)
GUICtrlCreateLabel("введите номер магазина (77 или NN88)",2,170)
$number1=GUICtrlCreateInput("",10, 190)
$OK_Btn = GUICtrlCreateButton("Поехали", 75,230 , 70, 25)
if $PSK8 = "" Then
GUICtrlSetData($secret1,_Crypto_GetRandomString(12,7))
Else
GUICtrlSetData($secret1,$PSK8)
EndIf
_GUICtrlIpAddress_Set($ipext1, $ipext8)
_GUICtrlIpAddress_Set($lanip1, $lanip8)
GUISetState(@SW_SHOW)
While 1
$msg = GUIGetMsg()
Select
Case $msg = $GUI_EVENT_CLOSE
Exit
Case $msg = $OK_Btn
$lanip=_GUICtrlIpAddress_Get($lanip1)
$ipext=_GUICtrlIpAddress_Get($ipext1)
$PSK=GUICtrlRead($secret1)
$magazname=GUICtrlRead($name1)
$magaznumber=GUICtrlRead($number1)
$lanip2=_GUICtrlIpAddress_GetArray($lanip1)
;проверки на дурака
if $lanip = 0 or $ipext=0 or StringLen($PSK)=0 then
MsgBox(4160, "Information", "поля не заполнены")
else
if StringRegExp($magazname,"[0-9a-zA-Z_]") =0 then
MsgBox(4160, "Information", "имя транслитом!")
Else
if $lanip2[3]<>1 Then
MsgBox(4160, "Information", "локальный адрес роутера на .1 должен кончаться!")
Else
GUIDelete()
ExitLoop
EndIf
EndIf
EndIf
EndSelect
WEnd
$_plinkhandle = Run(@comspec & " /c " & $_plink_loc & " -ssh admin@" & $_plinkserver &" -pw MegaPass","",@SW_HIDE,7)
sleep (500)
;DFL иногда меняются. согласимся с обновлением ключей в реестре.
;Лучше конечно менять ключи в реестре или использовать сертификаты, но...
StdinWrite($_plinkhandle, "y"& @CR)
sleep (500)
StdinWrite($_plinkhandle, "script -remove -name=vpns.sgs "& @CR)
sleep (500)
StdinWrite($_plinkhandle, "script -remove -name=script.sgs "& @CR)
sleep (500)
;нужно будет включить интерфейс в группу - заберем её.
StdinWrite($_plinkhandle, "script -create InterfaceGroup VPNs -name=vpns.sgs "& @CR)
sleep (500)
StdinWrite($_plinkhandle, "Exit " & @CR)
sleep (250)
ProcessClose("plink.exe")
RunWait(@comspec & " /c " & $scploc & " -pw MegaPass admin@"&$_plinkserver&":script/vpns.sgs " & $file,"",@SW_HIDE,7)
$line = FileReadLine($file,1)
local $lannet=StringMid($lanip,1,StringLen($lanip)-1)&"0/24"
FileOpen ( $file, 2)
FileWriteLine ( $file, "cc AddressFolder VpnAdresses")
FileWriteLine ( $file, "add IP4Address Pool"&$magaznumber&"_"&$magazname&" Address="&$lannet&" -silent")
FileWriteLine ( $file, "add IP4Address Ip"&$magaznumber&"_"&$magazname&" Address="&$ipext&" -silent")
FileWriteLine ( $file, "add IP4Address Router"&$magaznumber&"_"&$magazname&" Address="&$lanip&" -silent")
FileWriteLine ( $file, "cc ..")
FileWriteLine ( $file, "add PSK Key_"&$magaznumber&"_"&$magazname&" Type=ASCII PSKAscii="&$PSK&" Comments="&$PSK&" -silent")
;для новых DFL синтаксис поменяется
FileWriteLine ( $file, "add IPsecTunnel Magaz_"&$magazname&" LocalNetwork=InterfaceAddresses/lannet RemoteNetwork=VpnAdresses/Pool"&$magaznumber&"_"&$magazname&" RemoteEndpoint=VpnAdresses/Ip"&$magaznumber&"_"&$magazname&" IKEAlgorithms=High IPsecAlgorithms=High AuthMethod=PSK PSK=Key_"&$magaznumber&"_"&$magazname&" PFS=PFS KeepAlive=Manual KeepAliveSourceIP=VpnAdresses/Router_Main KeepAliveDestinationIP=VpnAdresses/Router"&$magaznumber&"_"&$magazname&" index=57 -silent")
$line=StringReplace ( $line, "add", "set" ,1,1 )
$line=StringReplace ( $line, " -silent", "" ,1,1 )
$line=StringReplace ( $line, " -force", "" ,1,1 )
$line=_StringInsert($line, ", "& "Magaz_"&$magazname, -1)
FileWriteLine ( $file, $line)
FileCLose($file)
FileCopy($file,$file1)
RunWait(@comspec & " /c " & $scploc & " -pw MegaPass " & $file1 & " admin@"&$_plinkserver&":script/script.sgs","",@SW_HIDE,7)
$_plinkhandle = Run(@comspec & " /c " & $_plink_loc & " -ssh admin@" & $_plinkserver &" -pw QfJatp123","",@SW_HIDE,7)
sleep (500)
StdinWrite($_plinkhandle, "script -execute -name=script.sgs "& @CR)
sleep (250)
StdinWrite($_plinkhandle, "activate "& @CR)
sleep (10000)
StdinWrite($_plinkhandle, "commit "& @CR)
sleep (250)
StdinWrite($_plinkhandle, "exit "& @CR)
sleep (250)
ProcessClose("plink.exe")
EndFunc
Кстати, в процессе обнаружилось решение и для резервного копирования конфигурации.
Пасхалка: резервное копирование D-Link DFL
В процессе отладки в корне структуры DFL обнаружились два файла: full.bak и config.bak. Несложно догадаться, что это полный бэкап системы и копия конфигурации. Для создания полноценной резервной копии устройства достаточно подключиться по SCP и скопировать оба файла. В этом же скрипте резервного копирования можно написать нечто вроде политики хранения — удалять файлы старше двух недель.
#include
#Include
Local $_plinkserver = "адрес dfl"
local $scploc="\\server\share\pscp.exe"
local $path=@ScriptDir&"\"
$date1=@YEAR&@MON&@MDAY
local $file = $path &"config-"&$date1&".bak"
$handle=Run(@comspec & " /c " & $scploc & " -pw MegaPass admin@"&$_plinkserver&":config.bak " & $file,"",@SW_HIDE,7)
sleep (500)
StdinWrite($_plinkhandle, "y"& @CR)
sleep (500)
$file = $path &"full-"&$date1&".bak"
RunWait(@comspec & " /c " & $scploc & " -pw MegaPass admin@"&$_plinkserver&":full.bak " & $file,"",@SW_HIDE,7)
;чистим старые бэкапы
;если бэкапов нет, то лучше ничего не чистить
if FileExists($path &"full-"&$date1&".bak") and FileExists($file = $path &"config-"&$date1&".bak") Then
$files = _FileListToArray($path, "*.bak", 1,True)
$date=@YEAR&"/"&@MON&"/"&@MDAY
$newdate=_DateAdd("D",-14,$date)
$formatdate=StringSplit($newdate,"/")
$newdate=$formatdate[1]&$formatdate[2]&$formatdate[3]&@HOUR&@MIN&@SEC
If IsArray($files) Then
For $i = 1 To UBound($files) - 1
$aTime = FileGetTime( $files[$i], 0, 1)
If $aTime < $newdate Then
FileDelete($files[$i])
EndIf
Next
EndIf
EndIf
Скрипт успешно бэкапит любые модели DFL.
Итого
Конечно, эти два способа настройки не претендуют на лучшие практики, а кто-то даже назовет их костылями. Для небольших и разовых изменений конфигурации проще использовать SNMP или специальный API от производителя. Но даже изобретение своего велосипеда сэкономило массу времени на просьбах помочь с настройкой от ранее не работавших с Mikrotik коллег. Опять же, меньше шанс ошибиться.
Если вам доводилось использовать подобные способы упрощенной настройки для других устройств — поделитесь рецептами с коллегами!