Администрирование FreeBSD, Linux, ... FreeBSD ipnat: автоматическая поддержка правил NAT в актуальном состоянии Wed, April 26 2017  

Поделиться

нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


FreeBSD ipnat: автоматическая поддержка правил NAT в актуальном состоянии Печать
Добавил(а) microsin   

Такая необходимость возникает, если провайдер не выдает постоянный IP, либо Вам не хочется переплачивать за эту услугу (как например, было у меня с Корбиной).

В случае NAT, настроенного через ipnat, в файле /etc/ipnat.rules могут быть правила, привязанные к текущему внешнему IP, например (адрес 89.178.134.209 автоматически назначен провайдером на виртуальном внешнем интерфейсе ng0 при соединении по pptp VPN с Корбиной):

bimap ng0 192.168.7.1 -> 89.178.134.209

Это очень неудобно, поскольку при переподключении IP в этой строке должен меняться. Мне удалось избавиться от этой проблемы с помощью скрипта, который запускается каждую минуту, проверяет IP адрес на интерфейсе ng0, и делает все необходимые действия (если IP нет, то пытается переподключиться, если IP поменялся, то корректирует файл /etc/ipnat.rules и перезапускает правила NAT, если все нормально, то никаких действий не производится). Кроме того, если Вы поменяли правила в /etc/ipnat.rules, то новые правила применятся автоматически. Скрипт запускается заданием, добавленным вручную в файл /etc/crontab (системный crontab. Я пробовал его назначить иначе, через crontab -e, но почему-то скрипт работал неправильно - не находилась команда ifconfig, применяемая в скрипте):

*       *       *       *       *       root    /путь_до_скрипта/corbina.sh

Вот текст скрипта corbina.sh:

