Раздача интернета с 3G модема в локальную сеть в Linux

в 22:54, , рубрики: 3g модем, iptables, linux, nat, метки: , , ,

Эта статья — продолжение статьи Беспроводная точка доступа, используя Linux. Тут я опишу, что же необходимо сделать для того, чтобы раздавать интернет с 3G-модема по уже созданной по инструкции из предыдущего топика вайфай-сети.

1) Прежде всего, научить Linux работать с модемом
2) Создать NAT для раздачи интернета
3) Запихнуть всё это дело в автозагрузку
Итак, bash, wvdial и iptables под мышку — и поехали!

Подключение USB 3G-модема

Бывает и так, что в некоторых странах есть свои провайдеры 3G-интернета, которые не предоставляют настроек для подключения, используя Linux, что, в общем-то, и понятно — 'популярность' как провайдера, так и Linux даёт о себе знать. Не все конфиги есть ещё на сайтах, тем более — для отдельных программ. Итак, в Латвии, где я и проживаю. есть два провайдера — LMT и Bite. Оба они предоставляют беспроводной интернет через модемы Huawei, залоченные, естественно, на них, ну да не в этом дело. Ну так вот — необходимо обеспечить интернет всюду, где есть 3G, используя модем и сервер. Что же делать?

Прежде всего, воткнуть модем в ноут. USB-модемы определяются в Linux как устройства под адресом /dev/ttyUSB*, где * — порядковый номер устройства, обычно адрес выглядит как /dev/ttyUSB0.

root@localhost:/# ls /dev/ttyUSB* 
ls: cannot access /dev/ttyUSB*: No such file or directory

Ой. Что-то он не определяется. А проблема вот такая (обмусоленная уже тысячу раз): модем — это устройство типа “два в одном”. Почему? Он совмещает в одной флешке как собственно модем, так и встроенный накопитель с драйверами модема под Windows (я уже молчу про кардридер). В Linux по умолчанию включается режим диска, а не модема Для того, чтобы включить ещё и режим модема, нужно установить пакет usb-modeswitch. После этого нужно перезагрузиться и опять подключить модем, подождать секунд 10 и опять выполнить команду на вывод списка устройств модема:

root@localhost:/# ls /dev/ttyUSB* 
/dev/ttyUSB0 /dev/ttyUSB1 /dev/ttyUSB2 

Когда вывод походит на этот, всё отлично и можно двигаться дальше. У нас есть три устройства. Нам необходимо лишь одно — под номером 0, остальные 2 мы не используем — они не для наших целей. Насколько мне известно, одно из них, скорее всего, используется для отсылки СМС, а второе — для просмотра уровня сигнала сети и прочего.

Теперь — дело за программой, которая подключит нас. Я буду использовать программу wvdial, дополнительно к ней нужно установить пакет ppp, если он ещё не установлен.

apt-get install ppp wvdial

Многие советуют использовать программу wvdialconf для настройки подключения, но в данном случае она нам не поможет. После установки нам нужно отредактировать файл /etc/wvdial.conf. Стираем из него всё содержание, затем разбираемся в формате файла. Я предоставлю рабочие конфиги для провайдера LMT с тарифом OKarte Internets datorā и модемом Huawei E173 и Bite с неизвестным тарифом и модемом Huawei E1550.

[Dialer lmt] 
Init1 = AT
Init2 = AT&FE0V1X1&D2&C1S0=0
#Init3 = AT+CPIN="1219" 
Init4 = AT+CGDCONT=1,"IP","internet.lmt.lv" 
Phone = *99# 
ISDN = 0
Username = { } 
Password = { } 
Ask Password = 0 
Modem = /dev/ttyUSB0 
PPPD Options = noauth crtcts multilink usepeerdns lock defaultroute nobsdcomp nodeflate refuse-pap refuse-eap refuse-chap refuse-mschap +chap 
Idle Seconds = 3000 
Modem Type = USB Modem 
Compuserve = 0 
Auto DNS = 1 
Dial Command = ATD 
Stupid Mode = 1 
FlowControl = NOFLOW
[Dialer bite] 
Init1 = AT
Init2 = AT&FE0V1X1&D2&C1S0=0
#Init3 = AT+CPIN="1219"
Init4 = AT+CGDCONT=1,"IP","internet" 
Phone = *99# 
ISDN = 0 
Username = { } 
Password = { } 
Ask Password = 0 
Modem = /dev/ttyUSB0 
PPPD Options = noauth crtcts multilink usepeerdns lock defaultroute nobsdcomp nodeflate refuse-pap refuse-eap refuse-chap refuse-mschap +chap 
Idle Seconds = 3000 
Modem Type = USB Modem
Compuserve = 0 
Auto DNS = 1 
Dial Command = ATD 
Stupid Mode = 1 
FlowControl = NOFLOW

