Шукати в цьому блозі

четвер, 27 жовтня 2016 р.

RouterOS та передача маршрутної інформації у DHCP

Певно, що тим, хто читає цей нотаток, відомо, що у DHCP можна передавати окрім всього іншого й маршрутну інформацію. Тут розглянемо як це робиться у RouterOS та намалюємо маленький скрипт який допоможе в цій роботі у подальшому.

RFC3442 має назву The Classless Static Route Option for Dynamic Host Configuration Protocol (DHCP) version 4 і описує формат опції 121, в якій передається маршрутна інформація. Для тієї самої задачі слугує й опція 249, але про неї окремо.

Формат опції у RFC виглядає так:
  Code Len Destination 1    Router 1
   +-----+---+----+-----+----+----+----+----+----+
   | 121 | n | d1 | ... | dN | r1 | r2 | r3 | r4 |
   +-----+---+----+-----+----+----+----+----+----+

    Destination 2       Router 2
   +----+-----+----+----+----+----+----+
   | d1 | ... | dN | r1 | r2 | r3 | r4 |
   +----+-----+----+----+----+----+----+
Тобто маємо безпосередньо код опції, наступний байт це кількість мережевих бітів (маска мережі), потім йдуть октети, що описують номер мережі та 4 байти/октети, що описують маршрутизатор через який має бути побудовано маршрут. Також у RFC читаємо, що для декількох маршрутів вони можуть бути склеєні.

Окрему увагу заслуговує кількість Destination октетів. У RFC можна побачити наступне:
        Width of subnet mask     Number of significant octets
                     0                     0
                  1- 8                     1
                  9-16                     2
                 17-24                     3
                 25-32                     4
Це все справедливо як для опції 121 так і для опції 249. Єдине зауваження щодо опції 249, яке знайшов на хабрі, те, що до маршрутної інформації варто додавати 00[адреса шлюзу]. Пишуть, що це справедливо для комп'ютерів під керуванням ОС Windows.

Все це була не дуже цікава, але не менш важлива теорія, приступимо до практики.

Озброївшись знаннями з RFC3442 можна доволі швидко написати додаток, що буде обчислювати маршрути й формувати відповідний вивід. Я полюбляю Perl тому скрипт було написано саме на ньому, https://github.com/oldengremlin/route-4-dhcp/blob/master/rfc3442.route-4-dhcp.pl:

Поставимо на меті описати маршрут на мережі RFC1918 через /dev/null. Взагалі то було б непогано змаршрутизувати їх через 127.0.0.1, але ж через описані опції маршрути можуть бути додано лише через ті адреси до яких клієнт може знати як дістатися. Тому, припустимо, що у нашій мережі 192.168.88.0/24 не існує хоста 192.168.88.2, а трафік пустимо через нього.

Назвемо скрипт rfc3442.route-4-dhcp та запустимо його з параметрами:
$ rfc3442.route-4-dhcp 10.0.0.0/8 192.168.88.2 172.16.0.0/12 192.168.88.2 192.168.0.0/16 192.168.88.2
option_121_route_10.0.0.0/8_via_192.168.88.2 : 0x080ac0a85802
option_249_route_10.0.0.0/8_via_192.168.88.2 : 0x080ac0a85802
option_121_route_172.16.0.0/12_via_192.168.88.2 : 0x0cac10c0a85802
option_249_route_172.16.0.0/12_via_192.168.88.2 : 0x0cac10c0a85802
option_121_route_192.168.0.0/16_via_192.168.88.2 : 0x10c0a8c0a85802
option_249_route_192.168.0.0/16_via_192.168.88.2 : 0x10c0a8c0a85802
aggregate_opt_121 : 0x080ac0a858020cac10c0a8580210c0a8c0a85802
aggregate_opt_249 : 0x080ac0a858020cac10c0a8580210c0a8c0a85802

Передаємо скрипту пари у вигляді мережа-шлюз і отримуємо попарний вивід для окремих мереж і останніми строками сукупну маршрутну інформацію для всіх мереж.

