Hard: "USB modem ZTE MF180" и мобильный телефон "Nokia 3110c".
Applications: bash, UDEV, ppp.
Задача: обеспечить автоматическое установление сессии PPP (соединение с сервером удалённого доступа провайдера интернет) при физическом подключении беспроводного модема с выбором профиля по идентификаторам оборудования.
Не так давно заимел я USB-"свисток" беспроводного модема "ZTE MF180" с симкой "K-Cell". Специально такое покупать не стал-бы, так как имеющийся на борту моего мобильного телефона модем меня вполне удовлетворяет, но "за так" - почему бы и нет? "Why not?", как говорится. В предыдущей заметке я рассказал о том, как вживлял модем "ZTE MF180" в рабочее окружение своего Linux.
Побаловался пару дней, но не более того. Применять на практике, в условиях командировки, на объекте в серверной с "отвалившимися" каналами связи, или просто на скамейке в парке, "ручное" подключение не особо удобно. Хотелось большей автоматизации. Созрела мысль настроить полностью автоматический выход в интернет мобильного компьютера, "нетбука" например, при физическом подключении устройства с беспроводным модемом на борту. У меня постоянно с собой мобильный GSM-телефон с "симкой" от "Bee-Line", в сумке с "нетбуком" прописался модем с "симкой" от "K-Cell" - нужно бы научить мой Linux распознавать, что ему подсунули и по созданным заранее профилям инициировать подключение к интернет. Чтобы просто воткнул один провод, и получил доступ в интернет через "K-Cell"; не понравилось - переткнулся на другой провод, получил доступ в интернет через "Bee-Line". Чтобы вообще без единого нажатия клавиши или движения "манипулятора типа мышь".
Как работать с "ZTE MF180" я уже рассказал здесь, повторяться не буду. Модем на борту мобильного телефона "Nokia 3110c" у меня корректно распознаётся уже лет пять, тут и говорить не о чём. Просто для представления о том, с чем имеем дело, показываю, какое у моего Linux ядро на данный момент:
# uname -a
Linux 2.6.32-5-amd64 x86_64 GNU/Linux
Беспроводной модем "ZTE MF180" распознаётся следующим образом:
# dmesg
....
usb 1-4: New USB device found, idVendor=19d2, idProduct=0031
....
usb 1-4: Product: ZTE WCDMA Technologies MSM
usb 1-4: Manufacturer: ZTE,Incorporated
....
USB Serial support registered for GSM modem (1-port)
option 1-4:1.0: GSM modem (1-port) converter detected
usb 1-4: GSM modem (1-port) converter now attached to ttyUSB0
option 1-4:1.1: GSM modem (1-port) converter detected
usb 1-4: GSM modem (1-port) converter now attached to ttyUSB1
option 1-4:1.3: GSM modem (1-port) converter detected
usb 1-4: GSM modem (1-port) converter now attached to ttyUSB2
usbcore: registered new interface driver option
option: v0.7.2:USB Driver for GSM modems
....
usb 1-4: New USB device found, idVendor=19d2, idProduct=0031
....
usb 1-4: Product: ZTE WCDMA Technologies MSM
usb 1-4: Manufacturer: ZTE,Incorporated
....
USB Serial support registered for GSM modem (1-port)
option 1-4:1.0: GSM modem (1-port) converter detected
usb 1-4: GSM modem (1-port) converter now attached to ttyUSB0
option 1-4:1.1: GSM modem (1-port) converter detected
usb 1-4: GSM modem (1-port) converter now attached to ttyUSB1
option 1-4:1.3: GSM modem (1-port) converter detected
usb 1-4: GSM modem (1-port) converter now attached to ttyUSB2
usbcore: registered new interface driver option
option: v0.7.2:USB Driver for GSM modems
....
Мобильный телефон "Nokia 3110c" с модемом на борту распознаётся следующим образом:
....
usb 3-2: New USB device found, idVendor=0421, idProduct=005e
....
usb 3-2: Product: Nokia 3110c
....
cdc_acm 3-2:1.1: ttyACM0: USB ACM device
usbcore: registered new interface driver cdc_acm
cdc_acm: v0.26:USB Abstract Control Model driver for USB modems and ISDN adapters
....
usb 3-2: New USB device found, idVendor=0421, idProduct=005e
....
usb 3-2: Product: Nokia 3110c
....
cdc_acm 3-2:1.1: ttyACM0: USB ACM device
usbcore: registered new interface driver cdc_acm
cdc_acm: v0.26:USB Abstract Control Model driver for USB modems and ISDN adapters
....
Напишем правила UDEV, которые будут отлавливать события включения и отключения целевых устройств, запускать сторонний скрипт с передачей таковому условного имени профиля подключения и адреса интерфейса, на котором расположен модем:
# cat /etc/udev/rules.d/99-gsm-mobile-net.rules
# Внимание: строка "ATTRS{modalias}" содержит идентификаторы в верхнем регистре, в отличии от других мест, где они указываются в нижнем.
# Детектирование подключения "ZTE MF180"
SUBSYSTEMS=="usb", KERNEL=="ttyUSB*", ACTION=="add", ATTRS{modalias}=="usb:v19D2p0031*", ATTRS{bInterfaceNumber}=="03", ATTRS{bInterfaceProtocol}=="ff", RUN+="/usr/local/bin/ppp-gsm-custom.sh zte-mf180 add $kernel"
# Детектирование отключения "ZTE MF180"
SUBSYSTEMS=="usb", KERNEL=="ttyUSB*", ACTION=="remove", ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0031", RUN+="/usr/local/bin/ppp-gsm-custom.sh zte-mf180 remove"
# Детектирование подключения "Nokia 3110c"
SUBSYSTEMS=="usb", KERNEL=="ttyACM*", ACTION=="add", ATTRS{modalias}=="usb:v0421p005E*", ATTRS{bInterfaceNumber}=="01", ATTRS{bInterfaceProtocol}=="01", RUN+="/usr/local/bin/ppp-gsm-custom.sh nokia-3110c add $kernel"
# Детектирование отключения "Nokia 3110c"
SUBSYSTEMS=="usb", ACTION=="remove", ATTRS{idVendor}=="0421", ATTRS{idProduct}=="005e", RUN+="/usr/local/bin/ppp-gsm-custom.sh nokia-3110c remove"
# Детектирование подключения "ZTE MF180"
SUBSYSTEMS=="usb", KERNEL=="ttyUSB*", ACTION=="add", ATTRS{modalias}=="usb:v19D2p0031*", ATTRS{bInterfaceNumber}=="03", ATTRS{bInterfaceProtocol}=="ff", RUN+="/usr/local/bin/ppp-gsm-custom.sh zte-mf180 add $kernel"
# Детектирование отключения "ZTE MF180"
SUBSYSTEMS=="usb", KERNEL=="ttyUSB*", ACTION=="remove", ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0031", RUN+="/usr/local/bin/ppp-gsm-custom.sh zte-mf180 remove"
# Детектирование подключения "Nokia 3110c"
SUBSYSTEMS=="usb", KERNEL=="ttyACM*", ACTION=="add", ATTRS{modalias}=="usb:v0421p005E*", ATTRS{bInterfaceNumber}=="01", ATTRS{bInterfaceProtocol}=="01", RUN+="/usr/local/bin/ppp-gsm-custom.sh nokia-3110c add $kernel"
# Детектирование отключения "Nokia 3110c"
SUBSYSTEMS=="usb", ACTION=="remove", ATTRS{idVendor}=="0421", ATTRS{idProduct}=="005e", RUN+="/usr/local/bin/ppp-gsm-custom.sh nokia-3110c remove"
После сохранения файла даём указание подсистеме UDEV перечитать и принять новую конфигурацию:
# udevadm control --reload-rules
Теперь напишем скрипт, который будет отрабатывать событие и осуществлять управление подключением:
# touch /usr/local/bin/ppp-gsm-custom.sh
# chmod ugo+x /usr/local/bin/ppp-gsm-custom.sh
# chmod ugo+x /usr/local/bin/ppp-gsm-custom.sh
#!/bin/bash
# Получаем имя профиля оборудования (специфика модема и провайдера)
PEER="${1}"
# Получаем статус события (add или remove)
ACTION="${2}"
# Получаем имя интерфейса, к которому подключен модем
ADDR="/dev/${3}"
# Указываем скорость передачи данных интерфейса
SPEED="460800"
# Указываем приложение (или скрипт), которое подготовит соединение между аппаратурой клиента и сервера удалённого доступа до того уровня, на котором уже возможен запуск передачи данных по протоколу PPP
CONNECT="/usr/sbin/chat -v -f /etc/ppp/peers/${PEER}.chat"
# Указываем конфигурационный файл, описывающий условия работы с целевым оборудованием
CONFIG="/etc/ppp/peers/${PEER}.conf"
# Указываем путь для файла журнала событий
LOG="/var/log/ppp-gsm-${PEER}.log"
# На всякий случай создаём файл журнала, если таковой отсутствует
[ ! -f "${LOG}" ] && touch "${LOG}"
# Выясняем PID целевого PPP-сеанса, если таковой уже запущен
PID=`ps wax | grep --invert-match grep | grep --max-count=1 --ignore-case "pppd[ ]*${ADDR}" | awk '{print $1}'`
case "${ACTION}" in
add)
# Проверяем существование "символьного" файла модема
[ ! -c "${ADDR}" ] && exit 1
if [ "${PID}" == "" ]; then
/usr/sbin/pppd ${ADDR} ${SPEED} connect "${CONNECT}" file ${CONFIG} logfile ${LOG} &
fi
;;
remove)
if [ "${PID}" != "" ]; then
kill -s KILL ${PID}
fi
;;
*)
exit 1
;;
esac
exit 0
# Получаем имя профиля оборудования (специфика модема и провайдера)
PEER="${1}"
# Получаем статус события (add или remove)
ACTION="${2}"
# Получаем имя интерфейса, к которому подключен модем
ADDR="/dev/${3}"
# Указываем скорость передачи данных интерфейса
SPEED="460800"
# Указываем приложение (или скрипт), которое подготовит соединение между аппаратурой клиента и сервера удалённого доступа до того уровня, на котором уже возможен запуск передачи данных по протоколу PPP
CONNECT="/usr/sbin/chat -v -f /etc/ppp/peers/${PEER}.chat"
# Указываем конфигурационный файл, описывающий условия работы с целевым оборудованием
CONFIG="/etc/ppp/peers/${PEER}.conf"
# Указываем путь для файла журнала событий
LOG="/var/log/ppp-gsm-${PEER}.log"
# На всякий случай создаём файл журнала, если таковой отсутствует
[ ! -f "${LOG}" ] && touch "${LOG}"
# Выясняем PID целевого PPP-сеанса, если таковой уже запущен
PID=`ps wax | grep --invert-match grep | grep --max-count=1 --ignore-case "pppd[ ]*${ADDR}" | awk '{print $1}'`
case "${ACTION}" in
add)
# Проверяем существование "символьного" файла модема
[ ! -c "${ADDR}" ] && exit 1
if [ "${PID}" == "" ]; then
/usr/sbin/pppd ${ADDR} ${SPEED} connect "${CONNECT}" file ${CONFIG} logfile ${LOG} &
fi
;;
remove)
if [ "${PID}" != "" ]; then
kill -s KILL ${PID}
fi
;;
*)
exit 1
;;
esac
exit 0
Подготовим профили подключений.
Просто для того, чтобы обезопаситься от использования нецелевого имени и пароля (в случае ошибки или опечатки), заводим в "chap-secrets" и "pap-secrets" пользователя, от имени которого будем проходить фиктивную процедуру аутентификации, если такая будет запрошена сервером (и запрашивается, что забавно, при том, что для оператора сотовой связи она бессмыслена):
# echo "internet * internet" >> /etc/ppp/chap-secrets
# echo "internet * internet" >> /etc/ppp/pap-secrets
# echo "internet * internet" >> /etc/ppp/pap-secrets
Создаём конфигурационный файл параметров PPP-сессии, именуя его в соответствии с выбранной ранее политикой. Например, в нашем случае для модема "ZTE MF180" UDEV будет передавать скрипту строку "zte-mf180":
# touch /etc/ppp/peers/zte-mf180.conf
# Если не указать явно имя пользователя, то клиент PPP шлёт в качестве такового имя компьютера
user internet
# Явно задаём режим работы, в котором мы не требуем аутентификации у сервера удалённого доступа (большинство публичных провайдеров её и не предоставят)
noauth
# Включаем поддержку аппаратного контроля целостности потока передаваемых данных (hardware flow control), разгружая тем самым процессор компьютера
crtscts
# Если на стороне клиента или сервера удалённого доступа установлено не особо интеллектуальное оборудование, то, возможно, придётся отключить сжатие данных с помощью того или иного набора алгоритмов (определить точку сбоя поможет временно включенный режим "отладки")
# noaccomp noccp nobsdcomp nodeflate nopcomp novj novjccomp
# Задаём период (в секундах) обмена служебными (предназначенными для настройки параметров взаимодействия канала передачи данных между клиентом и сервером удалённого доступа) LCP-пакетами (для детектирования "зависшей" линии передачи данных)
lcp-echo-interval 10
# Объявляем соединение "зависшим" после нескольких неудачных попыток обмена LCP-пакетами
lcp-echo-failure 3
# Указываем клиенту PPP инициировать соединение с сервером удалённого доступа в случае обнаружения разрыва такового
persist
# Задаём максимальное количество попыток соединения, после которых клиент PPP должен прекратить работу (0 - неограниченное количество)
maxfail 0
# Задаём паузу (в секундах) между попытками осуществить соединение с сервером удалённого доступа
holdoff 5
# Пресекаем использование уже имеющихся IP-адресов сетевых интерфейсов компьютера в качестве "локального" для соединения PPP, настаивая на автоматическом получение сетевых настроек от сервера удалённого доступа
noipdefault
# Запрашиваем у сервера удалённого доступа список DNS-серверов (я отключаю эту опцию, так как имею собственные DNS-сервера, указанные в "/etc/resolv.conf")
# usepeerdns
# Указываем сделать шлюз, полученный от сервера удалённого доступа, маршрутом "по умолчанию" для системы в целом (вторая опция уточняет поведение клиента PPP, на тот случай, если в системе уже имеется маршрут "по умолчанию" - он будет перезаписан новым)
defaultroute
replacedefaultroute
# Детализируем вывод сообщений о статусе соединения (полезно только тогда, когда локализуется проблема)
# debug
# Указываем не переводить приложение в "фоновый" режим, оставляя прикреплённым его к той консоли, в которой оно было запущено (полезно для быстрой оценки текущего статуса соединения во время локализации проблемы)
# nodetach
user internet
# Явно задаём режим работы, в котором мы не требуем аутентификации у сервера удалённого доступа (большинство публичных провайдеров её и не предоставят)
noauth
# Включаем поддержку аппаратного контроля целостности потока передаваемых данных (hardware flow control), разгружая тем самым процессор компьютера
crtscts
# Если на стороне клиента или сервера удалённого доступа установлено не особо интеллектуальное оборудование, то, возможно, придётся отключить сжатие данных с помощью того или иного набора алгоритмов (определить точку сбоя поможет временно включенный режим "отладки")
# noaccomp noccp nobsdcomp nodeflate nopcomp novj novjccomp
# Задаём период (в секундах) обмена служебными (предназначенными для настройки параметров взаимодействия канала передачи данных между клиентом и сервером удалённого доступа) LCP-пакетами (для детектирования "зависшей" линии передачи данных)
lcp-echo-interval 10
# Объявляем соединение "зависшим" после нескольких неудачных попыток обмена LCP-пакетами
lcp-echo-failure 3
# Указываем клиенту PPP инициировать соединение с сервером удалённого доступа в случае обнаружения разрыва такового
persist
# Задаём максимальное количество попыток соединения, после которых клиент PPP должен прекратить работу (0 - неограниченное количество)
maxfail 0
# Задаём паузу (в секундах) между попытками осуществить соединение с сервером удалённого доступа
holdoff 5
# Пресекаем использование уже имеющихся IP-адресов сетевых интерфейсов компьютера в качестве "локального" для соединения PPP, настаивая на автоматическом получение сетевых настроек от сервера удалённого доступа
noipdefault
# Запрашиваем у сервера удалённого доступа список DNS-серверов (я отключаю эту опцию, так как имею собственные DNS-сервера, указанные в "/etc/resolv.conf")
# usepeerdns
# Указываем сделать шлюз, полученный от сервера удалённого доступа, маршрутом "по умолчанию" для системы в целом (вторая опция уточняет поведение клиента PPP, на тот случай, если в системе уже имеется маршрут "по умолчанию" - он будет перезаписан новым)
defaultroute
replacedefaultroute
# Детализируем вывод сообщений о статусе соединения (полезно только тогда, когда локализуется проблема)
# debug
# Указываем не переводить приложение в "фоновый" режим, оставляя прикреплённым его к той консоли, в которой оно было запущено (полезно для быстрой оценки текущего статуса соединения во время локализации проблемы)
# nodetach
Конфигурационный файл сессии PPP для подключения к "интернету от Bee-Line" через модем "Nokia 3110c" аналогичен тому, что мы написали для "интернета от K-Cell" через модем "ZTE MF180". Просто копируем файл в новый, с соответствующим именем:
# cp /etc/ppp/peers/zte-mf180.conf /etc/ppp/peers/nokia-3110c.conf
Создаём файл со строками инициализации, который будет использоваться скриптом подготавливающим соединение между аппаратурой клиента и сервера удалённого доступа до того уровня, на котором уже возможен запуск передачи данных по протоколу PPP. Этот файл - для соединения с "K-Cell" через модем "ZTE MF180":
# touch /etc/ppp/peers/zte-mf180.chat
'ECHO' 'ON'
'TIMEOUT' '10'
'ABORT' 'BUSY'
'ABORT' 'ERROR'
'ABORT' 'NO ANSWER'
'' 'ATZ'
'' 'AT+ZSNT=0,0,2'
'OK' 'AT+CGDCONT=1,"IP","internet"'
'OK' 'ATDT*99#'
'TIMEOUT' '30'
CONNECT
'TIMEOUT' '10'
'ABORT' 'BUSY'
'ABORT' 'ERROR'
'ABORT' 'NO ANSWER'
'' 'ATZ'
'' 'AT+ZSNT=0,0,2'
'OK' 'AT+CGDCONT=1,"IP","internet"'
'OK' 'ATDT*99#'
'TIMEOUT' '30'
CONNECT
Строка инициализации у "Bee-Line" через "Nokia 3110c" несколько иная, потому конфигурацию для инициализации канала создаём отдельно:
# touch /etc/ppp/peers/nokia-3110c.chat
'ECHO' 'ON'
'TIMEOUT' '10'
'ABORT' 'BUSY'
'ABORT' 'ERROR'
'ABORT' 'NO ANSWER'
'' 'ATZ'
'OK' 'AT+CGDCONT=1,"IP","internet.beeline.kz"'
'OK' 'ATDT*99#'
'TIMEOUT' '30'
CONNECT
'TIMEOUT' '10'
'ABORT' 'BUSY'
'ABORT' 'ERROR'
'ABORT' 'NO ANSWER'
'' 'ATZ'
'OK' 'AT+CGDCONT=1,"IP","internet.beeline.kz"'
'OK' 'ATDT*99#'
'TIMEOUT' '30'
CONNECT
На этом подготовительные работы заканчиваются. Подключаем модем и проверяем содержимое журнала событий:
# cat /var/log/ppp-gsm-zte-mf180.log
ATZ
OK
AT+ZSNT=0,0,2
OK
AT+CGDCONT=1,"IP","internet"
OK
ATDT*99#
CONNECT
Serial connection established.
Using interface ppp0
Connect: ppp0 <--> /dev/ttyUSB2
CHAP authentication succeeded
Could not determine remote IP address: defaulting to 10.64.64.65
local IP address 10.93.198.69
remote IP address 10.64.64.65
primary DNS address 2.78.43.17
secondary DNS address 2.78.43.18
OK
AT+ZSNT=0,0,2
OK
AT+CGDCONT=1,"IP","internet"
OK
ATDT*99#
CONNECT
Serial connection established.
Using interface ppp0
Connect: ppp0 <--> /dev/ttyUSB2
CHAP authentication succeeded
Could not determine remote IP address: defaulting to 10.64.64.65
local IP address 10.93.198.69
remote IP address 10.64.64.65
primary DNS address 2.78.43.17
secondary DNS address 2.78.43.18
Отлично, мы уже "в интернете".
Если мы вынем модем из "USB-порта", отработают правила UDEV, которые запустят скрипт завершения сессии PPP. Работа программы будет корректно завершена, о чём в журнале событий появится соответствующая запись:
# cat /var/log/ppp-gsm-zte-mf180.log
Modem hangup
Connect time 3.0 minutes.
Sent 0 bytes, received 28 bytes.
Connection terminated.
Connect time 3.0 minutes.
Sent 0 bytes, received 28 bytes.
Connection terminated.
Вот как-то так обеспечивается у меня подключение к сети интернет на "нетбуке". Учитывая то, что при первой-же возможности я заменил в нём встроенный HDD на SSD, проверить почту и вообще, поработать, я могу где угодно: в движении на машине, в поезде, в кафе, в парке - да вообще, везде, в зоне покрытия провайдера мобильной связи.