Вкратце — файл разделён на секции. Каждая из секций отвечает за одну комбинацию модем-провайдер. Начало секции обозначается меткой [Dialer xxx], где ххх — это название метки, по которой мы будем указывать, какие именно настройки нужны для подключения. Если нам потребуются настройки LMT, мы наберём команду wvdial lmt, и будут использоваться настройки из секции [Dialer lmt] — суть понятна. Из этих настроек нам нужно обратить внимание на следующие:

InitX = AT-BLABLABLA

— AT-команды после InitX — те команды, которые wvdial отсылает модему перед тем, как поднять подключение.

#Init3 = AT+CPIN="1219"

— Эта настройка, если убрать # в начале, будет посылать модему команду ввода пин-кода. Если честно, желательно её отключить — у меня эта команда по непонятным причинам не работала корректно. Легче просто подключить модем один раз к компьютеру с Windows и отключить ввод пин-кода при подключении, используя программу, поставляемую с модемом.

Init4 = AT+CGDCONT=1,"IP","internet" 

— Здесь прописывается адрес APN, который предоставляет провайдер. Нужно обратить внимание на две последних отделённых кавычками части. Первая — IP — указывает IP-адрес для подключения, если настройки провайдера подразумевают то, что используется IP-адрес APN. Если же используется буквенный адрес вида “internet” или “internet.lmt.lv”, в первой части нужно оставить “IP”, а во второй — прописать буквенный адрес, как это сделано в примере.

Phone = *99# 

— Ну тут всё стандартно — этот номер телефона используют практически все провайдеры, и менять его в большинстве случаев не понадобится.

Username = { } 
Password = { } 

Имя пользователя и пароль для подключения к интернету. Если их нужно оставить пустыми, оставьте там скобочки вида { }. Если нет — просто поставьте там имя и пароль, без скобочек.

Modem = /dev/ttyUSB0

Имя устройства, которое нам нужно использовать. В 99% случаев оно будет именно таким.

Остальные параметры могут быть другими в случае других модемов, но для вышеперечисленных двух комбинаций модем-провайдер всё работает без проблем.

Ещё раз расскажу о том, как правильно запускать подключение вручную. Достаточно одной команды — wvdial xxx, где ххх — это название провайдера из конфигурационного файла (для меня это либо lmt, либо bite.) Однако — при запуске wvdial ”занимает собой” всю консоль, не давая возможности запустить что-либо ещё. Кроме того — если вы запустите wvdial в окне SSH и тут же разорвёте сессию, то и wvdial завершится. Нужно либо постоянно держать сессию открытой, либо использовать screen, который в данном случае решает сразу две проблемы довольно эффективно — что и советую.
Что в идеале нужно? Также научиться просто и легко запускать эти программы. В использовании мной описанной схемы есть свои нюансы:

1) Соединение нужно каждый раз запускать вручную.
— Достаточно немного изменить конфигурационные файлы системы, а именно — тот же /etc/network/interfaces:

auto ppp0 
iface ppp0 inet wvdial 
provider lmt
#Поднимать интерфейс ppp0 автоматически
#Для подключения вызывать команду wvdial с аргументом lmt. Естественно, аргумент будет меняться.

