From: Michael Tremer Date: Sun, 1 Jul 2012 15:42:31 +0000 (+0000) Subject: Enhanced modem support. X-Git-Tag: 005~71 X-Git-Url: http://git.ipfire.org/?p=network.git;a=commitdiff_plain;h=6c74a64cb3ff4d1b2966219ab1c6367123c35df6 Enhanced modem support. This patch adds a very advanced solution for dialup connection with serial modems (56k, UMTS, LTE and more). --- diff --git a/Makefile b/Makefile index e118ae86..5cf67f24 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ install: ln -svf ip-updown $(DESTDIR)$(sysconfdir)/ppp/ip-down ln -svf ip-updown $(DESTDIR)$(sysconfdir)/ppp/ipv6-up ln -svf ip-updown $(DESTDIR)$(sysconfdir)/ppp/ipv6-down - install -m 755 -v ppp/dialer $(DESTDIR)$(sysconfdir)/ppp + install -m 755 -v ppp/dialer $(DESTDIR)$(libdir)/network/ # Install pppoe-server wrapper. install -m 755 ppp/pppoe-server $(DESTDIR)$(libdir)/network/ diff --git a/functions.at b/functions.at new file mode 100644 index 00000000..b30c600d --- /dev/null +++ b/functions.at @@ -0,0 +1,38 @@ +#!/bin/bash +############################################################################### +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2012 IPFire Network Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +############################################################################### + +# This file defines a bunch of default AT commands. + +# Initialize the modem. +AT_INITIALIZE="+++ATZ" + +# Pulse dial. +AT_PULSE_DIAL="ATDP" + +# Tone dial. +AT_TONE_DIAL="ATDT" + +# Speaker settings. +AT_SPEAKER_OFF="ATM0" +AT_SPEAKER_ON="ATM1" + +# Hangup. +AT_HANGUP="ATH" diff --git a/functions.device b/functions.device index 29be51c0..ffff8a75 100644 --- a/functions.device +++ b/functions.device @@ -65,7 +65,12 @@ function device_exists() { # If device name was not found, exit. [ -n "${device}" ] || return ${EXIT_ERROR} - [ -d "${SYS_CLASS_NET}/${device}" ] + # Check for a normal network device. + [ -d "${SYS_CLASS_NET}/${device}" ] && return ${EXIT_OK} + + # If the check above, did not find a result, + # we check for serial devices. + serial_exists ${device} } function device_has_flag() { @@ -171,6 +176,10 @@ function device_is_wireless() { [ -d "${SYS_CLASS_NET}/${device}/phy80211" ] } +function device_is_serial() { + serial_exists $@ +} + # Check if the device is a physical network interface function device_is_ethernet() { local device=${1} @@ -221,6 +230,9 @@ function device_get_type() { elif device_is_ethernet ${device}; then echo "ethernet" + elif device_is_serial ${device}; then + echo "serial" + else echo "unknown" fi diff --git a/functions.modem b/functions.modem new file mode 100644 index 00000000..8b78478c --- /dev/null +++ b/functions.modem @@ -0,0 +1,530 @@ +#!/bin/bash +############################################################################### +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2012 IPFire Network Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +############################################################################### + +# Exit codes from the chat(8) command: +CHAT_OK=0 +CHAT_INVALID=1 +CHAT_ERROR=2 +CHAT_TIMEOUT=3 + +function modem_chat() { + local answer="OK" + local device + local timeout=2 + local quiet="false" + + while [ $# -gt 0 ]; do + case "${1}" in + --timeout=*) + timeout=$(cli_get_val ${1}) + ;; + --answer=*) + answer=$(cli_get_val ${1}) + ;; + --quiet) + quiet="true" + ;; + *) + device=${1} + shift; break + ;; + esac + shift + done + + assert serial_exists ${device} + assert serial_is_unlocked ${device} + assert isset answer + assert isset timeout + + local command=$@ + + log DEBUG "Sending command to ${device}: ${command}" + + ( + # This cannot be run with -x. + set +x 2>/dev/null + + chat -V -s -t ${timeout} "" "${command}" "${answer}" \ + < ${device} > ${device} || exit $? + print # Print line feed. + ) 2>&1 | __modem_chat_process_output "${answer}" ${quiet} + + local ret=${PIPESTATUS[0]} + + # Return the exit code of the chat command. + case "${ret}" in + ${CHAT_OK}) + return ${EXIT_OK} + ;; + + # When the timeout condition hit, the expected string has not + # been received in the given amount of time. + ${CHAT_TIMEOUT}|${CHAT_ERROR}) + return ${EXIT_ERROR} + ;; + + ${CHAT_INVALID}) + return ${EXIT_CONF_ERROR} + ;; + esac + + log WARNING "Received unknown exit code from chat(8): ${ret}" + return ${EXIT_ERROR} +} + +function __modem_chat_process_output() { + local answer=${1} + local quiet=${2} + + if enabled quiet; then + # Just throw everything away. + cat >/dev/null + return ${EXIT_OK} + fi + + local counter=0 + + local line + while read line; do + log DEBUG "Output[${counter}]: ${line}" + + # Also skip empty lines. + [ -n "${line}" ] || continue + + # Ignore all volatile messages. + [ "${line:0:1}" = "^" ] && continue + + counter=$(( ${counter} + 1 )) + + # Skip the first line, because that's out command. + [ ${counter} -eq 1 ] && continue + + # Filter out the expected answer. + [ "${line}" = "${answer}" ] && continue + + # Print the rest. + print "${line}" + done +} + +# Exit codes of the sim_status function. +EXIT_SIM_READY=0 +EXIT_SIM_PIN=1 +EXIT_SIM_PUK=2 +EXIT_SIM_UNKNOWN=3 + +function modem_sim_status() { + local device=${1} + assert isset device + + local output + output=$(modem_chat ${device} "AT+CPIN?") + assert_check_retval $? + + # Strip leading +CPIN: from the output. + output=${output#*: } + + case "${output}" in + "READY") + log DEBUG "${device}'s SIM is unlocked or doesn't need a PIN." + return ${EXIT_SIM_READY} + ;; + "SIM PIN") + log DEBUG "${device}'s SIM is waiting for a PIN." + return ${EXIT_SIM_PIN} + ;; + "SIM PUK") + log DEBUG "${device}'s SIM is PUK locked." + return ${EXIT_SIM_PUK} + ;; + esac + + log WARNING "${device}: Invalid output of the AT+CPIN? command." + return ${EXIT_SIM_UNKNOWN} +} + +function modem_sim_unlocked() { + local device=${1} + assert isset device + + modem_sim_status "${device}" + local ret=$? + + [ ${ret} -eq ${EXIT_SIM_READY} ] && return ${EXIT_TRUE} || return ${EXIT_FALSE} +} + +function modem_sim_locked() { + modem_sim_unlocked $@ && return ${EXIT_FALSE} || return ${EXIT_TRUE} +} + +function modem_sim_unlock() { + local device=${1} + assert isset device + + local pin=${2} + assert isset pin + + local command="AT+CPIN=${pin}" + + local new_pin=${3} + if isset new_pin; then + command="${command},${new_pin}" + fi + + modem_chat --timeout=2 --quiet "${device}" "${command}" + + local ret=$? + case "${ret}" in + ${EXIT_OK}) + log INFO "Successfully unlocked SIM card on ${device}." + ;; + *) + log ERROR "Could not unlock SIM card on ${device}." + ret=${EXIT_ERROR} + ;; + esac + + return ${ret} +} + +# Returns the vendor of the modem. +# For example: "huawei" +function modem_get_manufacturer() { + local device=${1} + assert isset device + + local output + output=$(modem_chat ${device} "AT+GMI") + assert_check_retval $? + + [ "${output:0:1}" = "+" ] && output=${output#*: } + output=${output//\"/} + + print "${output}" +} + +function modem_get_model() { + local device=${1} + assert isset device + + local output + output=$(modem_chat ${device} "AT+GMM") + assert_check_retval $? + + [ "${output:0:1}" = "+" ] && output=${output#*: } + output=${output//\"/} + + print "${output}" +} + +function modem_get_software_version() { + local device=${1} + assert isset device + + local output + output=$(modem_chat ${device} "AT+GMR") + assert_check_retval $? + + [ "${output:0:1}" = "+" ] && output=${output#*: } + output=${output//\"/} + + print "${output}" +} + +function modem_get_sim_imsi() { + local device=${1} + assert isset device + + # Make sure the SIM card is unlocked for this operation. + assert modem_sim_unlocked ${device} + + modem_chat ${device} "AT+CIMI" +} + +function modem_get_device_imei() { + local device=${1} + assert isset device + + local output + output=$(modem_chat --timeout=1 ${device} "AT+CGSN") || assert_check_retval $? + local ret=$? + + if [ ${ret} -eq ${EXIT_OK} ]; then + print "${output}" + fi + + return ${ret} +} + +function modem_is_mobile() { + local device=${1} + assert isset device + + # Check if the device can return it's IMEI. + # If not, it's probably a serial 56k modem or something + # in that category. + + modem_get_device_imei ${device} &>/dev/null +} + +# Exit codes of the network registration function. +EXIT_REG_REGISTERED_TO_HOME_NETWORK=0 +EXIT_REG_NOT_REGISTERED_NOT_SEARCHING=1 +EXIT_REG_NOT_REGISTERED_SEARCHING=2 +EXIT_REG_REGISTRATION_DENIED=3 +EXIT_REG_REGISTERED_ROAMING=4 +EXIT_REG_UNKNOWN=5 + +function modem_get_network_registration() { + local device=${1} + assert isset device + + # Make sure the SIM card is unlocked for this operation. + assert modem_sim_unlocked ${device} + + local output + output=$(modem_chat ${device} "AT+CREG?") + assert_check_retval $? + + # Cut out unneeded parts of the message. + output=${output#*: } + + case "${output}" in + [0-2],[0-5]) + local status=${output%,*} + + # The status variable must be zero. + [ ${status} -ne 0 ] && break + + local stat=${output#*,} + case "${stat}" in + 0) + return ${EXIT_REG_NOT_REGISTERED_NOT_SEARCHING} + ;; + 1) + return ${EXIT_REG_REGISTERED_TO_HOME_NETWORK} + ;; + 2) + return ${EXIT_REG_NOT_REGISTERED_SEARCHING} + ;; + 3) + return ${EXIT_REG_REGISTRATION_DENIED} + ;; + 5) + return ${EXIT_REG_REGISTERED_ROAMING} + ;; + *) + return ${EXIT_REG_UNKNOWN} + ;; + esac + ;; + esac + + # Apparently the output of the CREG? command was not in + # the right format. The modem will be tried to be set to the + # right mode. + + modem_set_network_registration ${device} 0 + modem_get_network_registration ${device} +} + +function modem_set_network_registration() { + local device=${1} + assert isset device + + # Make sure the SIM card is unlocked for this operation. + assert modem_sim_unlocked ${device} + + local mode=${2} + assert isset mode + + modem_chat ${device} "AT+CREG=${mode}" +} + +function modem_scan_networks() { + local device=${1} + assert isset device + + # Make sure the SIM card is unlocked for this operation. + assert modem_sim_unlocked ${device} + + local output + output=$(modem_chat --timeout=60 ${device} "AT+COPS=?") + assert_check_retval $? + + output=${output#*: } + + # XXX the output is not very nice to parse. +} + +function __modem_get_network_operator() { + local device=${1} + assert isset device + + # Make sure the SIM card is unlocked for this operation. + assert modem_sim_unlocked ${device} + + local argument=${2} + assert isset argument + + local output + output=$(modem_chat ${device} "AT+COPS?") + assert_check_retval $? + + output=${output#*: } + + local mode format operator act + read mode format operator act <<< "${output//,/ }" + + # Remove all " from the operator string. + operator=${operator//\"/} + + print "${!argument}" + return ${EXIT_OK} +} + +function modem_get_network_operator() { + local device=${1} + assert isset device + + __modem_get_network_operator ${device} operator +} + +# Exit codes of the network operator mode function. +EXIT_OPMODE_GSM=0 +EXIT_OPMODE_COMPACTGSM=1 +EXIT_OPMODE_GSM_WITH_EGPRS=2 +EXIT_OPMODE_UMTS=3 +EXIT_OPMODE_UMTS_WITH_HSDPA=4 +EXIT_OPMODE_UMTS_WITH_HSUPA=5 +EXIT_OPMODE_UMTS_WITH_HSDPA_AND_HSUPA=6 +EXIT_OPMODE_UNKNOWN=7 + +function modem_get_network_mode() { + local device=${1} + assert isset device + + local output + output=$(__modem_get_network_operator ${device} act) + assert_check_retval $? + + case "${output}" in + 0) + print "GSM" + return ${EXIT_OPMODE_GSM} + ;; + 1) + print "Compact GSM" + return ${EXIT_OPMODE_COMPACTGSM} + ;; + 2) + print "UMTS" + return ${EXIT_OPMODE_UMTS} + ;; + 3) + print "UMTS with HSDPA" + return ${EXIT_OPMODE_UMTS_WITH_HSDPA} + ;; + 4) + print "UMTS with HSUPA" + return ${EXIT_OPMODE_UMTS_WITH_HSUPA} + ;; + 5) + print "UMTS with HSDPA and HSUPA" + return ${EXIT_OPMODE_UMTS_WITH_HSDPA_AND_HSUPA} + ;; + *) + print "Unknown" + return ${EXIT_OPMODE_UNKNOWN} + ;; + esac +} + +function __modem_get_signal_quality() { + local device=${1} + assert isset device + + # Make sure the SIM card is unlocked for this operation. + assert modem_sim_unlocked ${device} + + local argument=${2} + assert isset argument + + local output + output=$(modem_chat ${device} "AT+CSQ") + assert_check_retval $? + + output=${output#*: } + + case "${output}" in + *,*) + local rssi=${output%,*} + local ber=${output#*,} + + print "${!argument}" + return ${EXIT_OK} + ;; + *) + log ERROR "Unknown format for AT+CSQ: ${device}: ${output}" + ;; + esac + + return ${EXIT_ERROR} +} + +function modem_get_signal_quality() { + local device=${1} + assert isset device + + local rssi + rssi=$(__modem_get_signal_quality ${device} rssi) + assert_check_retval $? + + isset rssi || return ${EXIT_ERROR} + + # 99 indicates an unknown signal strength. + [ ${rssi} -eq 99 ] && return ${EXIT_UNKNOWN} + + local dbm=$(( ${rssi} * 2 )) + dbm=$(( ${dbm} - 113 )) + + print "%d" "${dbm}" + return ${EXIT_OK} +} + +function modem_get_bit_error_rate() { + local device=${1} + assert isset device + + local ber + ber=$(__modem_get_signal_quality ${device} ber) + assert_check_retval $? + + isset ber || return ${EXIT_ERROR} + + # 99 indicates that the bit error rate could not be detected or + # is unknown for some other reason. + [ ${ber} -eq 99 ] && return ${EXIT_UNKNOWN} + + print "%d" "${ber}" + return ${EXIT_OK} +} diff --git a/functions.ppp b/functions.ppp index 640b0cac..29a4c965 100644 --- a/functions.ppp +++ b/functions.ppp @@ -45,6 +45,9 @@ function pppd_start() { 5) error "pppd terminated" ;; + 16) + error "pppd: Link terminated by modem" + ;; 19) error "pppd: Authentication failed" ;; @@ -214,14 +217,18 @@ function pppd_write_config() { assert isset file local auth + local baudrate + local connect_cmd local default_asyncmap="true" local interface local lcp_echo_failure=3 local lcp_echo_interval=20 local linkname local mtu mru + local password local plugin plugin_options - local user + local serial="false" + local username local value while [ $# -gt 0 ]; do @@ -229,6 +236,13 @@ function pppd_write_config() { --auth=*) auth=$(cli_get_val ${1}) ;; + --baudrate=*) + baudrate=$(cli_get_val ${1}) + assert isoneof baudrate ${SERIAL_BAUDRATES} + ;; + --connect-command=*) + connect_cmd=$(cli_get_val ${1}) + ;; # Enable or disable the use of the default asyncmap. --default-asyncmap=*) value=$(cli_get_val ${1}) @@ -268,14 +282,24 @@ function pppd_write_config() { --mru=*) mru=$(cli_get_val ${1}) ;; + --password=*) + password=$(cli_get_val ${1}) + ;; --plugin=*) plugin=$(cli_get_val ${1}) ;; --plugin-options=*) plugin_options=$(cli_get_val ${1}) ;; - --user=*) - user=$(cli_get_val ${1}) + # Sets if the modem is a serial device. + --serial=*) + serial=$(cli_get_val ${1}) + ;; + --serial-device=*) + serial_device=$(cli_get_val ${1}) + ;; + --username=*) + username=$(cli_get_val ${1}) ;; *) log WARNING "Unhandled argument: ${1}" @@ -297,6 +321,14 @@ function pppd_write_config() { fi fi + if enabled serial; then + assert isset serial_device + assert [ -c "${serial_device}" ] + fi + + # Set the user credentials. + ppp_secret "${username}" "${password}" + # Write the configuration header. mkdir -p $(dirname ${file}) 2>/dev/null config_header "PPP daemon configuration file" > ${file} @@ -304,8 +336,12 @@ function pppd_write_config() { # At first, set the name of the link. print "linkname ${linkname}\n" >> ${file} - # Configure the interface name. - print "# Interface name\nifname ${interface}\n" >> ${file} + # Configure the interface/zone name. + ( + print "# Interface name" + print "ifname ${interface}" + print + ) >> ${file} # Plugin settings if isset plugin; then @@ -317,10 +353,10 @@ function pppd_write_config() { fi # User authentication - if isset user; then + if isset username; then ( print "# User authentication" - print "user ${user}" + print "user ${username}" print "noauth" if isset auth; then @@ -342,6 +378,26 @@ function pppd_write_config() { ) >> ${file} fi + if enabled serial; then + ( + print "# Serial modem settings" + print "${serial_device} ${baudrate}" + print "crtscts" + print "lock" + print "modem" + print + ) >> ${file} + + # Connect command + if isset connect_cmd; then + ( + print "# Connect command" + print "connect \"${connect_cmd}\"" + print + ) >> ${file} + fi + fi + # Default asyncmap. if enabled default_asyncmap; then ( diff --git a/functions.routing b/functions.routing index 10e12828..9f0e013c 100644 --- a/functions.routing +++ b/functions.routing @@ -50,12 +50,13 @@ function routing_default_update() { if [ "$(routing_db_get ${zone} ${proto} active)" = "1" ]; then gateway=$(routing_db_get ${zone} ${proto} remote-ip-address) - assert device_exists ${zone} + # Go on if the device is not there anymore. + device_exists ${zone} || continue # If we have got a Point-to-Point device, we will directly send all # packets into the pipe. if device_is_ptp ${zone}; then - routes="${routes} dev ${zone}" + routes="${routes} nexthop dev ${zone}" # On other devices, we will use the gateway if we got one. elif isset gateway; then diff --git a/functions.serial b/functions.serial new file mode 100644 index 00000000..8540e293 --- /dev/null +++ b/functions.serial @@ -0,0 +1,69 @@ +#!/bin/bash +############################################################################### +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2012 IPFire Network Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +############################################################################### + +# This is a list of baudrates that are supported. +SERIAL_BAUDRATES="921600 460800 230400 115200 57600 38400 19200 9600" + +function serial_exists() { + local device=${1} + + [ -c "${device}" ] +} + +function serial_is_locked() { + local device=${1} + assert isset device + + device=$(basename ${device}) + + local dir + for dir in /var/lock /var/lock/ppp; do + [ -e "${dir}/LCK..${device}" ] \ + && return ${EXIT_TRUE} + done + + return ${EXIT_FALSE} +} + +function serial_is_unlocked() { + serial_is_locked ${device} \ + && return ${EXIT_FALSE} || return ${EXIT_TRUE} +} + +function serial_is_modem() { + local device=${1} + assert isset device + + modem_chat --timeout=2 "${device}" "AT" &>/dev/null + local ret=$? + + case "${ret}" in + ${EXIT_OK}) + return ${EXIT_TRUE} + ;; + ${EXIT_ERROR}) + return ${EXIT_FALSE} + ;; + *) + return ${EXIT_UNKNOWN} + ;; + esac +} diff --git a/hooks/zones/modem b/hooks/zones/modem index 472988b6..9dc2b64c 100755 --- a/hooks/zones/modem +++ b/hooks/zones/modem @@ -21,86 +21,106 @@ . /lib/network/header-zone -HOOK_SETTINGS="HOOK AUTH DEVICE BAUDRATE LINKNAME USER SECRET PEERDNS DEFAULTROUTE MTU" +# Modems support all authentication methods, that pppd does support. +MODEM_ALLOWED_AUTH_METHODS="${PPP_ALLOWED_AUTH_METHODS}" +HOOK_SETTINGS="HOOK" + +# Access Point Name. +APN= +HOOK_SETTINGS="${HOOK_SETTINGS} APN" + +# Sets the authentication algortihm that must be used. AUTH= +HOOK_SETTINGS="${HOOK_SETTINGS} AUTH" + +# Baudrate. BAUDRATE=921600 -DEFAULTROUTE=1 +HOOK_SETTINGS="${HOOK_SETTINGS} BAUDRATE" + +# The device name of the serial device. +# XXX how can we make sure that this does not change all the time? DEVICE= -LINKNAME="$(uuid)" +HOOK_SETTINGS="${HOOK_SETTINGS} DEVICE" + +# A monitor device. +# Send AT commands to this device, when the primary device is +# connected. +MONITOR_DEVICE= +HOOK_SETTINGS="${HOOK_SETTINGS} MONITOR_DEVICE" + +# Maximum transmission unit. MTU=1492 -PEERDNS=1 -SECRET= -USER= +HOOK_SETTINGS="${HOOK_SETTINGS} MTU" -MODEM_ALLOWED_AUTHS="chap pap" +# User credentials. +USERNAME= +PASSWORD= +HOOK_SETTINGS="${HOOK_SETTINGS} USERNAME PASSWORD" -function pppd_pid() { - local zone=${1} - shift +# PIN code. +PIN= +HOOK_SETTINGS="${HOOK_SETTINGS} PIN" - cat /var/run/${zone}.pid 2>/dev/null -} +# Phone number. +PHONE_NUMBER= +HOOK_SETTINGS="${HOOK_SETTINGS} PHONE_NUMBER" function _check() { - assert isset USER - assert isset SECRET - assert isset LINKNAME - assert isset DEFAULTROUTE - assert isset PEERDNS assert isset DEVICE + assert isset PHONE_NUMBER + + # Make sure the PIN code is an integer, when set. + if isset PIN; then + assert isinteger PIN + assert [ ${#PIN} -ge 4 ] + assert [ ${#PIN} -le 8 ] + fi - assert isbool DEFAULTROUTE - assert isbool PEERDNS - assert isinteger BAUDRATE + assert isoneof BAUDRATE ${SERIAL_BAUDRATES} - isset AUTH && assert isoneof AUTH ${MODEM_ALLOWED_AUTHS} + isset AUTH && assert isoneof AUTH ${MODEM_ALLOWED_AUTH_METHODS} } function _parse_cmdline() { local value while [ $# -gt 0 ]; do - case "$1" in - --user=*) - USER=${1#--user=} + case "${1}" in + --apn=*) + APN=$(cli_get_val ${1}) ;; - --secret=*) - SECRET=${1#--secret=} + --auth=*) + AUTH=$(cli_get_val ${1}) ;; - --linkname=*) - LINKNAME=${1#--name=} + --baudrate=*) + BAUDRATE=$(cli_get_val ${1}) + assert isoneif "${BAUDRATE}" ${SERIAL_BAUDRATES} + ;; + --device=*) + DEVICE=$(cli_get_val ${1}) + ;; + --monitor-device=*) + MONITOR_DEVICE=$(cli_get_val ${1}) ;; --mtu=*) - MTU=${1#--mtu=} - ;; - --defaultroute=*) - value=${1#--defaultroute=} - if enabled value; then - DEFAULTROUTE=1 - else - DEFAULTROUTE=0 - fi + MTU=$(cli_get_val ${1}) + assert isinteger ${MTU} ;; - --dns=*) - value=${1#--dns=} - if enabled value; then - PEERDNS=1 - else - PEERDNS=0 - fi + --password=*) + PASSWORD=$(cli_get_val ${1}) ;; - --auth=*) - AUTH=${1#--auth=} + --phone-number=*) + PHONE_NUMBER=$(cli_get_val ${1}) ;; - --device=*) - DEVICE=${1#--device=} + --pin=*) + PIN=$(cli_get_val ${1}) ;; - --baudrate=*) - BAUDRATE=${1#--baudrate=} + --username=*) + USERNAME=$(cli_get_val ${1}) ;; *) - echo "Unknown option: $1" >&2 + echo "Unknown argument: ${1}" >&2 exit ${EXIT_ERROR} ;; esac @@ -110,78 +130,56 @@ function _parse_cmdline() { function _up() { local zone=${1} - shift - assert isset zone + # Load configuration file. zone_config_read ${zone} - assert [ -e "/dev/${DEVICE}" ] - - # Creating necessary files - # XXX must be PPP_RUN - [ -d "${RED_RUN}/${LINKNAME}" ] || mkdir -p ${RED_RUN}/${LINKNAME} - - ppp_secret "${USER}" "${SECRET}" - - cat <${RED_RUN}/${LINKNAME}/options -# Naming options -ifname ${zone} -name ${LINKNAME} -linkname ${LINKNAME} - -# Device configuration -/dev/${DEVICE} ${BAUDRATE} -connect "/usr/sbin/chat -v -f /etc/ppp/dialer" -lock -modem -crtscts - -# User configuration -user ${USER} - -$(enabled PEERDNS && echo "usepeerdns") -$(enabled DEFAULTROUTE && echo "defaultroute") + # If we have got a PIN, we try to unlock the device first. + if isset PIN; then + modem_sim_status ${DEVICE} &>/dev/null + local sim_status_code=$? -noauth -$(isset AUTH && echo "require-${AUTH}") - -noipdefault - -# Maximum transmission/receive unit -mtu ${MTU} -mru ${MTU} - -# Disable the compression -noccp noaccomp nodeflate nopcomp novj novjccomp nobsdcomp nomppe - -updetach debug -EOF - - pppd_exec file ${RED_RUN}/${LINKNAME}/options - - local ret=$? + case "${sim_status_code}" in + ${EXIT_SIM_READY}) + # Everything's fine. The SIM card is + # already unlocked. + ;; + ${EXIT_SIM_PIN}) + # Try to unlock the device. + if ! modem_sim_unlock ${DEVICE} ${PIN}; then + # Reset the PIN setting. + PIN="" + config_write $(zone_dir ${zone})/settings ${HOOK_SETTINGS} + + error "Could not unlock the SIM card. Removing PIN from settings." + exit ${EXIT_ERROR} + fi + ;; + ${EXIT_SIM_PUK}) + error "SIM card is PUK locked. Please unlock manually." + exit ${EXIT_ERROR} + ;; + esac - # Get exit code from ppp daemon and handle it: - case "${ret}" in - 0) - log DEBUG "pppd detached successfully" - exit ${EXIT_OK} - ;; - esac + # For mobile devices, check if a PIN is required although none is set. + elif modem_is_mobile ${DEVICE} && modem_sim_locked ${DEVICE}; then + error "The SIM card is locked. Please configure the PIN code." + exit ${EXIT_ERROR} + fi - error_log "pppd exited with unknown exit code '${ret}'" + # Start the PPP daemon. + pppd_start ${zone} - exit ${EXIT_ERROR} + exit ${EXIT_OK} } function _down() { local zone=${1} - shift + assert isset zone - # Kill pppd - # XXX very ugly - kill $(pppd_pid ${zone}) &>/dev/null + # Stop the PPP daemon. + pppd_start ${zone} exit ${EXIT_OK} } @@ -194,19 +192,19 @@ function _status() { zone_config_read ${zone} - cli_headline " Configuration:" - printf "${DEVICE_PRINT_LINE1}" "User:" "${USER}" - printf "${DEVICE_PRINT_LINE1}" "Secret:" "" - echo - printf "${DEVICE_PRINT_LINE1}" "MTU:" "${MTU}" - printf "${DEVICE_PRINT_LINE1}" "Use default route?" "$(enabled DEFAULTROUTE && echo "enabled" || echo "disabled")" - printf "${DEVICE_PRINT_LINE1}" "Use peer DNS?" "$(enabled PEERDNS && echo "enabled" || echo "disabled")" - echo - cli_headline " Ports:" - zone_ports_status ${zone} - if [ -z "$(zone_get_ports ${zone})" ]; then - echo -e " ${COLOUR_WARN}No ports attached. Won't be able to start.${COLOUR_NORMAL}" + cli_headline 2 "Configuration" + cli_print_fmt1 2 "Username" "${USERNAME}" + cli_print_fmt1 2 "Password" "" + cli_space + + cli_headline 2 "Device settings" + cli_print_fmt1 2 "Device" "${DEVICE}" + if isset MONITOR_DEVICE; then + cli_print_fmt1 2 "Monitor device" "${MONITOR_DEVICE}" fi + cli_print_fmt1 2 "Baudrate" "${BAUDRATE}" + cli_print_fmt1 2 "MTU/MRU" "${MTU}" + cli_space # Exit if zone is down if ! zone_is_up ${zone}; then @@ -214,16 +212,90 @@ function _status() { exit ${EXIT_ERROR} fi + cli_headline 2 "Carrier network" + + # If the device and the monitor device are both locked, + # we cannot show any carrier information. + local device dev + for dev in ${DEVICE} ${MONITOR_DEVICE}; do + if ! serial_exists ${dev}; then + continue + fi + if serial_is_locked ${dev}; then + continue + fi + + device=${dev} + done + + if isset device; then + cli_print_fmt1 2 "Operator" \ + "$(modem_get_network_operator ${device})" + cli_print_fmt1 2 "SIM IMSI" \ + "$(modem_get_sim_imsi ${device})" + cli_print_fmt1 2 "Mode" \ + "$(modem_get_network_mode ${device})" + cli_print_fmt1 2 "Signal strength" \ + "$(modem_get_signal_quality ${device}) dBm" + local ber=$(modem_get_bit_error_rate ${device}) + isset ber || ber="unknown" + cli_print_fmt1 2 "Bit error rate" "${ber}" + else + cli_print 2 "Device is locked." + fi + cli_space + # XXX display time since connection started - cli_headline " Point-to-Point-over-Ethernet protocol:" - echo " IP-Address : $(routing_db_get ${zone} local-ip-address)" - echo " Gateway : $(routing_db_get ${zone} remote-ip-address)" - echo " DNS-Server : $(routing_db_get ${zone} dns)" - echo - echo " MAC-Remote : $(routing_db_get ${zone} remote-address)" - echo - echo " MTU : $(device_get_mtu ${zone})" - echo # Empty line + cli_headline 2 "Point-to-Point-over-Ethernet protocol" + local proto + for proto in ${IP_SUPPORTED_PROTOCOLS}; do + routing_db_exists ${zone} ${proto} || continue + + local headline + case "${proto}" in + ipv6) + headline="Internet Protocol Version 6" + ;; + ipv4) + headline="Internet Protocol Version 4" + ;; + *) + headline="Unkown protocol" + ;; + esac + cli_headline 3 "${headline}" + + cli_print_fmt1 3 "IP address" "$(routing_db_get ${zone} ${proto} local-ip-address)" + cli_print_fmt1 3 "Gateway" "$(routing_db_get ${zone} ${proto} remote-ip-address)" + cli_print_fmt1 3 "DNS servers" "$(routing_db_get ${zone} ${proto} dns)" + cli_space + done + + exit ${EXIT_OK} +} + +function _ppp_write_config() { + local zone=${1} + assert isset zone + + local file=${2} + assert isset file + + # Read in the configuration files. + zone_config_read ${zone} + + pppd_write_config ${file} \ + --interface="${zone}" \ + --username="${USERNAME}" \ + --password="${PASSWORD}" \ + --mtu="${MTU}" \ + --auth="${AUTH}" \ + \ + --serial="true" \ + --serial-device="${DEVICE}" \ + --baudrate="${BAUDRATE}" \ + --connect-command="/usr/lib/network/dialer ${zone}" + exit ${EXIT_OK} } diff --git a/hooks/zones/pppoe b/hooks/zones/pppoe index a219bfdc..dd44fa61 100755 --- a/hooks/zones/pppoe +++ b/hooks/zones/pppoe @@ -215,9 +215,6 @@ function _ppp_write_config() { # Read in the configuration files. zone_config_read ${zone} - # Set the user credentials. - ppp_secret "${USERNAME}" "${PASSWORD}" - # Prepare the command line options for the pppoe plugin. local plugin_options @@ -236,12 +233,13 @@ function _ppp_write_config() { pppd_write_config ${file} \ --interface="${zone}" \ - --user="${USERNAME}" \ + --username="${USERNAME}" \ + --password="${PASSWORD}" \ --mtu="${MTU}" \ --auth="${AUTH}" \ \ --plugin="${PPPOE_PLUGIN}" \ --plugin-options="${plugin_options}" - return ${EXIT_OK} + exit ${EXIT_OK} } diff --git a/man/network-device.8.in b/man/network-device.8.in index bb65e00b..3e007562 100644 --- a/man/network-device.8.in +++ b/man/network-device.8.in @@ -4,7 +4,7 @@ network-device \- Network Configuration Control Program .SH SYNOPSIS -\fBnetwork [OPTIONS] device [status|discover] ...\fR +\fBnetwork [OPTIONS] device [status|discover|unlock] ...\fR .SH DESCRIPTION By the device subcommands, it is very easy to get status information @@ -30,6 +30,15 @@ for what the device should be used. .RE .PP +\fB unlock\fR +.RS 4 +This command will unlock the SIM card in a modem. Only serial devices +are supported which are the most UMTS or 3G modems. +.PP +For the PIN or PUK code, the user will be prompted. +.RE +.PP + .SH SEE ALSO network(8) diff --git a/network b/network index 808d2cd6..ef2064e7 100755 --- a/network +++ b/network @@ -50,6 +50,11 @@ function cli_config() { } function cli_device() { + if cli_help_requested $@; then + cli_show_man network-device + exit ${EXIT_OK} + fi + local device=${1} local action=${2} shift 2 @@ -68,6 +73,9 @@ function cli_device() { status) cli_device_status ${device} ;; + unlock) + cli_device_serial_unlock ${device} $@ + ;; *) cli_show_man network-device ;; @@ -80,12 +88,22 @@ function cli_device_status() { local device=${1} assert device_exists ${device} + # Disable debugging output here. + local log_disable_stdout=${LOG_DISABLE_STDOUT} + LOG_DISABLE_STDOUT="true" + # Save the type of the device for later. local type=$(device_get_type ${device}) cli_headline 1 "Device status: ${device}" cli_print_fmt1 1 "Name" "${device}" + # Handle serial devices. + if [ "${type}" = "serial" ]; then + cli_device_status_serial ${device} + return $? + fi + # Print the device status. device_is_up ${device} &>/dev/null local status=$? @@ -138,6 +156,69 @@ function cli_device_status() { cli_space fi + # Reset the logging level. + LOG_DISABLE_STDOUT=${log_disable_stdout} +} + +function cli_device_status_serial() { + local device=${1} + assert device_is_serial ${device} + + serial_is_locked ${device} &>/dev/null + local locked=$? + + cli_print_fmt1 1 "Locked" "$(cli_print_bool ${locked})" + cli_space + + # Cannot go on when the device is locked. + [ ${locked} -eq ${EXIT_TRUE} ] && return ${EXIT_OK} + + cli_print_fmt1 1 "Manufacturer" \ + "$(modem_get_manufacturer ${device})" + cli_print_fmt1 1 "Model" \ + "$(modem_get_model ${device})" + cli_print_fmt1 1 "Software version" \ + "$(modem_get_software_version ${device})" + + if modem_is_mobile ${device}; then + cli_print_fmt1 1 "IMEI" \ + "$(modem_get_device_imei ${device})" + cli_space + + cli_headline 2 "Network status" + modem_sim_status ${device} &>/dev/null + local sim_status_code=$? + + local sim_status="unknown" + case "${sim_status_code}" in + ${EXIT_SIM_READY}) + sim_status="SIM ready" + ;; + ${EXIT_SIM_PIN}) + sim_status="PIN locked" + ;; + ${EXIT_SIM_PUK}) + sim_status="PUK locked" + ;; + esac + cli_print_fmt1 2 "SIM status" "${sim_status}" + + if [ ${sim_status_code} -eq ${EXIT_SIM_READY} ]; then + cli_print_fmt1 2 "IMSI" \ + "$(modem_get_sim_imsi ${device})" + cli_print_fmt1 2 "Operator" \ + "$(modem_get_network_operator ${device})" + cli_print_fmt1 2 "Mode" \ + "$(modem_get_network_mode ${device})" + cli_print_fmt1 2 "Signal quality" \ + "$(modem_get_signal_quality ${device}) dBm" + + local ber=$(modem_get_bit_error_rate ${device}) + isset ber || ber="unknown" + cli_print_fmt1 2 "Bit Error Rate" "${ber}" + fi + fi + cli_space } function cli_device_discover() { @@ -211,6 +292,92 @@ function cli_device_discover() { [ "${up}" = "1" ] || device_set_down ${device} } +function cli_device_serial_unlock() { + if cli_help_requested $@; then + cli_show_man network-device + exit ${EXIT_OK} + fi + + local device=${1} + assert isset device + + if ! device_is_serial ${device}; then + error "${device} is not a serial device." + error "Unlocking is only supported for serial devices." + exit ${EXIT_ERROR} + fi + + # Read the current state of the SIM card. + modem_sim_status ${device} &>/dev/null + local sim_status_code=$? + + # If the SIM card is already unlocked, we don't need to do anything. + if [ ${sim_status_code} -eq ${EXIT_SIM_READY} ]; then + print "The SIM card is already unlocked." + exit ${EXIT_OK} + + # If the SIM card is in an unknown state, we cannot do anything. + elif [ ${sim_status_code} -eq ${EXIT_SIM_UNKNOWN} ]; then + error "The SIM card is in an unknown state." + exit ${EXIT_ERROR} + fi + + # Ask for the code. + local code=${2} + local require_new_pin="false" + local new_pin + + while ! isinteger code; do + local message + case "${sim_status_code}" in + ${EXIT_SIM_PIN}) + message="Please enter PIN:" + ;; + ${EXIT_SIM_PUK}) + message="Please enter PUK:" + require_new_pin="true" + ;; + esac + assert isset message + + echo -n "${message} " + read -s code + echo # Print newline. + + if enabled require_new_pin; then + local i new_pin2 + for i in 0 1; do + case "${i}" in + 0) + message="Please enter a new PIN code:" + ;; + 1) + message="Please confirm the new PIN code:" + ;; + esac + + echo -n "${message} " + read -s new_pin2 + echo # Print newline. + + if [ -n "${new_pin}" ]; then + if [ "${new_pin}" != "${new_pin2}" ]; then + error "The entered PIN codes did not match." + exit ${EXIT_ERROR} + fi + else + new_pin=${new_pin2} + fi + done + fi + done + + # Trying to unlock the SIM card. + modem_sim_unlock ${device} ${code} ${new_pin} + + exit $? +} + function cli_hostname() { if cli_help_requested $@; then cli_show_man network diff --git a/ppp/dialer b/ppp/dialer index 1ccd5fb8..ccec5a27 100644 --- a/ppp/dialer +++ b/ppp/dialer @@ -1,11 +1,124 @@ -TIMEOUT 5 -REPORT CONNECT -'' '+++ATZ' -'' 'AT' -'' '+++ATZ' -OK 'ATH' -'' 'AT+CGDCONT=1,"IP","event.vodafone.de"' -'' 'sleep 5' -TIMEOUT 30 -OK 'ATDT*99#' -CONNECT '' +#!/bin/bash +############################################################################### +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2012 IPFire Network Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +############################################################################### + +LOG_FACILITY=$(basename ${0}) + +# Do not print any log messages because these may be sent to the modem. +LOG_DISABLE_STDOUT="true" + +. /usr/lib/network/functions + +log DEBUG "dialer called with arguments: $@" + +# The zone is an optional argument. +ZONE=${1} +assert isset ZONE + +# If we have the zone information, we will +# load the zone configuration. +if zone_exists ${ZONE}; then + zone_config_read ${ZONE} +fi + +# The default speaker settings is on. +at_speaker=${AT_SPEAKER_ON} + +# The default dial method is tone dial. +at_dial=${AT_TONE_DIAL} + +# Initalize the commandline +commandline="" + +# If we are running in debug mode, we start chat with the +# verbose flag as well. +if enabled DEBUG; then + commandline="${commandline} -v" +fi + +# Create a temporary chat script file. +file=$(mktemp) +commandline="${commandline} -f ${file}" + +# Helper function to write beatiful lines to +# the chat scripts. +function println() { + printf "%-30s %s\n" "$@" >> ${file} +} + +### Write the connect script. + +# Set the timeout value for the configuration commands to +# 3 seconds. This will be increased later. +println "TIMEOUT" 3 + +# Let's log everything until we are properly connected. +println "REPORT" "CONNECT" + +# End the connection, when one of the following conditions +# happens: +for condition in "BUSY" "NO ANSWER" "NO CARRIER" "NO DIALTONE"; do + println "ABORT" "'${condition}'" +done + +# Now, we get to the exciting stuff. +# Initalize the modem. +println "''" "${AT_INITIALIZE}" +println "''" "AT" +println "''" "${AT_INITIALIZE}" + +# End all left over connections by hanging up. +println "OK" "${AT_HANGUP}" + +# Apply the speaker setting. +println "OK" "${at_speaker}" + +# Set the APN if any. +if isset APN; then + println "''" "'AT+CGDCONT=1,\"IP\",\"${APN}\"'" +fi + +# Enter a 5 seconds break so the modem can setup itself +# to the settings we just transmitted to it. +for i in $(seq 0 5); do + println "''" "\\d" +done + +# Reset the timeout value to 30 seconds. +println "TIMEOUT" 30 + +# Actually dial the number. +println "OK" "${at_dial}${PHONE_NUMBER}" + +# Wait for the CONNECT string. +println "CONNECT" "\\c" + +# If login credentials were set, we send them. +if isset USERNAME && isset PASSWORD; then + println "ogin:--ogin:" "${USERNAME}" + println "assword:" "${PASSWORD}" +fi + +# Exec the chat command which will start talking to the modem. +log DEBUG "Exec'ing chat with command line: ${commandline}" +exec chat ${commandline} + +error "Could not execute chat. Exiting." +exit ${EXIT_ERROR} diff --git a/ppp/ip-updown b/ppp/ip-updown index 3d2cfc5b..656fa4b0 100755 --- a/ppp/ip-updown +++ b/ppp/ip-updown @@ -19,25 +19,34 @@ # # ############################################################################### +LOG_FACILITY=$(basename ${0}) + umask 022 +PPP_VARIABLES="IFNAME IPLOCAL IPREMOTE DNS1 DNS2 MACREMOTE LLLOCAL LLREMOTE" + # Give the variables we get passed by pppd an own namespace -for i in IFNAME IPLOCAL IPREMOTE DNS1 DNS2 MACREMOTE LLLOCAL LLREMOTE; do +for i in ${PPP_VARIABLES}; do export PPP_${i}=${!i} unset ${i} done . /usr/lib/network/functions +log DEBUG "Called." +for i in ${PPP_VARIABLES}; do + i="PPP_${i}" + log DEBUG " ${i} = ${!i}" +done + # Zone equals IFNAME ZONE=${PPP_IFNAME} -assert isset ZONE # If the given device is a known zone, we will call the required # hook methods. If we don't know about any zone with name ${ZONE}, # we do nothing. -if zone_exists ${ZONE}; then +if isset ZONE && zone_exists ${ZONE}; then HOOK=$(zone_get_hook ${ZONE}) assert isset HOOK @@ -50,7 +59,7 @@ if zone_exists ${ZONE}; then log DEBUG " $@" hook_zone_exec ${HOOK} ppp-${PROGNAME} ${ZONE} - local ret=$? + ret=$? exit ${ret} fi