Тепер перенесемо всю цю інформацію у RouterOS:
/ip dhcp-server option
add code=121 name=aggregate_opt_121_over_fakeip_192.168.88.2 value=0x080ac0a858020cac10c0a8580210c0a8c0a85802
add code=249 name=aggregate_opt_249_over_fakeip_192.168.88.2 value=0x080ac0a858020cac10c0a8580210c0a8c0a8580200c0a85801

Щодо опції 249 читаємо зауваження нагорі і дивимося як було додано маршрут через основну адресу маршрутизатора - 192.168.88.1.

Опції задано, тепер їх можна додати у список опцій у описі мережі в налаштуваннях dhcp-серверу. Якщо у dhcp-сервері вже було налаштовано передачу інформації щодо wpad то разом з маршрутною інформацією команда налаштування мережі може виглядати якось так:
/ip dhcp-server network
add address=192.168.88.0/24 comment=dhcp-server dhcp-option=\
    auto-proxy-config,aggregate_opt_121_over_fakeip_192.168.88.2,aggregate_opt_249_over_fakeip_192.168.88.2,disable_nbt dns-server=\
    192.168.88.1 gateway=192.168.88.1 netmask=24 ntp-server=192.168.88.1

Домашнім завданням може бути написання скрипту якій розшифровує 0x080ac0a858020cac10c0a8580210c0a8c0a85802 у зворотньому напрямку ;)

середу, 26 жовтня 2016 р.

RouterOS та налаштування Proxy через DHCP

Рік тому публікував тут як налаштувати прозорий web-proxy на Мікротиці. Але сталося, що частині комп'ютерів, які це вміють, треба було якось віддавати налаштування щодо proxy автоматично.

Для isc dhcp серверу було знайдено опис щодо option 252, через яку можна налаштувати Web Proxy Autodiscovery Protocol (надалі wpad). Зазвичай налаштовується якось так:
option wpad code 252 = text;
option wpad "http://192.168.xxx.xxx/wpad.dat";
де http://192.168.xxx.xxx/wpad.dat вказує на файл налаштувань проксі.

Того самого ефекту можна досягнути у RouterOS наступним чином. Додаємо налаштування dhcp-опції та додаємо її до налаштувань мережі у dhcp-server-і:
/ip dhcp-server option add code=252 name=auto-proxy-config value="'http://192.168.xxx.xxx/wpad.dat'"
/ip dhcp-server network … dhcp-option=auto-proxy-config
Після додання опції клієнти які підтримують wpad автоматично завантажать wpad.dat та застосують налаштування з нього.

Приклад вмісту доволі простого wpan.dat:
// Функція використання проксі
function FindProxyForURL(url, host)
{
    // якщо текстове ім'я хоста без точок, то з'єднуємо з ним безпосередньо (корисно для локальних адрес)
    if (isPlainHostName(host)) { return "DIRECT"; }

    // якщо хост в мережі 192.168.88.0/24 або в мережі 127.0.0.0/8 то до нього підключаємося напряму
    if (isInNet (host, "192.168.88.0", "255.255.255.0") || isInNet (host, "127.0.0.0", "255.0.0.0")) { return "DIRECT"; }

    // якщо жодна умова не виконується - повертаємо ім'я проксі
    return "PROXY 192.168.88.1:8080; DIRECT";
}

Доступні наступні функції:

isResolvable(host) — запит імені хоста (перевірка існування) на DNS-сервері.
Приклад:
if (isResolvable(host))

dnsResolve(host) — запит DNS-сервера для перекладу імені в IP.
Приклад:
if (dnsResolve(host) == "xxx.xxx.xxx.xxx")

myIpAddress() — повертає IP-адреса (що складається з цілих чисел і точок) вузла, на якому запущений браузер.
Приклад:
if (myIpAddress() == "xxx.xxx.xxx.xxx")

