Yes, it is because of inactivity timeouts. But without rooting the device I cannot determine for sure if it is that specific hostapd ap_max_inactivity=300
default, but I believe it is. I'm not sure if it is
the case that my Linux 4.10 x86_64 iwlwifi driver (recently restructured) is not acting appropriately when it receives the empty data frame.
But, I think it is really not in for Android to decide to prevent users from setting this parameter anymore, when it used to let them in previous versions.
The fix is, of course, to start a 'ping monitor daemon', like this shell script on the AP client host (Linux in my case):
#!/usr/bin/bash
declare -i wpm_should_exit=0;
function wpm_exit()
{ wpm_should_exit=1;
}
function wlan_ping_monitor()
{ trap 'wpm_exit' HUP INT QUIT TERM;
local the_IF="$1";
declare -i ok=1 i=0;
def_router=$(ip route show default 2>/dev/null |\
sed -rn \
'/^default[[:space:]]via/{s/^.*via[[:space:]]+'\
'//;s/[[:space:]].*$//;p}'); # join above lines!
if [ x == x"$def_router" ]; then
echo "$FUNCNAME: default router is not set. Nothing to do." >&2;
return 1;
fi
kernel_org=$(host www.kernel.org | sed -rn \
'/has[[:space:]]+address/{s/^.*address' \
'[[:space:]]'//;p}'); # join above lines!
if [ x = x"$kernel_org" ]; then
echo "$FUNCNAME: Unable to resolve kernel.org -"\
" must be able to ping an internet host." >&2;
return 1;
fi
logger -p user.info "$FUNCNAME : started.";
sleep 60;
while ((0 == wpm_should_exit)); do
((ok=0));
for (( i=0; i < 3 ; i+=1 )); do
if /usr/bin/ping -n -q -c 2 -W 8 $kernel_org >/dev/null 2>&1 || \
/usr/bin/ping -n -q -c 2 -W 4 $def_router >/dev/null 2>&1; \
then ((ok=1)); break 1 ; fi
logger -p daemon.error \
"$FUNCNAME: ping $def_router or $kernel_org failed.";
sleep 1;
done
if (( ok == 0 )); then
logger -p daemon.error \
"$FUNCNAME"': interface '"$the_IF"\
' is down. Could not ping '$def_router' or '$kernel_org\
'. Restarting Network...' ;
/etc/init.d/network restart || break;
((ok=1));
sleep 60;
else
sleep 8;
fi
done
logger -p daemon.error "$FUNCNAME: restarting network failed.";
rm -vf $PID_FILE;
}
declare -i status=0;
case "$0" in
(\*${BASH_SOURCE})
wlan_ping_monitor ;
status=$?;
;;
esac
((status==0));
For completeness, here is daemon.sh, with which the above script
is invoked by '/etc/init.d/network start':
PID_FILE=/run/wlan_ping_monitor.pid \
daemon /etc/init.d/wlan_ping_monitor.sh
Missing is only the 'setpgid' and 'noctty' bash built-ins, which does:
setpgid( pid = getpid(), pid );
and
ioctl(0, TIOCSCTTY , 0 );
But, they are not strictly necessary to test - just remove / comment out -
these built-ins are easy to write & available on request. They must be
built-ins, not programs, because they must effect the current process.
One weird thing about the ping script is that it must send a packet
out to the WAN internet to reset the inactivity timer - it is not
enough to simply ping the phone , which can fail to respond to ICMP
packets within 4 seconds while still passing through pings to the WAN.
#!/usr/bin/bash
. /usr/lib64/bash-${BASH_VERSION}_loadables/load.sh
if [ ! -v N_DAEMONS ]; then
declare -xi N_DAEMONS=0;
fi
if [ ! -v TMPDIR ]; then
declare -x TMPDIR=/tmp;
fi
if [ ! -v USER ]; then
declare -x USER=$(whoami);
fi
declare -i daemon_status=0;
function daemon()
{ export \
PID_FILE=${PID_FILE:-${TMPDIR}/${USER}- daemon-${N_DAEMONS}.pid};
declare -xi N_DAEMONS=0;
if [[ "$PID_FILE" =~ ^(.*)[\.][pP][iI][dD]$ ]]; then
export PID_FILE="${BASH_REMATCH[1]}";
fi
while [ -f "$PID_FILE".pid ]; do
((N_DAEMONS+=1));
export PID_FILE="${PID_FILE%-\*}-${N_DAEMONS}";
done
export PID_FILE="${PID_FILE}.pid" LOG_FILE="${PID_FILE}.log";
echo "daemon: PID_FILE: $PID_FILE LOG_FILE: $LOG_FILE" >&2;
declare -i checkjobs_enabled=0;
if shopt -q checkjobs; then
((checkjobs_enabled=1));
shopt -u checkjobs;
fi
trap "" CHLD HUP TERM QUIT EXIT ERR;
{ tty=$(ps -o 'tty=' -p $BASHPID 2>/dev/null);
{ trap "-" CHLD HUP TERM QUIT EXIT ERR;
if [ x != x$tty ] && [ -e /dev/$tty ]; then
noctty < /dev/$tty || :; # another built-in -just comment out
fi
setpgid;
echo "$BASHPID" > "$PID_FILE";
declare -a ps=($(cat /proc/${BASHPID}/stat));
if [ x != x"${ps[3]}" ] && [ "${ps[3]}" != 1 ] && \
[ -d /proc/${ps[3]} ] ; then
echo 'DAEMON: '${ps[3]}' : '$(readlink \
/proc/${ps[3]}/exe) >&2;
fi
exec "$@" </dev/null >${LOG_FILE} 2>&1 ;
} &
exit $?; # our child becomes child of init
}&
declare -i ok=$?;
trap "-" CHLD HUP TERM QUIT EXIT ERR;
if ((checkjobs_enabled)); then
shopt -s checkjobs;
fi
return $ok;
}
case "$0" in
(\*/${BASH_SOURCE}) ;&
(${BASH_SOURCE})
daemon "$@";
((daemon_status=$?));
;;
esac
((daemon_status==0));
This daemon is also killed by /etc/init.d/network
stop (which is invoked
by 'network restart').
It is a shame that it is necessary, when simply moving from using an Android 4.2.2 phone Access Point / router to using an Android 7.0 AP, that such methods must be used by clients to maintain an 'always on' WAN internet connection through the AP.