#!/bin/bash # Rules for chat scripts # No comments allowed at the end of AT+CGDCONT in chat script # The last AT+CGDCONT= must use the same context as the dialer. # If desired, The AT+CGDCONT may be prefixed by #MT[[:space:]]+ # Example: #MT AT+CGDCONT="IPV6","data","192.168.2.1",0,1,"EXTRA" # # If you do not use a comment, the entire AT+CGDCONT command # must be surrounded by apostrophes. This command will be executed # twice, once in the chat wrapper, and a 2nd time in the chat # itself. # # The space after "#MT" may be any number including tabs. # If #MT AT+CGDCONT= is found, only the last one is chosen. # Any uncommented AT+CGDCONT= is then ignored. # If there are not #MT AT+CGDCONT= lines, then any line without # a comment chararacter before AT+CGDONT= is accepted, but only the # last one in the file. # # On entry to this script, file descriptor 0 is the modem. # For ppp to work, stty must work on file descriptor 0. # If stty fails, so will ppp. . /usr/libexec/ppp/wait_for_reset NAME=chat_wrapper CONFIG=/etc/default/${NAME} CHAT_WRAPPER_RECURSION=/run/chat_wrapper_recursion RQCMD="/usr/bin/radio-query ${RADIOOPTION}" : ${LOG:="/usr/bin/logger -t ${NAME} -p daemon.notice"} function finish { ${LOG} "Launch:" "$@" exec "$@" # NOTREACHED } # This function is used to determine if the modem and serial driver # are working. In testing, the driver has gotten into states where # stty fails. Before the chat script is started, the ioctl TCGETS is # performed, and will fail if the driver is hung. This same function # is called by stty. The modem port used by pppd is STDIN. Cases # have been observed on the H5 where radio-cmd works, but stty does # not, resulting in pppd failing. When this happens, radio-reset is # the best thing to try. function sanity { id="^[0-9][0-9][0-9][0-9][0-9][0-9]" if ! log=$(stty 2>&1) || ! [[ $($RQCMD --iccid) =~ $id ]] ; then ${LOG} "Modem has response issues, so see if we can try again" if ((${#log} > 0)); then ${LOG} "stty error: ${log}" fi array=( ) # our parent is PPID, which is pppd. Get the pppd # parameter list. while IFS= read -r -d '' eparm; do execarg+=( "$eparm" ) done < <(cat /proc/$PPID/cmdline) i=0 ${LOG} "List parameters" while ((i < ${#execarg[@]})) ; do ${LOG} "$i: ${execarg[$i]}" ((i++)) done if [[ ${execarg[0]} == "pppd" ]] ; then execarg[0]=/usr/sbin/pppd fi # Break any recursion loop. if [[ -f ${CHAT_WRAPPER_RECURSION} ]] ; then ${LOG} "Failure: modem is still unresponsive after reset" ${LOG} "Failure: Verify that modem and SIM are in place" rm -f ${CHAT_WRAPPER_RECURSION} exit 1 fi touch ${CHAT_WRAPPER_RECURSION} ${LOG} "Bad TTY, possibly due to modem in bad state/no SIM detected" ${LOG} "Reset modem, and we relaunch pppd" # Our terminal is the modem. When we reset the modem, # we will be sent a SIGHUP. Ignore it (:). trap : SIGHUP /usr/sbin/mts-io-sysfs store radio-reset 0 wait_for_reset ${LOG} "Completed wait_for_reset" count=0 while ((count < 60)) ; do # Wait for radio-reset to get the modem responding if [[ $(radio-cmd -t10000 'AT') =~ ^OK ]]; then # parent is dying, so remove the pppd locks for the new pppd. for f in /run/lock/LCK..modem_at0 /run/lock/LCK..modem_at1 ; do if [[ -f $f ]] ; then mypid=$(cat $f) mypid="${mypid#"${mypid%%[![:space:]]*}"}" mypid="${mypid%"${mypid##*[![:space:]]}"}" ${LOG} "PPID is $PPID, pid is $mypid" # Remove the lock, if it belongs to our parent, pppd if ((${#mypid} == 0)) || [[ $mypid = $PPID ]] ; then ${LOG} "match $mypid $PPID" sleep 60 rm -f $f else ${LOG} "mismatch |$mypid| |$PPID|" fi fi done # Locks gone, so start new pppd nohup "${execarg[@]}" >/dev/null 2>&1 & # Tell our parent we failed, which causes our pppd to terminate. exit 1 else sleep 5 fi done ${LOG} "ERROR: After five minutes the modem is still not ready" ${LOG} "ERROR: Verify that SIM is in place" exit 1 fi return 0 } # End of sanity function # If > 0, need to wait because we were not registered. WAITREG=0 [[ -f $CONFIG ]] || exit 1 . ${CONFIG} : ${REGWAITTIME:=300} : ${FINALWAIT:=5} : ${COPSWAIT:=10} : ${CFUNWAIT:=10} ${LOG} Timeout is $REGWAITTIME, execute "$@" # Wait for udev to discover modem has been reset. wait_for_reset ((i=$#)) chatscript="${!i}" ${LOG} Parsing chat script "$chatscript" # CONTEXT is last context string in chat script CONTEXT=$(egrep "^#MT[[:space:]]+(AT\+CGDCONT=.*)" ${chatscript} | tail -1) if ((${#CONTEXT} == 0)) ; then CONTEXT=$(egrep "^[^#]+AT\+CGDCONT=" ${chatscript} | tail -1) [[ $CONTEXT =~ \'(AT\+CGDCONT=([0-9]+)[^$\']+) ]] else [[ $CONTEXT =~ (AT\+CGDCONT=([0-9]+).*)$ ]] fi # CONTEXTNUM is the context number that is configured in the dialer. CONTEXT="${BASH_REMATCH[1]}" if ((${#CONTEXT} == 0)) ; then ${LOG} No context specifiction in the chat script finish "$@" # NOTREACHED fi CONTEXTNUM=${BASH_REMATCH[2]} # Make sure CFUN=1, in case the last pppd was aborted with it set to 0. ${LOG} "Verify CFUN=1" cfun=$(/usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+CFUN?') result1=$? if ((result1 != 0)) || ((${#cfun} == 0)) ; then ${LOG} "Is modem in bad state -- \"CFUN?\" is broken?" sanity ((WAITREG++)) fi if ! [[ $cfun =~ ^\+CFUN:\ 1[,[:space:]] ]] ; then /usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+CFUN=1' ${LOG} "Just set CFUN=1 so wait ${CFUNWAIT} seconds" sleep ${CFUNWAIT} ((WAITREG++)) fi ${LOG} "Using Context ${CONTEXTNUM} based on chat script: ${CONTEXT}" # At this point if there is no context number, we can skip everything else. # Get Modem's context settings MCONTEXT=$(/usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+CGDCONT?' 2>&1 | tr -d '\r') if [[ $MCONTEXT =~ ^[Ee][Rr][Rr][Oo][Rr] ]] ; then RADIOOPTION="${RADIOOPTION2}" sleep 1 MCONTEXT=$(/usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+CGDCONT?' 2>&1 | tr -d '\r') CRESULT=$? fi if ! [[ $MCONTEXT =~ \+CGDCONT:[[:space:]]+${CONTEXTNUM},\"([^\"]*)\",\"([^\"]*)\",\"([^\"]*)\",([0-9]+),([0-9]+)([^$'\n']*) ]] ; then logger -s -p daemon.error "No valid context in modem. Is it ready?" echo "$MCONTEXT" | logger -s -p daemon.error echo "Result is $CRESULT" | logger -s -p daemon.error if [[ $MCONTEXT =~ ^[Ee][Rr][Rr][Oo][Rr] ]] ; then logger -s -p daemon.error "Context query response error. Diagnose ..." sanity fi fi MPDP="${BASH_REMATCH[1]}" MAPN="${BASH_REMATCH[2]}" MADDR="${BASH_REMATCH[3]}" MDCOMP="${BASH_REMATCH[4]}" MHCOMP="${BASH_REMATCH[5]}" MFULLBOAT="${BASH_REMATCH[6]}" [[ $CONTEXT =~ AT\+CGDCONT=${CONTEXTNUM},\"([^\"]*)\",\"([^\"]*)\"(,\"([^\"]*)\"(,([0-9]+)(,([0-9]+)(,[^\']*))*)*)* ]] PDP="${BASH_REMATCH[1]}" APN="${BASH_REMATCH[2]}" ADDR="${BASH_REMATCH[4]}" # Optional DCOMP="${BASH_REMATCH[6]}" # Optional HCOMP="${BASH_REMATCH[8]}" # Optional FULLBOAT="${BASH_REMATCH[9]}" # Optional # On some modems there are more parameters than others. if [[ $MFULLBOAT != $FULLBOAT ]] ; then ${LOG} "Only the first five context parameters are considered. The rest will be ignored." ${LOG} "modem: \"$MFULLBOAT\"" ${LOG} "chat script: \"$FULLBOAT\"" fi if ((${#DCOMP} == 0)) ; then ((DCOMP=0)) # Default fi if ((${#HCOMP} == 0)) ; then ((HCOMP=0)) # Default fi # For Quectel, address 0 is expressed as "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0" Q0ADDR="0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0" Q1ADDR="0.0.0.0" ADDRMATCH=1 if [[ $MADDR != $ADDR ]] ; then ADDRMATCH=0 if [[ $MADDR = $Q0ADDR ]] || [[ $MADDR = $Q1ADDR ]] ; then if [[ -z $ADDR ]] || [[ $ADDR = 0 ]] ; then ADDRMATCH=1 fi fi fi # Only update context on a mismatch between chat and modem. if [[ $MPDP != $PDP ]] || [[ $MAPN != $APN ]] || \ (( $ADDRMATCH == 0 )) || ((MDCOMP != DCOMP)) || \ ((MHCOMP != HCOMP)) ; then ${LOG} "Modem context $MPDP,$MAPN,$MADDR,$MDCOMP,$MHCOMP does not match chat script" ${LOG} "Chat script context $PDP,$APN,$ADDR,$DCOMP,$HCOMP does not match the modem" if [[ $MPDP != $PDP ]] ; then ${LOG} "PDP mismatch" fi if [[ $MAPN != $APN ]] ; then ${LOG} "APN mismatch" fi if [[ $MADDR != $ADDR ]] ; then ${LOG} "ADDR mismatch" ${LOG} "Modem Address $MADDR" ${LOG} "Chat Address $ADDR" fi if [[ $MDCOMP != $DCOMP ]] ; then ${LOG} "DCOMP mismatch" fi if [[ $MHCOMP != $HCOMP ]] ; then ${LOG} "HCOMP mismatch" fi if [[ $MFULLBOAT != $FULLBOAT ]] ; then ${LOG} "Final parameter mismatches ignored" ${LOG} "Parameter 6 and up on the modem:" ${LOG} "\"${MFULLBOAT}\"" ${LOG} "Parameter 6 and up on the chat script:" ${LOG} "\"${FULLBOAT}\"" fi ${LOG} "$MCONTEXT" ${LOG} "Dropping registration with carrier to set context" # Need to deregister ((WAITREG++)) /usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+COPS=2' count=10 while ((count > 0)) && ! [[ $($RQCMD --netreg) =~ ^NOT\ REGISTERED$ ]] ; do sleep 2 ((count--)) done # H5 radios do not like addresses that are empty with "" if ((${#ADDR} == 0)) ; then CMDSTR="AT+CGDCONT=${CONTEXTNUM},\"${PDP}\",\"${APN}\",,$DCOMP,${HCOMP}${FULLBOAT}" else CMDSTR="AT+CGDCONT=${CONTEXTNUM},\"${PDP}\",\"${APN}\",\"${ADDR}\",$DCOMP,${HCOMP}${FULLBOAT}" fi ${LOG} "Issued command /usr/bin/radio-cmd ${RADIOOPTION} -t10000 ..." ${LOG} "... ${CMDSTR}" # If we fail to set the APN, sleep and try it again for up to 10 seconds. result=1 count=10 while ((result != 0)) && ((count > 0)) ; do ((count--)) || true LOGMSG=$(/usr/bin/radio-cmd ${RADIOOPTION} -t10000 "${CMDSTR}" 2>&1) result=$? ${LOG} "Setting context: Result ${result}, Got response ${LOGMSG}" if [[ $LOGMSG =~ ERROR ]] ; then result=1 fi sleep 1 done # re-enable the modem to defaults. /usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+COPS=0' # Some older modems will not re-register after the COPS # command if you do not reset them using CFUN. /usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+CFUN=0' ${LOG} "Set COPS=0, CFUN=1, so wait ${CFUNWAIT} seconds" sleep ${CFUNWAIT} /usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+CFUN=1' sleep ${CFUNWAIT} # Abort PPP if the Context is not correct. We don't want # to cause the carrier to disable the account by dialing # out with a bad APN. if ((result != 0)) ; then ${LOG} "AT+CGDCONT failed, aborting" exit $result fi sleep 1 ${LOG} "New context is set. Wait up to $REGWAITTIME seconds to register" else ${LOG} "Context $CONTEXTNUM matches verify COPS, then wait for registration." # Check and set COPS and CFUN to be sure we were not interrupted at some point # in the past. cops=$(/usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+COPS?') result0=$? if ((result0 != 0)) || ! [[ $cops =~ ^\+COPS:[[:space:]]0[,[:space:]] ]] ; then /usr/bin/radio-cmd ${RADIOOPTION} -t10000 'AT+COPS=0' ((WAITREG++)) ${LOG} "Cops status was $cops" ${LOG} "Just set COPS=0 so wait ${COPSWAIT} seconds" sleep ${COPSWAIT} fi fi # Wait for registration uptime=$(cat /proc/uptime) [[ $uptime =~ ^([^\.]*) ]] t0=${BASH_REMATCH[1]} while ! [[ $($RQCMD --netreg) =~ ^(REGISTERED|ROAMING)$ ]] ; do sleep 5 ((WAITREG++)) uptime=$(cat /proc/uptime) [[ $uptime =~ ^([^\.]*) ]] t1=${BASH_REMATCH[1]} if ((t1-t0 > REGWAITTIME)) ; then ${LOG} "$((t1-t0)) seconds has expired without registration" sanity fi done uptime=$(cat /proc/uptime) [[ $uptime =~ ^([^\.]*) ]] t1=${BASH_REMATCH[1]} ${LOG} "Re-registered in $((t1-t0)) seconds" if ((WAITREG > 0)) ; then ${LOG} "Wait ${FINALWAIT} more seconds" sleep $FINALWAIT fi sanity rm -f ${CHAT_WRAPPER_RECURSION} finish "$@" # NOTREACHED