isPlainHostName(host) — перевіряє, чи містяться точки в імені вузла. якщо точка знайдена, то повертається значення false; якщо немає, повертається значення true.
Приклад:
if (isPlainHostName(host))

dnsDomainLevels(host) — повертає ціле число, яке дорівнює кількості точок в імені вузла.
Приклад:
if (dnsDomainLevels(host) > 0)

dnsDomainIs(host, ".company.com") — повертає значення true, якщо домен з імені вузла збігається з заданим доменом.
Приклад:
if (isPlainHostName(host) || dnsDomainIs(host, ".company.com"))

localHostOrDomainIs(host, "www.company.com") — повертає значення true, якщо домен з імені вузла збігається з заданим доменом. Виконується тільки для URL-адрес, що відносяться до локального домену.
Приклад:
if (!localHostOrDomainIs(host, "www.company.com"))

shExpMatch(str, shexp) — повертає значення true, якщо str відповідає шаблоном оболонки shexp.
Приклад:
if (shExpMatch(host, "*.com"))

isInNet(host, pattern, mask) — повертає значення true, якщо IP-адреса вузла відповідає зазначеному шаблоном (наприклад, 127.0.0.0). mask вказує, яку частина IP-адреси слід зіставляти (255 = зіставляти, 0 = ігнорувати).
Приклад:
if (isInNet(host, "999.99.9.9", "255.0.255.0"))

url.substring(0, n) — витягує вказану кількість знаків з початку рядка
Приклад:
url.substring(0, 4)

weekdayRange( day1 [, day2] [,"GMT"] ) — повертає значення true, якщо поточний
час системи потрапляє в діапазон, заданий з використанням параметрів і необов'язковий параметр GMT вказує, якщо задано не місцевий час, а час за Гринвічем.
Приклад:
if(weekdayRange("WED", "SAT", "GMT"))

dateRange(day1 [,month1] [,year1] [,day2] [,month2] [,year2] [,gmt] )
day — число дня місяцю між 1 и 31;
month — місяць: JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC;
year — повний (чотиризначний) рік;
gmt — зона GMT (по Гринвічу).
Приклад:
dateRange (1) // true якщо сьогодні перше число, використовується локальний час.
dateRange (1, "GMT") // true сьогодні перше число, використовується час за Гринвічем.
dateRange (1, 15) // true якщо сьогодні число в діапазоні від 1 до 15-е.
dateRange (24, "DEC") // true кожне 24-е грудня.
dateRange (24, "DEC", 1995) // true якщо сьогодні 24е-е грудня 1995 року.
dateRange ( "JAN", "MAR") // true з січня по березень
dateRange (1, "JUN", 15, "AUG") // true з 1 червня по 15-е серпня (включно).
dateRange (1, "JUN", 15, 1995, "AUG", 1995) // true з 1-го червня 1995 року по 15-е червня 1995 року.
dateRange ( "OCT", 1995, "MAR", 1996) // true з жовтня 1995 по березень 1996 (включно).
dateRange (1995) // true протягом 1995 року.
dateRange (1995, 1997) // true з 1995 до 1997

timeRange(hour1 [,min1] [,sec1] [,hour2] [,min2] [,sec2] [,gmt] )
hour — час від 0 до 23;
min — хвилини від 0 до 59;
sec — секунди від 0 до 59;
gmt — зона GMT (по Гринвічу).
Приклад:
timerange (12) // true до 13:00.
timerange (12, 13) // true з 12:00 до 13:00.
timerange (12, "GMT") // true з початку для до 13:00 за Гринвічем.
timerange (9, 17) // true from 9am to 5pm.
timerange (8, 30, 17, 00) // true з 8:30 to 17:00.
timerange (0, 0, 0, 0, 0, 30) // true з півночі і до 00:00:30 секунд.

Також ще підтримуються змінні.
Приклад:
ip_host  = dnsResolve(host);
ip_localnet = "192.168.0.0";
ip_localhost = "127.0.0.1";
proxy_general = "PROXY gwhost.my:3128";
proxy_local = "PROXY localhost:3128";