Для меня этот способ не подходит — он рассчитан на то, что провайдер не меняется, но большая вероятность, что это понадобится кому-то ещё. Да и не особо-то надёжно это работает, по моему опыту, лучше настроить udev. Для себя же я не нашёл подходящих решений — для этого надо было бы определять принадлежность вставленной сим-карты тому или иному провайдеру, а решение с использованием этого становится очень сложным.
Ну а если всё же надо быть постоянно подключённым, даже если что-то глючит и модем отключается от сети? Ну тогда поможет следующий скрипт. Он смотрит, есть ли wvdial в списке процессов, а если нет, то делает ifup ppp0, что в совокупности с вышеупомянутыми настройками в interfaces должно вызывать wvdial заново:

Засунуть себе в cron

#!/bin/bash
# (c)2009 John de Graaff, rewritten by CRImier
# This script checks if wvdial is running.
# If it's not, it brings ppp0 up and down.
# It is assumed that ifup ppp0 starts wvdial
if test "$(pidof wvdial)" != "" ; 
then
	exit 0
else
	logger "wvdial not running. Better restart ppp0."
	/sbin/ifdown ppp0
	sleep 2
	/sbin/ifup ppp0
	logger "ppp0 restarted."
	exit 0

2) При включении ноутбука, если модем был подключен во время загрузки системы, иной раз случаются зависания, которые выражаются в следующем — при попытке подключения, используя wvdial, выходят строчки вида

--> Cannot open /dev/ttyUSB0: Device or resource busy 

, и подключиться не удаётся. Лечится на один раз просто — нужно лишь вынуть и воткнуть модем, а затем запустить соединение вручную, но вы же понимаете, что при отсутствии физического доступа к компьютеру эта задача усложняется до невозможности.
— Пока что я не могу предоставить нормального решения, поскольку сам ещё не занялся этим. Предполагают, что это из-за того, что программа usb-modeswitch не отрабатывает корректно, если модем вставлен в компьютер при запуске системы. Видимо, нужно покопаться с udev или указать какие-либо особые параметры для usb-modeswitch.

3) В условиях плохого приёма соединение часто обрубается
— Всё довольно просто. Дело в том, что у портов ЮСБ есть ограничение на отдаваемый ток, при превышении которого, насколько я помню, порт отрубается. Видимо, в условиях плохого приёма сигнала сети модем пытается повысить мощность приёмника и передатчика, и случается так, что модем начинает потреблять больший ток, чем выдерживает порт — порт отключается, модем выключается, соедниение отрубается насовсем. Посоветовать могу лишь, к примеру, купить отдельный адаптер питания для модема и впаять его в кабель.

После того, как интернет появился на нашем сервере, остаётся лишь настроить раздачу интернета с модема по Wi-Fi сети.

NAT

Если у компьютера есть два сетевых интерфейса, это ещё не означает, что из коробки можно спокойно раздавать интернет с одного на другой. Однако — не всё так сложно, чаще всего требуется всего пара настроек. Конечно, эти настройки сложно запомнить, не вникая в суть каждой строчки, но ведь для этого есть эта статья! Я нашёл наиболее подходящий для этой ситуации и безглючный скрипт, не могу не дать ссылку на него, поскольку найденный на нём скрипт самый короткий и ясный из тех, что я встречал — остальные умудряются растянуть пару правил iptables на несколько страниц… Прежде всего, посмотрю, что в нём надо бы изменить под мои нужды:

Найденный скрипт
Спойлер:

#!/bin/sh

PATH=/usr/sbin:/sbin:/bin:/usr/bin

#
# delete all existing rules.
#
iptables -F
iptables -t nat -F
iptables -t mangle -F
iptables -X

# Always accept loopback traffic
iptables -A INPUT -i lo -j ACCEPT


# Allow established connections, and those not coming from the outside
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -m state --state NEW -i ! eth1 -j ACCEPT
iptables -A FORWARD -i eth1 -o eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow outgoing connections from the LAN side.
iptables -A FORWARD -i eth0 -o eth1 -j ACCEPT

# Masquerade.
iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE

# Don't forward from the outside to the inside.
iptables -A FORWARD -i eth1 -o eth1 -j REJECT

# Enable routing.
echo 1 > /proc/sys/net/ipv4/ip_forward

