This patch adds support for QMI to netifd. It enables netifd to access the network functions of several 2G/3G/4G sticks. For usage of qmi, the device has to support 2 functions: -cdc-wdm interface for control -wwan interface for data
Throuput should also be a lot better than through serial and pppd. To use this, you should have a stick like a Huawei E392. It is configured through /etc/config/network. Example: config interface 'broadband' option ifname 'wwan0' option proto 'qmi' option pincode '1234' option apn 'web.vodafone.de' option username 'user' option password 'password' # if you want a sepearate LTE APN to be used, set option lte_apn_use '1' option lte_apn 'web.vodafone.de' option lte_username 'user' option lte_password 'password' Stability is watched via a small watchdog, which does arp to check if modems still responds. To be able to use username and password authentication, latest libqmi is needed. Because I have no IPv6 enabled cellphone provider, this is not tested. Any ideas are welcome! Thanks to Aleksander Morgado for his great work! Signed-off-by: André Valentin <avalen...@marcant.net> --- diff -uNrp net/netifd-qmi/files/lib/netifd/dhcp-qmi.script net/netifd-qmi/files/lib/netifd/dhcp-qmi.script --- net/netifd-qmi/files/lib/netifd/dhcp-qmi.script 1970-01-01 01:00:00.000000000 +0100 +++ net/netifd-qmi/files/lib/netifd/dhcp-qmi.script 2013-01-08 15:32:29.000000000 +0100 @@ -0,0 +1,50 @@ +#!/bin/sh +[ -z "$1" ] && echo "Error: should be run by udhcpc" && exit 1 + +. /lib/functions.sh +. /lib/netifd/netifd-proto.sh + +set_classless_routes() { + local max=128 + local type + while [ -n "$1" -a -n "$2" -a $max -gt 0 ]; do + proto_add_ipv4_route "${1%%/*}" "${1##*/}" "$2" + max=$(($max-1)) + shift 2 + done +} + +setup_interface () { + proto_init_update "*" 1 + subnet=255.255.255.255 + proto_add_ipv4_address "$ip" "${subnet:-255.255.255.0}" 255.255.255.255 "$router" + proto_add_ipv4_route 0.0.0.0 0 "$router" + + # CIDR STATIC ROUTES (rfc3442) + [ -n "$staticroutes" ] && set_classless_routes $staticroutes + [ -n "$msstaticroutes" ] && set_classless_routes $msstaticroutes + + for dns in $dns; do + proto_add_dns_server "$dns" + done + for domain in $domain; do + proto_add_dns_search "$domain" + done + proto_send_update "$INTERFACE" +} + +deconfig_interface() { + proto_init_update "*" 0 + proto_send_update "$INTERFACE" +} + +case "$1" in + deconfig) + deconfig_interface + ;; + renew|bound) + setup_interface + ;; +esac + +exit 0 diff -uNrp net/netifd-qmi/files/lib/netifd/proto/qmi.sh net/netifd-qmi/files/lib/netifd/proto/qmi.sh --- net/netifd-qmi/files/lib/netifd/proto/qmi.sh 1970-01-01 01:00:00.000000000 +0100 +++ net/netifd-qmi/files/lib/netifd/proto/qmi.sh 2013-02-28 11:41:14.000000000 +0100 @@ -0,0 +1,307 @@ +#!/bin/sh +# +# 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, version 2. +# +# 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, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Copyright (C) 2012 Aleksander Morgado <aleksan...@gnu.org> +# Copyright (C) 2012 André Valentin / MarcanT GmbH <avalen...@marcant.net> + +. /lib/functions.sh +. ../netifd-proto.sh +init_proto "$@" + +proto_qmi_init_config() { + proto_config_add_string "ipaddr" + proto_config_add_string "netmask" + proto_config_add_string "hostname" + proto_config_add_string "clientid" + proto_config_add_string "vendorid" + proto_config_add_boolean "broadcast" + proto_config_add_string "reqopts" + proto_config_add_string "apn" + proto_config_add_string "username" + proto_config_add_string "password" + proto_config_add_boolean "lte_apn_use" + proto_config_add_string "lte_apn" + proto_config_add_string "lte_username" + proto_config_add_string "lte_password" + proto_config_add_string "pincode" + proto_config_add_string "technology" + proto_config_add_string "auto" +} + +proto_qmi_log() { + local level="$1" + local message="$2" + [ -z "$message" ] && { + logger -p "$1" -t "qmi[$$]" + return; + } + shift + logger -p "$level" -t "qmi[$$]" -- "$@" +} + +proto_qmi_clear_state() { + local $config=$1 + uci_revert_state network $config CID + uci_revert_state network $config PDH + uci_revert_state network $config QMIDEVICE +} + +proto_qmi_start_network() { + local config=$1 + local DEVICE=$2 + local APN="$3" + local CID=$(uci_get_state network $config CID) + local PDH=$(uci_get_state network $config PDH) + + if [ "x$CID" != "x" ]; then + USE_PREVIOUS_CID="--client-cid=$CID" + fi + + if [ "x$PDH" != "x" ]; then + logger -p daemon.info "error: cannot re-start network, PDH already exists" + return 3 + fi + + START_NETWORK_CMD="qmicli -d $DEVICE --wds-start-network=$APN $USE_PREVIOUS_CID --client-no-release-cid" + logger -p daemon.info "Starting network with '$START_NETWORK_CMD'..." + START_NETWORK_OUT=`$START_NETWORK_CMD` + + # Save the new CID if we didn't use any before + if [ "x$CID" = "x" ]; then + CID=`echo "$START_NETWORK_OUT" | grep CID | sed "s/'//g" | awk 'BEGIN { FS = ": " } ; { print $2 }'` + if [ "x$CID" = "x" ]; then + logger -p daemon.info "error: network start failed, client not allocated" + return 1 + else + uci_set_state network $config CID $CID + fi + fi + + PDH=`echo "$START_NETWORK_OUT" | grep handle | sed "s/'//g" | awk 'BEGIN { FS = ": " } ; { print $2 }'` + if [ "x$PDH" = "x" ]; then + proto_qmi_log daemon.info "error: network start failed, no packet data handle" + # Cleanup the client + qmicli -d "$DEVICE" --wds-noop --client-cid="$CID" + uci_revert_state network $config CID + uci_revert_state network $config PDH + uci_revert_state network $config QMIDEVICE + CID="" + PDH="" + return 2 + else + uci_set_state network $config PDH "$PDH" + fi + uci_set_state network $config QMIDEVICE "$DEVICE" + proto_qmi_log daemon.info "Network started successfully: CID: $CID, PDH=$PDH, DEVICE=$DEVICE" +} + +proto_qmi_stop_network () { + local config=$1 + local CID=$(uci_get_state network $config CID) + local PDH=$(uci_get_state network $config PDH) + local DEVICE=$(uci_get_state network $config QMIDEVICE) + + if [ "x$CID" = "x" ]; then + proto_qmi_log daemon.info "Network already stopped" + elif [ "x$PDH" = "x" ]; then + proto_qmi_log daemon.info "Network already stopped; need to cleanup CID $CID" + # Cleanup the client + qmicli -d "$DEVICE" --wds-noop --client-cid="$CID" + else + STOP_NETWORK_CMD="qmicli -d $DEVICE --wds-stop-network=$PDH --client-cid=$CID" + proto_qmi_log daemon.info "Stopping network with '$STOP_NETWORK_CMD'..." + STOP_NETWORK_OUT=`$STOP_NETWORK_CMD` + proto_qmi_log daemon.info "Network stopped successfully" + fi + uci_revert_state network $config CID + uci_revert_state network $config PDH + uci_revert_state network $config QMIDEVICE +} + +proto_qmi_packet_service_status () { + local config=$1 + local CID=$(uci_get_state network $config CID) + local PDH=$(uci_get_state network $config PDH) + local DEVICE=$(uci_get_state network $config QMIDEVICE) + + if [ "x$CID" != "x" ]; then + USE_PREVIOUS_CID="--client-cid=$CID --client-no-release-cid" + fi + + STATUS_CMD="qmicli -d $DEVICE --wds-get-packet-service-status $USE_PREVIOUS_CID" + + STATUS_OUT=`$STATUS_CMD` + + CONN=`echo "$STATUS_OUT" | grep "Connection status" | sed "s/'//g" | awk 'BEGIN { FS = ": " } ; { print $2 }'` + if [ "x$CONN" = "x" ]; then + proto_qmi_log daemon.info "error: couldn't get packet service status" + return 2 + else + if [ "x$CONN" != "xconnected" ]; then + proto_qmi_log daemon.debug "Status: $CONN" + return 64 + fi + fi +} + +proto_qmi_setup() { + local config="$1" + local iface="$2" + local ipaddr hostname clientid vendorid broadcast reqopts apn username password pincode auto lte_apn_use lte_apn lte_username lte_password + json_get_vars ipaddr hostname clientid vendorid broadcast reqopts apn username password pincode auto data lte_apn_use lte_apn lte_username lte_password + + # Load technology list + config_load network + config_get technology $config technology + + # Setup APN config + apn_standard="${apn}" + [ -n "${username}" ] && { + apn_standard="${apn_standard},both,${username}" + } + [ -n "${password}" ] && { + apn_standard="${apn_standard},${password}" + } + apn_lte="${lte_apn}" + [ -n "${lte_username}" ] && { + apn_lte="${apn_lte},both,${lte_username}" + } + [ -n "${password}" ] && { + apn_lte="${apn_lte},${lte_password}" + } + + # Reset auto state if forced by technology selection + uci_revert_state network $config auto + + local CDCDEV + CDCDEV=/dev/$(basename $(ls /sys/class/net/${iface}/device/usbmisc/cdc-wdm* -d)) || { + CDCDEV="$device" + proto_qmi_log daemon.err "Control device not found, using network.${config}.device: $CDCDEV" + return 1 + } + proto_qmi_log daemon.info "Wan Device: ${iface}, Control CDC: ${CDCDEV}, APN: $apn_standard, LTE APN: $apn_lte, Pincode: $pincode" + if [ -z "$CDCDEV" ]; then + proto_qmi_log daemon.err "Device $CDCDEV is empty" + fi + while ! [ -c "$CDCDEV" ]; do + proto_qmi_log daemon.debug "Waiting for device creation: ${CDCDEV}" + sleep 2 + done + # Just in case there is still context data + proto_qmi_stop_network ${config} + + # Check PIN + [ -n "$pincode" ] && { + set -o pipefail + if ! qmicli -d $CDCDEV "--dms-uim-verify-pin=PIN,${pincode}" 2>&1 | proto_qmi_log daemon.info; then + qmicli -d $CDCDEV --dms-uim-get-pin-status | proto_qmi_log daemon.info + proto_qmi_log daemon.info "PIN Verification failed, shutting down and block restart." + proto_notify_error "$config" PIN_FAILED + proto_block_restart "$interface" + return 1 + fi + } + # Print info about system selection for debugging purpose + qmicli -d $CDCDEV --nas-get-system-selection-preference 2>&1 | proto_qmi_log daemon.debug + + # Wait for registration + while ! qmicli -d $CDCDEV --nas-get-serving-system|grep 'Registration state'|grep "'registered'" > /dev/null; do + sleep 1; + proto_qmi_log daemon.info "Waiting for registration" + done + + # Print current network info + qmicli -d $CDCDEV --nas-get-serving-system 2>&1 | proto_qmi_log daemon.debug + + # Select APN + if qmicli -d $CDCDEV --nas-get-serving-system | grep -q "'lte'" > /dev/null && [ "${lte_apn_use}" = "1" ]; then + current_apn="$apn_lte" + else + current_apn="$apn_standard" + fi + + # Try to start network + set -o pipefail + while ! proto_qmi_start_network ${config} $CDCDEV "${current_apn}" 2>&1 | proto_qmi_log daemon.info; do + sleep 5 + done + + # Show status and start watchdog + qmicli -d $CDCDEV --nas-get-serving-system 2>&1 | proto_qmi_log daemon.debug + ( + set -o pipefail + let counter=0 + while sleep 20; do + proto_qmi_packet_service_status ${config} + STATUS=$? + [ "$STATUS" -gt 0 ] && { + proto_qmi_log daemon.err "QMI Status shows error ${STATUS}, shutting down ${config}, control device $CDCDEV" + (ifdown $config; sleep 30; ifup $config) </dev/null > /dev/null 2>&1 & + exit 1 + } + if ! router=$(ip neigh show dev ${iface} | cut -d ' ' -f1 ); then + proto_qmi_log daemon.info "Neighbor not found" + continue + fi + if ! arping -q -c1 -w 5 -I ${iface} $(ip neigh show dev ${iface}|cut -d' ' -f1); then + let counter=${counter}+1 + proto_qmi_log daemon.err "Arping gateway timed out, Error counter: $counter" + fi + [ "$counter" -gt 5 ] && { + proto_qmi_log daemon.err "Error counter to high: $counter, shutting down ${config}, control device ${CDCDEV}" + (ifdown $config; sleep 5; sleep 30; ifup $config) </dev/null > /dev/null 2>&1 & + exit 1 + } + done + ) </dev/null > /dev/null 2>&1 & + local watchdog_pid=$! + echo $watchdog_pid > /var/run/qmi-watchdog-${config}.pid + proto_qmi_log daemon.info "Started watchdog pid $watchdog_pid" + + local opt dhcpopts + for opt in $reqopts; do + append dhcpopts "-O $opt" + done + + [ "$broadcast" = 1 ] && broadcast="-B" || broadcast= + [ -n "$clientid" ] && clientid="-x 0x3d:${clientid//:/}" || clientid="-C" + + proto_export "INTERFACE=$config" + proto_run_command "$config" udhcpc \ + -p /var/run/udhcpc-$iface.pid \ + -s /lib/netifd/dhcp-qmi.script \ + -f -t 0 -i "$iface" \ + -x lease:60 \ + ${ipaddr:+-r $ipaddr} \ + ${hostname:+-H $hostname} \ + ${vendorid:+-V $vendorid} \ + $clientid $broadcast $dhcpopts +} + +proto_qmi_teardown() { + local interface="$1" + local iface="$2" + local CID=$(uci_get_state network $interface CID) + local DEVICE=$(uci_get_state network $interface QMIDEVICE) + [ -e /var/run/qmi-watchdog-${interface}.pid ] && { + kill $(cat /var/run/qmi-watchdog-${interface}.pid) + rm /var/run/qmi-watchdog-${interface}.pid + } + proto_qmi_stop_network ${interface} + proto_kill_command "$interface" + echo "$interface done" +} + +add_protocol qmi diff -uNrp net/netifd-qmi/Makefile net/netifd-qmi/Makefile --- net/netifd-qmi/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ net/netifd-qmi/Makefile 2013-02-28 11:35:01.000000000 +0100 @@ -0,0 +1,39 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=netifd-qmi +PKG_VERSION:=1.0 +PKG_RELEASE:=1 + +PKG_MAINTAINER:=André Valentin <avalen...@marcant.net> + +include $(INCLUDE_DIR)/package.mk + +define Package/netifd-qmi + SECTION:=utils + CATEGORY:=Network + DEPENDS:=+libqmi +iputils-arping + TITLE:=QMI support for netifd +endef + +define Package/netifd-qmi/description + Helper scripts to enable netifd to manage qmi interfaces +endef + +define Build/Prepare +endef + +define Build/Configure +endef + +define Build/Compile +endef + +define Package/netifd-qmi/install + $(INSTALL_DIR) $(1)/lib + $(INSTALL_DIR) $(1)/lib/netifd + $(INSTALL_DIR) $(1)/lib/netifd/proto + $(INSTALL_BIN) ./files/lib/netifd/dhcp-qmi.script $(1)/lib/netifd/ + $(INSTALL_BIN) ./files/lib/netifd/proto/qmi.sh $(1)/lib/netifd/proto/ +endef + +$(eval $(call BuildPackage,netifd-qmi)) _______________________________________________ openwrt-devel mailing list openwrt-devel@lists.openwrt.org https://lists.openwrt.org/mailman/listinfo/openwrt-devel