#!/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, either version 2 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, write to the Free Software Foundation, Inc., 51 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # Copyright (C) 2012-2017 Aleksander Morgado print_usage () { echo "usage: $0 [OPTIONS] [DEVICE] [COMMAND]" } help () { echo "Usage: qmi-network [OPTIONS] [DEVICE] [COMMAND]" echo echo "Simple network management of QMI devices" echo echo "Commands:" echo " start Start network connection" echo " stop Stop network connection" echo " status Query network connection status" echo echo "Options:" echo " --profile=[PATH] Use the profile in the specified path" echo " --help Show help options" echo " --version Show version" echo echo "Notes:" echo echo " 1) [DEVICE] is given as the full path to the cdc-wdm character" echo " device, e.g.:" echo " /dev/cdc-wdm0" echo echo " 2) The qmi-network script requires a profile to work. Unless" echo " explicitly specified with \`--profile', the file is assumed to" echo " be available in the following path:" echo " /etc/qmi-network.conf" echo echo " 3) The APN to use should be configured in the profile, in the" echo " following way (e.g. assuming APN is called 'internet'):" echo " APN=internet" echo echo " 4) Optional APN user/password strings may be given in the following" echo " way:" echo " APN_USER=user" echo " APN_PASS=password" echo echo " 5) Optional IP_TYPE strings may be given in the following" echo " way:" echo " IP_TYPE=[4|6]" echo echo " 6) If you want to instruct the qmi-network script to use the" echo " qmi-proxy setup, you can do so by configuring the following line" echo " in the profile:" echo " PROXY=yes" echo echo " 7) To instruct the qmi-network script to use an already existing" echo " Profile ID, or to create a new one on the fly, use the following" echo " configuration in the profile:" echo " PROFILE=number # to use the profile-id number" echo " PROFILE=auto # to create a temporary profile on the fly" echo echo " 8) Once the qmi-network script reports a successful connection" echo " you still need to run a DHCP client on the associated WWAN network" echo " interface." echo } version () { echo "qmi-network @VERSION@" echo "Copyright (C) 2013-2021 Aleksander Morgado" echo "License GPLv2+: GNU GPL version 2 or later " echo "This is free software: you are free to change and redistribute it." echo "There is NO WARRANTY, to the extent permitted by law." echo } # Basic options if [ $# -lt 2 ]; then if [ "$1" = "--help" ]; then help exit 0 elif [ "$1" = "--version" ]; then version exit 0 fi echo "error: missing arguments" 1>&2 print_usage exit 255 fi # Defaults PROFILE_FILE=/etc/qmi-network.conf # Device + Command with options; options given first while [ $# -gt 2 ]; do OPT="$1" shift case "$OPT" in "--") break 2;; "--profile") if [ $# -gt 2 ]; then PROFILE_FILE="$1" shift else PROFILE_FILE="" fi ;; "--profile="*) PROFILE_FILE="${OPT#*=}";; *) echo >&2 "Invalid option: $OPT" print_usage exit 255;; esac done if [ -z "$PROFILE_FILE" ]; then echo "error: empty profile path given" 1>&2 print_usage exit 255 fi if [ $# -ne 2 ] || [ "$1" = '--*' ] || [ "$2" = '--*' ]; then echo "error: missing arguments" 1>&2 print_usage exit 255 fi DEVICE=$1 COMMAND=$2 STATE_FILE=/tmp/qmi-network-state-`basename $DEVICE` load_profile () { if [ -f "$PROFILE_FILE" ]; then echo "Loading profile at ${PROFILE_FILE}..." . $PROFILE_FILE if [ -n "$APN" ]; then echo " APN: $APN" else echo " APN: unset" fi if [ -n "$APN_USER" ]; then echo " APN user: $APN_USER" else echo " APN user: unset" fi if [ -n "$APN_PASS" ]; then echo " APN password: $APN_PASS" else echo " APN password: unset" fi if [ "$PROXY" = "yes" ]; then echo " qmi-proxy: $PROXY" PROXY_OPT='--device-open-proxy' else echo " qmi-proxy: no" fi if [ -n "$IP_TYPE" ]; then echo " IP_TYPE: $IP_TYPE" else echo " IP_TYPE: unset" fi if [ -n "$PROFILE" ]; then echo " PROFILE: $PROFILE" else echo " PROFILE: unset" fi else echo "Profile at '$PROFILE_FILE' not found..." fi } save_state () { KEY=$1 VAL=$2 echo "Saving state at ${STATE_FILE}... ($KEY: $VAL)" if [ -f "$STATE_FILE" ]; then PREVIOUS=`cat $STATE_FILE` PREVIOUS=`echo "$PREVIOUS" | grep -v $KEY` if [ -n "$PREVIOUS" ]; then echo $PREVIOUS > $STATE_FILE else rm $STATE_FILE fi fi if [ -n "$VAL" ]; then echo "$KEY=\"$VAL\"" >> $STATE_FILE fi } load_state () { if [ -f "$STATE_FILE" ]; then echo "Loading previous state from ${STATE_FILE}..." . $STATE_FILE if [ -n "$CID" ]; then echo " Previous CID: $CID" fi if [ -n "$PDH" ]; then echo " Previous PDH: $PDH" fi fi } clear_state () { echo "Clearing state at ${STATE_FILE}..." rm -f $STATE_FILE } setup_data_format () { RUN_WDA=0 # Read link layer protocol setup in the device DEVICE_DATA_FORMAT_CMD="qmicli -d $DEVICE --wda-get-data-format $PROXY_OPT" echo "Checking data format with '$DEVICE_DATA_FORMAT_CMD'..." if [ -n "$QMIDEBUG" ]; then DEVICE_DATA_FORMAT_OUT="\ [/dev/cdc-wdm1] Successfully got data format QoS flow header: no Link layer protocol: '802-3' Uplink data aggregation protocol: 'disabled' Downlink data aggregation protocol: 'disabled' NDP signature: '0' Uplink data aggregation max size: '0' Downlink data aggregation max size: '0'" else DEVICE_DATA_FORMAT_OUT=`$DEVICE_DATA_FORMAT_CMD` fi DEVICE_LLP=`echo "$DEVICE_DATA_FORMAT_OUT" | sed -n "s/.*Link layer protocol:.*'\(.*\)'.*/\1/p"` if [ -z "$DEVICE_LLP" ]; then echo "Device link layer protocol not retrieved: WDA unsupported" 1>&2 return fi if [ "$DEVICE_LLP" != "802-3" -a "$DEVICE_LLP" != "raw-ip" ]; then echo "Device link layer protocol not retrieved: unexpected value reported: '$DEVICE_LLP'" 1>&2 return fi echo "Device link layer protocol retrieved: $DEVICE_LLP" # Read link layer protocol setup in the kernel EXPECTED_DATA_FORMAT_CMD="qmicli -d $DEVICE --get-expected-data-format" echo "Getting expected data format with '$EXPECTED_DATA_FORMAT_CMD'..." if [ -n "$QMIDEBUG" ]; then EXPECTED_LLP="raw-ip" else EXPECTED_DATA_FORMAT_OUT=`$EXPECTED_DATA_FORMAT_CMD` if [ $? -eq 0 ]; then EXPECTED_LLP=$EXPECTED_DATA_FORMAT_OUT echo "Expected link layer protocol retrieved: $EXPECTED_LLP" else echo "Expected link layer protocol not retrieved: kernel unsupported" 1>&2 EXPECTED_LLP="802-3" # The kernel doesn't support expected data format, so we change the # device data format instead RUN_WDA=1 fi fi if [ "$EXPECTED_LLP" != "802-3" -a "$EXPECTED_LLP" != "raw-ip" ]; then echo "Expected link layer protocol not retrieved: unexpected value reported: '$EXPECTED_LLP'" 1>&2 return fi if [ "$DEVICE_LLP" = "$EXPECTED_LLP" ]; then echo "Device and kernel link layer protocol match: $DEVICE_LLP" return fi if [ $RUN_WDA -eq 1 ]; then DEVICE_DATA_FORMAT_SET_CMD="qmicli -d $DEVICE --wda-set-data-format=$EXPECTED_LLP $PROXY_OPT" echo "Updating device link layer protocol with '$DEVICE_DATA_FORMAT_SET_CMD'..." if [ -n "$QMIDEBUG" ]; then DEVICE_DATA_FORMAT_SET_OUT="\ [/dev/cdc-wdm1] Successfully set data format QoS flow header: no Link layer protocol: '802-3' Uplink data aggregation protocol: 'disabled' Downlink data aggregation protocol: 'disabled' NDP signature: '0' Downlink data aggregation max datagrams: '0' Downlink data aggregation max size: '0'" else DEVICE_DATA_FORMAT_SET_OUT=`$DEVICE_DATA_FORMAT_SET_CMD` fi LLP=`echo "$DEVICE_DATA_FORMAT_SET_OUT" | sed -n "s/.*Link layer protocol:.*'\(.*\)'.*/\1/p"` if [ -z "$LLP" ]; then echo "Error updating Device link layer protocol" 1>&2 else echo "New device link layer protocol retrieved: $LLP" fi else EXPECTED_DATA_FORMAT_SET_CMD="qmicli -d $DEVICE --set-expected-data-format=$DEVICE_LLP" echo "Updating kernel link layer protocol with '$EXPECTED_DATA_FORMAT_SET_CMD'..." EXPECTED_DATA_FORMAT_SET_OUT=`$EXPECTED_DATA_FORMAT_SET_CMD` if [ $? -eq 0 ]; then echo "Kernel link layer protocol updated" else echo "Error updating kernel link layer protocol " 1>&2 fi fi } # qmicli -d /dev/cdc-wdm0 --wds-create-profile --client-no-release-cid create_profile() { if [ -n "$CID" ]; then USE_PREVIOUS_CID="--client-cid=$CID" fi CREATE_PROFILE_ARGS="3gpp" if [ -n "$APN" ]; then CREATE_PROFILE_ARGS="${CREATE_PROFILE_ARGS},apn='$APN'" fi if [ -n "$IP_TYPE" ]; then if [ "$IP_TYPE" -eq 4 ]; then CREATE_PROFILE_ARGS="${CREATE_PROFILE_ARGS},pdp-type='IP'" fi if [ "$IP_TYPE" -eq 6 ]; then CREATE_PROFILE_ARGS="${CREATE_PROFILE_ARGS},pdp-type='IPV6'" fi fi if [ -n "$APN_USER" ]; then CREATE_PROFILE_ARGS="${CREATE_PROFILE_ARGS},username='$APN_USER'" if [ -n "$APN_PASS" ]; then CREATE_PROFILE_ARGS="${CREATE_PROFILE_ARGS},password='$APN_PASS'" fi fi CREATE_PROFILE_CMD="qmicli -d $DEVICE --wds-create-profile=$CREATE_PROFILE_ARGS $USE_PREVIOUS_CID --client-no-release-cid $PROXY_OPT" echo "Creating profile with '$CREATE_PROFILE_CMD'..." CREATE_PROFILE_OUT=`$CREATE_PROFILE_CMD` # Save the profile ID PROFILE_ID=`echo "$CREATE_PROFILE_OUT" | sed -n "s/.*Profile index:\s*'\([0-9]*\)'.*/\1/p"` if [ -z "$PROFILE_ID" ]; then echo "Profile ID creation failed" echo $CREATE_PROFILE_OUT exit 255 fi } delete_profile() { if [ -n "$PROFILE" -a "$PROFILE" = "auto" ]; then if [ -n "$CID" ]; then USE_PREVIOUS_CID="--client-cid=$CID" fi DELETE_PROFILE_CMD="qmicli -d $DEVICE --wds-delete-profile=3gpp,$PROFILE_ID $USE_PREVIOUS_CID --client-no-release-cid $PROXY_OPT" echo "Deleting profile with '$DELETE_PROFILE_CMD'..." $DELETE_PROFILE_CMD fi } # qmicli -d /dev/cdc-wdm0 --wds-start-network --client-no-release-cid # [/dev/cdc-wdm0] Network started # Packet data handle: 3634026241 # [/dev/cdc-wdm0] Client ID not released: # Service: 'wds' # CID: '80' start_network () { if [ -n "$CID" ]; then USE_PREVIOUS_CID="--client-cid=$CID" fi if [ -n "$PDH" ]; then echo "error: cannot re-start network, PDH already exists" 1>&2 exit 3 fi setup_data_format if [ -n "$PROFILE" ]; then if [ "$PROFILE" = "auto" ]; then create_profile else PROFILE_ID="$PROFILE" fi START_NETWORK_ARGS="3gpp-profile=$PROFILE_ID" fi if [ -z "$PROFILE" -a -n "$APN" ]; then START_NETWORK_ARGS="apn='$APN'" if [ -n "$APN_USER" ]; then START_NETWORK_ARGS="${START_NETWORK_ARGS},username='$APN_USER'" if [ -n "$APN_PASS" ]; then START_NETWORK_ARGS="${START_NETWORK_ARGS},password='$APN_PASS'" fi fi if [ -n "$IP_TYPE" ]; then START_NETWORK_ARGS="${START_NETWORK_ARGS},ip-type='$IP_TYPE'" fi fi START_NETWORK_CMD="qmicli -d $DEVICE --wds-start-network=$START_NETWORK_ARGS $USE_PREVIOUS_CID --client-no-release-cid $PROXY_OPT" echo "Starting network with '$START_NETWORK_CMD'..." if [ -n "$QMIDEBUG" ]; then START_NETWORK_OUT="\ [/dev/cdc-wdm0] Network started Packet data handle: '3634026241' [/dev/cdc-wdm0] Client ID not released: Service: 'wds' CID: '80'" else START_NETWORK_OUT=`$START_NETWORK_CMD` fi # Save the new CID if we didn't use any before if [ -z "$CID" ]; then CID=`echo "$START_NETWORK_OUT" | sed -n "s/.*CID.*'\(.*\)'.*/\1/p"` if [ -z "$CID" ]; then echo "error: network start failed, client not allocated" 1>&2 exit 1 else save_state "CID" $CID fi fi PDH=`echo "$START_NETWORK_OUT" | sed -n "s/.*handle.*'\(.*\)'.*/\1/p"` if [ -z "$PDH" ]; then echo "error: network start failed, no packet data handle" 1>&2 # Cleanup the client qmicli -d "$DEVICE" --wds-noop --client-cid="$CID" $PROXY_OPT clear_state exit 2 else save_state "PDH" $PDH fi echo "Network started successfully" if [ "$PROFILE" = "auto" ]; then delete_profile fi } # qmicli -d /dev/cdc-wdm0 --wds-stop-network stop_network () { if [ -z "$CID" ]; then echo "Network already stopped" elif [ -z "$PDH" ]; then echo "Network already stopped; need to cleanup CID $CID" # Cleanup the client qmicli -d "$DEVICE" --wds-noop --client-cid="$CID" $PROXY_OPT else STOP_NETWORK_CMD="qmicli -d $DEVICE --wds-stop-network=$PDH --client-cid=$CID $PROXY_OPT" echo "Stopping network with '$STOP_NETWORK_CMD'..." if [ -n "$QMIDEBUG" ]; then STOP_NETWORK_OUT="\ [/dev/cdc-wdm0] Network stopped " else STOP_NETWORK_OUT=`$STOP_NETWORK_CMD` fi echo "Network stopped successfully" fi clear_state } # qmicli -d /dev/cdc-wdm0 --wds-get-packet-service-status packet_service_status () { if [ -n "$CID" ]; 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 $PROXY_OPT" echo "Getting status with '$STATUS_CMD'..." if [ -n "$QMIDEBUG" ]; then STATUS_OUT="\ [/dev/cdc-wdm0] Connection status: 'disconnected' " else STATUS_OUT=`$STATUS_CMD` fi CONN=`echo "$STATUS_OUT" | sed -n "s/.*Connection status:.*'\(.*\)'.*/\1/p"` if [ -z "$CONN" ]; then echo "error: couldn't get packet service status" 1>&2 exit 2 else echo "Status: $CONN" if [ "$CONN" != "connected" ]; then exit 64 fi fi } # Main # Load profile, if any load_profile # Load previous state, if any load_state # Process commands case $COMMAND in "start") start_network ;; "stop") stop_network ;; "status") packet_service_status ;; *) echo "error: unexpected command '$COMMAND'" 1>&2 print_usage exit 255 ;; esac exit 0