Хм-хм. Этот скрипт уже староват — iptables ругается на одну из команд и не хочет выполнять, да и тот путь, которым в статье скрипт пытаются поместить в автозагрузку, тоже работает не всегда на моей практике. Более того, есть проблема — этот скрипт отлично подходит для ситуации, когда ничего не собирается меняться. Если бы так и было, я бы поставил iptables-persistence и на этом закончил бы статью. А вот я собираюсь иногда получать интернет по интерфейсу ppp0, иногда — по eth0, а иногда — вообще по wlan1, причём менять интерфейс хочу одной консольной командой. Так, eth1 в примере — внешний интерфейс, а eth0 — внутренний. Заменим их переменными, чтобы при необходимости можно было поменять одну строчку, а не редактировать весь текст. Также я хочу, чтобы при перезапуске компьютера последний выбранный внешний интерфейс сохранялся. Что тогда? Нужно всё поменять!

Задачи:
  1. Принимать первый аргумент командной строки в качестве названия внешнего интерфейса, проверяя подлинность имени, используя команду ifconfig;
  2. Добавить сохранение выбранного интерфейса в какой-нибудь файл в /etc и сделать ключ выбора последнего интерфейса, а лучше — при отсутствии имени интерфейса как аргумента.
  3. Запихнуть это всё красиво в автозагрузку и в $PATH.

Что же вышло в итоге?

#!/bin/bash
#NAT script from www.debian-administration.org, modified by CRImier
# Exit status 0 if operation is correct
# Exit status 1 if trying to use last interface used when running for the first time
# Exit status 2 if interface doesn't exist
EIF=''
IIF='wlan0'
PATH=/usr/sbin:/sbin:/bin:/usr/bin
LOGFILE=/etc/nat-if.conf
touch $LOGFILE

#
#Checking command-line arguments and setting $EIF variable according to them
#

if [ $1 == "" ] #If there's no arguments, just use previous settings.
then
	EIF=`cat $LOGFILE`
	if [ $EIF == "" ] #Just check for an empty file!
	then
		echo "Please, specify interface name for first usage using 'firewall interface', e.g. 'firewall eth0'"
		exit 1
	fi
elif [ $1 == "help" ] #Output help message
then
	echo "NAT script"
	echo "(c) www.debian-administration.org, modified by CRImier"
	echo "Usage: 'firewall interface', 'firewall info' or simply 'firewall' to use last interface firewall was set on."
	echo "Argument is external interface name, internal interface name is hard-coded in the script"
	exit 0
elif [ $1 == "info" ] #Print interface firewall is set on
then 
	cat $LOGFILE
	exit 0
else
	ifconfig $1 &>/dev/null
	if [ $? == 0 ]
	then #Interface name must be correct as ifconfig gives 0 exit code
		EIF=$1
		echo $EIF > $LOGFILE 
	else 
		echo "Incorrect interface name"
		exit 2
	fi
fi

#
#$EIF is set correctly, let's apply the rules:
#

iptables -F 
iptables -t nat -F
iptables -t mangle -F
iptables -X
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -i $EIF -o $IIF -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -i $IIF -o $EIF -j ACCEPT
iptables -t nat -A POSTROUTING -o $EIF -j MASQUERADE
iptables -A FORWARD -i $EIF -o $IIF -j REJECT
echo 1 > /proc/sys/net/ipv4/ip_forward
echo "Firewall started."

Комментарии писал на английском — так привычнее. Если будут просьбы — могу и перевести.

Ну и не забываем обязательную часть:

chmod +x /etc/init.d/user-autorun

Окей, скрипт у нас готов. Как можно понять, вариантов вызова четыре — firewall (используется последний интерфейс), firewall наш_интерфейс, firewall info (выводит текущий интерфейс, на котором настроен NAT) или firewall help. Осталась лишь автозагрузка и $PATH.

echo $PATH 
>/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Для того, чтобы вызывать скрипт командой firewall, не указывая местоположение, нужно запихнуть его в одну из папок, указанных в PATH. Я предпочитаю /usr/local/bin по религиозным соображениям. Полный путь к скрипту будет /usr/local/bin/firewall, а вот вызвать из консоли его всегда можно будет просто командой firewall.

Автозагрузка

А теперь — автозагрузка, с ней посложнее. Я сразу опишу создание скрипта автозагрузки, в который можно будет запихнуть всё, что угодно. Он будет стартовать вместе с системой, нооо…

