| #!/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 <aleksander@aleksander.es> |
| |
| 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 <http://gnu.org/licenses/gpl-2.0.html>" |
| 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 |