#!/bin/sh
logname=/var/log/corbina.log byteIP="([0-9]|[0-9][0-9]|[01][0-9][0-9]|2[0-4][0-9]|25[0-5])" regexIP="$byteIP(\.$byteIP){3}(\b|/)" natcfg='/etc/ipnat.rules' outside=ng0 timeout=60 # timeout in seconds, while $outside interface bring up tmpIPNATL='/scripts/ipnatl'
log () # процедура просто записывает строку в лог .var/log/corbina.log { prefix=`eval date` echo $prefix': '$1 >> $logname }
reloadNAT () # процедура вписывает в файл правил NAT новый IP # и применяет новые правила { log "correct $natcfg with new $outside IP $WANIP" # для корректировки правил применяем скрипт на Perl # replace.pl (см. ниже) regex="bimap $outside 192.168.7.1/32 -> $byteIP(\.$byteIP){3}/" replaceval="bimap $outside 192.168.7.1/32 -> $WANIP/" perl /scripts/replace.pl $natcfg "$regex" "$replaceval" ipnatmsg=`eval ipnat -CF -f $natcfg` log "$ipnatmsg" }
getWANIP () # если WAN интерфейс имеет IP, то он записывается в переменную $WANIP # иначе $WANIP будет пустая { WANIP=`eval ifconfig $outside | grep -E -o --regexp="$byteIP(\.$byteIP){3} -->"
| grep -E -o --regexp="$byteIP(\.$byteIP){3}"` }
# тут начинается работа скрипта - получим IP на внешнем интерфейсе getWANIP
# ветка 1: работает, если нет IP адреса на интерфейсе WAN # В этом случае делается попытка переподключения (с помощью # посылки сигналов демону mpd). В случае успеха # корректируются правила NAT, а затем они вводятся в работу if test -z "$WANIP" then log "no IP address at $outside adapter, try reconnect..." log "killall -USR2 mpd" killall -USR2 mpd sleep 1 log "killall -USR1 mpd" killall -USR1 mpd # цикл: в течение 60 секунд ожидаем появления IP на интерфейсе $outside. # Это должно означать, что подключение произошло успешно. timecnt=0 while test $timecnt -ne $timeout do getWANIP if test -n "$WANIP" then # IP назначен успешно (переменная $WANIP не пустая), прерываем цикл break fi # задержка на 1 сек, ждем появления IP sleep 1 # отсчет таймаута timecnt=`expr $timecnt + 1` done if test -n "$WANIP" then # да, IP действительно назначен, перезагружаем ipnat новыми правилами log "in $timecnt sec WAN $outside RST ok." reloadNAT exit 0 else # подключиться на этот раз не удалось log "timeout $timeout sec expired, WAN $outside RST error... Quit." exit 1 fi fi
# ветка 2: IP назначен, проверяем его на соответствие правилам NAT # в файле /etc/ipnat.rules ipnatrulesIP=`eval more $natcfg | grep -E --regexp="^bimap" | grep -E -o --regexp=
"-> $byteIP(\.$byteIP){3}(\b|/)" | grep -E -o --regexp="$byteIP(\.$byteIP){3}"` if test -n "$ipnatrulesIP" then # в файле правил NAT есть правило с IP, который возможно надо поменять if test "$ipnatrulesIP" != "$WANIP" then # кроме того, его действительно надо поменять log "$outside IP $WANIP != IP NAT, reload NAT rules" reloadNAT exit 0 fi fi
# ветка 3: проверяем на соответствие текущих правил # (которые работают) и правил из /etc/ipnat.rules # Запишем во временный файл текущие правила NAT: ipnat -l > $tmpIPNATL # Отфильтруем все ненужное (оставим в файле только правила NAT. # для этого применяем скрипт на Perl cut_block.pl - см. ниже): perl /scripts/cut_block.pl $tmpIPNATL "List of active MAP/Redirect filters:" "List of active sessions:" # впишем в переменную $ipnatrules_curr текущие правила NAT ipnatrules_curr=`more $tmpIPNATL` # впишем в переменную $ipnatrules_file системные правила NAT # (они в файле /etc/ipnat.rules) ipnatrules_file=`more $natcfg | grep -E --regexp="^[^#]"` # удаляем временный файл rm $tmpIPNATL if test "$ipnatrules_curr" != "$ipnatrules_file" then # правила NAT поменялись, применяем новые правила log "ipnat -l != $natcfg, apply $natcfg" ipnatmsg=`eval ipnat -CF -f $natcfg` log "$ipnatmsg" fi exit 0

Немного замечаний по тексту. Переменная regexIP представляет регулярное выражение для поиска IP в тексте. Переменная outside задает имя внешнего интерфейса, на котором постоянно меняется IP (IP вычисляется командой ifconfig $outside). Процедура log() помогает вести отдельный лог для работы скрипта. Процедура getWANIP() получает IP на интерфейсе $outside и записывает его в переменную $WANIP. Процедура reloadNAT() меняет нужные правила в /etc/ipnat.rules и корректирует их новым IP, а потом перезапускает ipnat с новыми правилами. Остальные комментарии достаточно подробно вставлены по месту у текст.

Для корректировки файла /etc/ipnat.rules процедура reloadNAT() запускает скрипт replace.pl, написанный на Perl. Вот его текст:

#----------------------------------------------------------------------------------------------
# Script replaces in file < pattval > with < replval >. Value < pattval >
# is interpreted as regular expression.
#
# Usage:
# perl replace.pl file pattval repval
#----------------------------------------------------------------------------------------------

sub
Usage { die "Usage:\nperl replace.pl file regex_pattern replace_value\n"; }
# просто проверка наличия входных параментров, без затей: if (@ARGV[0] eq "") { Usage(); } if (@ARGV[1] eq "") { Usage(); } if (@ARGV[2] eq "") { Usage(); }
$file=@ARGV[0]; # имя файла, в котором ищем $tempfile=$file.".tmp"; # имя временного файла, который потом заменит исходный $regex=@ARGV[1]; # регулярное выражение для поиска $replval=@ARGV[2]; # текст, который заменит значение, попавшее в регулярное выражение
#Открываем рабочие файлы в двоичном режиме. # Открыть входной файл только на чтение, если не получится, то выход: open SRCFILE, '<', $file or die "Cannot open file ".$file.": $!\n"; # Открыть выходной файл только на запись, если не получится, то выход: open DSTFILE, '>', $tempfile or die "Cannot open file ".$tempfile.": $!\n";
# Цикл: читаем построчно входной файл, корректируем, если надо, строку, # и результат пишем в выходной файл. while (!eof(SRCFILE)) { $line = readline SRCFILE; # /g задает менять все "срабатывания" $regex в строке. # Если /g убрать, то произойдет только первая замена. $line =~ s/$regex/$replval/g; print DSTFILE $line; }
# закрываем файлы close SRCFILE; close DSTFILE;
# старый файл удаляем и заменяем его на новый, скорректированный unlink $file; rename $tempfile, $file;

Еще один скрипт cut_block.pl, тоже на Perl, оставляет во временном файле только правила NAT, остальное все удаляется. Правила NAT выделяются по текстовым маркерам начала и конца блока текста. Скрипт, конечно, простейший, и вся система будет работать только в том случае, если у Вас в правилах NAT /etc/ipnat.rules записано все именно так, как должна вывести команда ipnat -l. Вот содержимое этого скрипта:

#----------------------------------------------------------------------------------------------
# Script get all strings block from file beetween beg_mark and end_mark 
# and placed these strings to file (another strings are removed). 
# Marks and empty strings excluded from block.
#
# Usage:
# perl cut_block.pl file beg_mark end_mark
#----------------------------------------------------------------------------------------------

sub
Usage { die "Usage:\nperl cut_block.pl FILE BEGmark ENDmark\n"; }
if
(@ARGV[0] eq "") { Usage(); } if (@ARGV[1] eq "") { Usage(); } if (@ARGV[2] eq "") { Usage(); }
$file
=@ARGV[0]; $tempfile=$file.".tmp"; $markBEG=@ARGV[1]; $markEND=@ARGV[2]; $BeginDetected=0;
open
SRCFILE, '<', $file or die "Cannot open file ".$file.": $!\n"; #открыть только на чтение open DSTFILE, '>', $tempfile or die "Cannot open file ".$tempfile.": $!\n"; #открыть только на запись
sub trim { my($string)=@_; for ($string) { s/^\s+//; #обрезать в начале s/\s+$//; #обрезать в конце } return $string; }
while (!eof(SRCFILE)) { $line = readline SRCFILE; $line = &trim($line); if ($BeginDetected) { if (length($line)) { $idx = index ($line, $markEND); if (-1!=$idx) { last; } print DSTFILE $line."\n"; } } else { if (length($line)) { $idx = index ($line, $markBEG); if (-1!=$idx) { $BeginDetected=1; } } } }
close SRCFILE; close DSTFILE;
unlink $file; rename $tempfile, $file;

 

 

Комментарии  

 
0 #1 Сабир 17.05.2016 12:58
Немного программировани я - это всегда хорошая зарядка для мозга и, мне кажется, что это должно приветствоватьс я! :) Но, на сколько я знаю, у вас соединение поднимается mpd-хой, а она может запускать скрипты, как при дисконекте, так и при соединении. И тогда скрипты получаются достаточно компактные и не нужно делать проверок через определенные промежутки времени.
Цитировать
 

Добавить комментарий


Защитный код
Обновить

Top of Page