Нельзя просто так взять и создать файл автозагрузки. Есть одна проблема — Debian с какого-то времени пересмотрел свои требования к файлам автозагрузки. Файл мало просто создать, его нужно ещё по-особому отформатировать:

  1. Первая проблема — это LSB headers. Это заголовок файла автозагрузки. Нужен он потому, что компоненты автозагрузки должны выполняться в определённом порядке, поскольку часть из них зависят друг от друга. Предположим, у вас есть два скрипта в автозагрузке — один из них должен будет монтировать сетевую папку, а второй — делать в неё резервную копию файлов. Естественно, что сначала нужно выполнить первый, а потом — второй, поскольку второй зависит от первого. Для указания таких зависимостей и используются заголовки загрузочного файла. Впрочем, будет достаточно того заголовка, который я выложу в образце файла автозагрузки.
  2. Вторая проблема — любой скрипт в автозагрузке при запуске системы вызывается командой /etc/init.d/script start, а при выключении компьютера — командой /etc/init.d/script stop. Нужно добавить условия для обработки этих случаев.

Я сделал просто — взял за основу скрипт из имеющихся в /etc/init.d/ — уж они-то должны быть созданы по правилам, потом изучил этот скрипт и вырезал из него всё ненужное. Осталось два места, которые нужно изменить — место для команд, которые выполняются при запуске системы, и место для команд, которые выполняются при выключении компьютера. Впрочем, сейчас всё увидите:

#!/bin/sh

### BEGIN INIT INFO
# Provides:          firewall
# Required-Start:    $network $local_fs $remote_fs
# Required-Stop:     $network $local_fs $remote_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# X-Interactive:     false
# Short-Description: Start user autorun events
### END INIT INFO

case "$1" in
	start)
		echo "Starting user autorun events"
		/usr/local/bin/firewall 
		#Место для команд, которые должны выполниться при запуске системы
		;;
	stop)
		echo "Stopping user autorun events"
		#Место для команд, которые должны выполниться при завершении работы системы
		#Останавливать NAT нет необходимости
		;;
	*)
		echo "Usage: /etc/init.d/user-autorun {start|stop}"
		exit 1
		;;
esac

exit 0

Опять же, дать права на исполнение:

chmod +x /etc/init.d/user-autorun

В файле автозагрузки лучше указывать полный путь к исполняемому файлу, поскольку иначе при загрузке иногда возникают проблемы вида “firewall: command not found”.

Этот файл кладём в папку /etc/init.d/. Полный путь к нашему файлу автозагрузки — /etc/init.d/user-autorun. Осталось лишь указать системе, что нужно выполнять этот файл при загрузке:

update-rc.d user-autorun defaults

Эта команда заодно и проверяет, соответствует ли заголовок скрипта нужному, поэтому — если с этим будут проблемы, ничего в автозагрузку не поставится и придётся разбираться с ошибками. Всё, скрипт автозагрузки готов к работе и будет выполняться каждый раз при запуске системы, запуская скрипт маршрутизации. Конечно, в данном решении есть свои недостатки, вроде невозможности как-либо контролировать доступ пользователей к Интернету, кроме отключения-включения самого скрипта, но для случая переносного сервера плюс один и огромный — это просто работает, без вмешательства и стабильно, а альтернативные системы при надобности я ещё успею рассмотреть.
Удачной настройки!


Следующая статья, скорее всего, будет про написание простого веб-интерфейса на Python, используя web.py. Через этот интерфейс можно будет управлять NAT (правда, не превышая возможностей написанного скрипта), включать/выключать wvdial, отсылать смс и просматривать состояние модема… А также делать всё, до чего дойдут руки. Пока что пишу скрипт для взаимодействия с модемом и продумываю интерфейс таким образом, чтобы его было легко использовать даже на мобильных устройствах. Также в запасе есть почти готовая статья по настройке параметров энергосбережения ноутбука, используя cpufreqd. Стоит ли выкладывать её, будет ли актуально?

Аргументированная критика и дополнения к статье категорически приветствуются.

Автор: CRImier

Источник

Поделиться

* - обязательные к заполнению поля