linux-router/lnxrouter

2361 lines
74 KiB
Bash
Executable File

#!/bin/bash
VERSION=0.7.6
PROGNAME="$(basename "$0")"
export LC_ALL=C
SCRIPT_UMASK=0122
umask $SCRIPT_UMASK
phead() {
echo "linux-router $VERSION (https://github.com/garywill/linux-router)"
}
phead2() {
echo "Released under LGPL, with no warranty. Use on your own risk."
}
usage() {
phead
phead2
cat << EOF
Usage: $PROGNAME <options>
Options:
-h, --help Show this help
--version Print version number
-i <interface> Interface to make NATed sub-network,
and to provide Internet to
(To create WiFi hotspot use '--ap' instead)
-o <interface> Specify an inteface to provide Internet from.
(Note using this with default DNS option may leak
queries to other interfaces)
-n Do not provide Internet
--ban-priv Disallow clients to access my private network
-g <ip> This host's IPv4 address in subnet (mask is /24)
(example: '192.168.5.1' or '5' shortly)
-6 Enable IPv6 (NAT)
--no4 Disable IPv4 Internet (not forwarding IPv4).
Usually used with '-6'
--p6 <prefix> Set IPv6 LAN address prefix (length 64)
(example: 'fd00:0:0:5::' or '5' shortly)
Using this enables '-6'
--dns <ip>|<port>|<ip:port>
DNS server's upstream DNS.
Use ',' to seperate multiple servers
(default: use /etc/resolv.conf)
(Note IPv6 addresses need '[]' around)
--no-dns Do not serve DNS
--no-dnsmasq Disable dnsmasq server (DHCP, DNS, RA)
--catch-dns Transparent DNS proxy, redirect packets(TCP/UDP)
whose destination port is 53 to this host
--log-dns Show DNS query log (dnsmasq)
--dhcp-dns <IP1[,IP2]>|no
Set IPv4 DNS offered by DHCP (default: this host).
--dhcp-dns6 <IP1[,IP2]>|no
Set IPv6 DNS offered by DHCP (RA)
(default: this host)
(Note IPv6 addresses need '[]' around)
Using both above two will enable '--no-dns'
--hostname <name> DNS server associate this name with this host.
Use '-' to read name from /etc/hostname
-d DNS server will take into account /etc/hosts
-e <hosts_file> DNS server will take into account additional
hosts file
--dns-nocache DNS server no cache
--mac <MAC> Set MAC address
--random-mac Use random MAC address
--tp <port> Transparent proxy,
redirect non-LAN TCP and UDP(not tested) traffic to
port. (usually used with '--dns')
WiFi hotspot options:
--ap <wifi interface> <SSID>
Create WiFi access point
-p, --password <password>
WiFi password
--qr Show WiFi QR code in terminal (need qrencode)
--hidden Hide access point (not broadcast SSID)
--no-virt Do not create virtual interface
Using this you can't use same wlan interface
for both Internet and AP
--virt-name <name> Set name of virtual interface
-c <channel> Specify channel (default: use current, or 1 / 36)
--country <code> Set two-letter country code for regularity
(example: US)
--freq-band <GHz> Set frequency band: 2.4 or 5 (default: 2.4)
--driver Choose your WiFi adapter driver (default: nl80211)
-w <WPA version> '2' for WPA2, '1' for WPA, '1+2' for both
(default: 2)
--psk Use 64 hex digits pre-shared-key instead of
passphrase
--mac-filter Enable WiFi hotspot MAC address filtering
--mac-filter-accept Location of WiFi hotspot MAC address filter list
(defaults to /etc/hostapd/hostapd.accept)
--hostapd-debug <level> 1 or 2. Passes -d or -dd to hostapd
--isolate-clients Disable wifi communication between clients
--no-haveged Do not run haveged automatically when needed
--hs20 Enable Hotspot 2.0
WiFi 4 (802.11n) configs:
--wifi4 Enable IEEE 802.11n (HT)
--req-ht Require station HT (High Throughput) mode
--ht-capab <HT caps> HT capabilities (default: [HT40+])
WiFi 5 (802.11ac) configs:
--wifi5 Enable IEEE 802.11ac (VHT)
--req-vht Require station VHT (Very High Thoughtput) mode
--vht-capab <VHT caps> VHT capabilities
--vht-ch-width <index> Index of VHT channel width:
0 for 20MHz or 40MHz (default)
1 for 80MHz
2 for 160MHz
3 for 80+80MHz (Non-contigous 160MHz)
--vht-seg0-ch <channel> Channel index of VHT center frequency for primary
segment. Use with '--vht-ch-width'
--vht-seg1-ch <channel> Channel index of VHT center frequency for secondary
(second 80MHz) segment. Use with '--vht-ch-width 3'
Instance managing:
--daemon Run in background
-l, --list-running Show running instances
--lc, --list-clients <id|interface>
List clients of an instance. Or list neighbors of
an interface, even if it isn't handled by us.
(passive mode)
--stop <id> Stop a running instance
For <id> you can use PID or subnet interface name.
You can get them with '--list-running'
Examples:
$PROGNAME -i eth1
$PROGNAME --ap wlan0 MyAccessPoint -p MyPassPhrase
$PROGNAME -i eth1 --tp <transparent-proxy> --dns <dns-proxy>
EOF
}
check_empty_option(){
if [[ "$1" == "" ]]; then
usage
exit 0
fi
}
define_global_variables(){
# user options
GATEWAY4= # IPv4 address for this host
PREFIX6= # IPv6 LAN address prefix for this host
IID6=1 # IPv6 LAN ID for this host
IPV6=0 # enable ipv6
NO4=0 # no IPv4 Internet
BANLAN=0 # ban clients from accessing private addresses
DHCP_DNS=gateway # which ipv4 DNS the DHCP gives clients
DHCP_DNS6=gateway # which ipv6 DNS the DHCP gives clients
dnsmasq_NO_DNS=0 # disable dns server
NO_DNSMASQ=0 # disable dnsmasq (dns and dhcp)
CATCH_DNS=0 # catch clients 53 port packets
SHOW_DNS_QUERY=0 # log dns
ETC_HOSTS=0
ADDN_HOSTS=
DNS_NOCACHE=
CONN_IFACE= # which interface user choose to use to create network
INTERNET_IFACE= # which interface to get Internet from
THISHOSTNAME= # this host's name the DNS tells clients
TP_PORT= # transparent proxy port
DNS= # upstream DNS
MAC_USE_RANDOM=0
NEW_MACADDR=
DAEMONIZE=0
# script variables
SUBNET_IFACE= # which interface to create network
SHARE_METHOD=nat
OLD_MACADDR=
SUBNET_NET4=
SUBNET_NET6=
##### wifi hotspot
# user options
HIDDEN=0 # hidden wifi hotspot
WIFI_IFACE=
CHANNEL=default
HOTSPOT20=0 # For enabling Hotspot 2.0
WPA_VERSION=2
MAC_FILTER=0
MAC_FILTER_ACCEPT=/etc/hostapd/hostapd.accept
IEEE80211N=0
REQUIREHT=0
IEEE80211AC=0
REQUIREVHT=0
HT_CAPAB='[HT40+]'
VHT_CAPAB=
VHTCHANNELWIDTH=0
VHTSEG0CHINDEX=0
VHTSEG1CHINDEX=0
DRIVER=nl80211
NO_VIRT=0 # not use virtual interface
COUNTRY=
FREQ_BAND=2.4
NO_HAVEGED=0
HOSTAPD_DEBUG_ARGS=
USE_PSK=0
ISOLATE_CLIENTS=0
QR=0 # show wifi qr
# script variables
VWIFI_IFACE= # virtual wifi interface name, if created
VIRT_NAME= # name to use for virtual interface if --virt-name is used
AP_IFACE= # can be VWIFI_IFACE or WIFI_IFACE
USE_IWCONFIG=0 # some device can't use iw
#######
#-- to deal with info of a running instance. then will exit
LIST_RUNNING=0
STOP_ID=
LIST_CLIENTS_ID=
# -- variables for running
CONFDIR=
IP_VERs=
NM_UNM_LIST= # it's called "list" but for now one interface
NM_PID=
FIREWALLD_PID=
TMP_FIREWALLD_ZONE=
}
parse_user_options(){
while [[ -n "$1" ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
--version)
echo "$VERSION"
exit 0
;;
-i)
shift
CONN_IFACE="$1"
shift
;;
-o)
shift
INTERNET_IFACE="$1"
shift
;;
-n)
shift
SHARE_METHOD=none
;;
--ban-priv)
shift
BANLAN=1
;;
--tp)
shift
TP_PORT="$1"
SHARE_METHOD=redsocks
shift
;;
-g)
shift
GATEWAY4="$1"
shift
;;
-6)
shift
IPV6=1
;;
--no4)
shift
NO4=1
;;
--p6)
shift
PREFIX6="$1"
IPV6=1
shift
;;
--mac)
shift
NEW_MACADDR="$1"
shift
;;
--random-mac)
shift
MAC_USE_RANDOM=1
;;
--dns)
shift
DNS="$1"
shift
;;
--no-dns)
shift
dnsmasq_NO_DNS=1
;;
--no-dnsmasq)
shift
NO_DNSMASQ=1
;;
--dhcp-dns)
shift
DHCP_DNS="$1"
shift
;;
--dhcp-dns6)
shift
DHCP_DNS6="$1"
shift
;;
--catch-dns)
shift
CATCH_DNS=1
;;
--log-dns)
shift
SHOW_DNS_QUERY=1
;;
--hostname)
shift
THISHOSTNAME="$1"
shift
;;
-d)
shift
ETC_HOSTS=1
;;
-e)
shift
ADDN_HOSTS="$1"
shift
;;
--dns-nocache)
shift
DNS_NOCACHE=1
;;
--isolate-clients)
shift
ISOLATE_CLIENTS=1
;;
--ap)
shift
WIFI_IFACE="$1"
shift
SSID="$1"
shift
;;
-p|--password)
shift
PASSPHRASE="$1"
shift
;;
--qr)
shift
QR=1
;;
--hidden)
shift
HIDDEN=1
;;
--mac-filter)
shift
MAC_FILTER=1
;;
--mac-filter-accept)
shift
MAC_FILTER_ACCEPT="$1"
shift
;;
-c)
shift
CHANNEL="$1"
shift
;;
--hs20)
shift
HOTSPOT20=1
;;
-w)
shift
WPA_VERSION="$1"
[[ "$WPA_VERSION" == "2+1" ]] && WPA_VERSION=1+2
shift
;;
--wifi4|--ieee80211n)
shift
IEEE80211N=1
;;
--req-ht|--require-ht)
shift
REQUIREHT=1
;;
--wifi5|--ieee80211ac)
shift
IEEE80211AC=1
;;
--req-vht|--require-vht)
shift
REQUIREVHT=1
;;
--ht-capab)
shift
HT_CAPAB="$1"
shift
;;
--vht-capab)
shift
VHT_CAPAB="$1"
shift
;;
--vht-ch-width|--vht-channel-width)
shift
VHTCHANNELWIDTH="$1"
shift
;;
--vht-seg0-ch|--vht-seg0-channel)
shift
VHTSEG0CHINDEX="$1"
shift
;;
--vht-seg1-ch|--vht-seg1-channel)
shift
VHTSEG1CHINDEX="$1"
shift
;;
--driver)
shift
DRIVER="$1"
shift
;;
--no-virt)
shift
NO_VIRT=1
;;
--virt-name)
shift
VIRT_NAME="$1"
shift
;;
--country)
shift
COUNTRY="$1"
shift
;;
--freq-band)
shift
FREQ_BAND="$1"
shift
;;
--no-haveged)
shift
NO_HAVEGED=1
;;
--hostapd-debug)
shift
if [ "$1" = "1" ]; then
HOSTAPD_DEBUG_ARGS="-d"
elif [ "$1" = "2" ]; then
HOSTAPD_DEBUG_ARGS="-dd"
else
printf "Error: argument for --hostapd-debug expected 1 or 2, got %s\n" "$1"
exit 1
fi
shift
;;
--psk)
shift
USE_PSK=1
;;
--daemon)
shift
DAEMONIZE=1
;;
--stop)
shift
STOP_ID="$1"
shift
;;
-l|--list-running)
shift
LIST_RUNNING=1
;;
--lc|--list-clients)
shift
LIST_CLIENTS_ID="$1"
shift
;;
*)
echo "Invalid parameter: $1" 1>&2
exit 1
;;
esac
done
}
# seperate ip and port
sep_ip_port() {
# usage: sep_ip_port <ip:port> <var for ip> <var for port>
# input <ip:port> can be:
# port (ip is 127.0.0.1)
# ipv4
# [ipv6]
# ipv4:port
# [ipv6]:port
local IP
local PORT
local INPUT
INPUT="$1"
if (echo "$INPUT" | grep '\.' >/dev/null 2>&1) ;then
if (echo "$INPUT" | grep ':' >/dev/null 2>&1) ;then
# ipv4 + port
IP="$(echo "$INPUT" | cut -d: -f1)"
PORT="$(echo "$INPUT" | cut -d: -f2)"
else
# ipv4
IP="$INPUT"
fi
elif (echo "$INPUT" | grep '\]' >/dev/null 2>&1) ;then
if (echo "$INPUT" | grep '\]\:' >/dev/null 2>&1) ;then
# ipv6 + port
IP="$(echo "$INPUT" | cut -d']' -f1 | cut -d'[' -f2)"
PORT="$(echo "$INPUT" | cut -d']' -f2 |cut -d: -f2)"
else
# ipv6
IP="$(echo "$INPUT" | cut -d']' -f1 | cut -d'[' -f2)"
fi
else
# port
IP='127.0.0.1'
PORT="$INPUT"
fi
printf -v "$2" %s "$IP"
printf -v "$3" %s "$PORT"
}
#=========================
is_interface() {
[[ -z "$1" ]] && return 1
[[ -d "/sys/class/net/${1}" ]]
}
is_vface_name_allocated(){
is_interface "$1" || [[ -f "$COMMON_CONFDIR/vfaces/${1}" ]]
}
get_interface_phy_device() { # only for wifi interface
local x
for x in /sys/class/ieee80211/*; do
[[ ! -e "$x" ]] && continue
if [[ "${x##*/}" = "$1" ]]; then
echo "$1"
return 0
elif [[ -e "$x/device/net/$1" ]]; then
echo "${x##*/}"
return 0
elif [[ -e "$x/device/net:$1" ]]; then
echo "${x##*/}"
return 0
fi
done
echo "Failed to get phy interface" >&2
return 1
}
get_adapter_info() { # only for wifi interface
local iPHY
iPHY=$(get_interface_phy_device "$1")
[[ $? -ne 0 ]] && return 1
iw phy "$iPHY" info
}
get_adapter_kernel_module() {
local MODULE
MODULE=$(readlink -f "/sys/class/net/$1/device/driver/module")
echo "${MODULE##*/}"
}
can_be_sta_and_ap() {
# iwconfig does not provide this information, assume false
[[ $USE_IWCONFIG -eq 1 ]] && return 1
if [[ "$(get_adapter_kernel_module "$1")" == "brcmfmac" ]]; then
echo "WARN: brmfmac driver doesn't work properly with virtual interfaces and" >&2
echo " it can cause kernel panic. For this reason we disallow virtual" >&2
echo " interfaces for your adapter." >&2
echo " For more info: https://github.com/oblique/create_ap/issues/203" >&2
return 1
fi
get_adapter_info "$1" | grep -E '{.* managed.* AP.*}' > /dev/null 2>&1 && return 0
get_adapter_info "$1" | grep -E '{.* AP.* managed.*}' > /dev/null 2>&1 && return 0
return 1
}
can_be_ap() {
# iwconfig does not provide this information, assume true
[[ $USE_IWCONFIG -eq 1 ]] && return 0
get_adapter_info "$1" | grep -E '\* AP$' > /dev/null 2>&1 && return 0
return 1
}
can_transmit_to_channel() {
local IFACE CHANNEL_NUM CHANNEL_INFO
IFACE=$1
CHANNEL_NUM=$2
if [[ $USE_IWCONFIG -eq 0 ]]; then
CHANNEL_INFO=$(get_adapter_info "${IFACE}" | grep -E " [0-9]+(\.[0-9]+){0,1} MHz \[${CHANNEL_NUM}\]")
[[ -z "${CHANNEL_INFO}" ]] && return 1
[[ "${CHANNEL_INFO}" == *no\ IR* ]] && return 1
[[ "${CHANNEL_INFO}" == *disabled* ]] && return 1
return 0
else
CHANNEL_NUM=$(printf '%02d' ${CHANNEL_NUM})
CHANNEL_INFO=$(iwlist "${IFACE}" channel | grep -E "Channel[[:blank:]]${CHANNEL_NUM}[[:blank:]]?:")
[[ -z "${CHANNEL_INFO}" ]] && return 1
return 0
fi
}
# taken from iw/util.c
ieee80211_frequency_to_channel() {
local FREQ=$1
if [[ $FREQ -eq 2484 ]]; then
echo 14
elif [[ $FREQ -lt 2484 ]]; then
echo $(( ($FREQ - 2407) / 5 ))
elif [[ $FREQ -ge 4910 && $FREQ -le 4980 ]]; then
echo $(( ($FREQ - 4000) / 5 ))
elif [[ $FREQ -le 45000 ]]; then
echo $(( ($FREQ - 5000) / 5 ))
elif [[ $FREQ -ge 58320 && $FREQ -le 64800 ]]; then
echo $(( ($FREQ - 56160) / 2160 ))
else
echo 0
fi
}
is_interface_wifi_connected() {
if [[ $USE_IWCONFIG -eq 0 ]]; then
iw dev "$1" link 2>&1 | grep -E '^Connected to' > /dev/null 2>&1 && return 0
else
iwconfig "$1" 2>&1 | grep -E 'Access Point: [0-9a-fA-F]{2}:' > /dev/null 2>&1 && return 0
fi
return 1
}
is_unicast_macaddr() {
local x
x=$(echo "$1" | cut -d: -f1)
x=$(printf '%d' "0x${x}")
[[ $(expr $x % 2) -eq 0 ]]
}
get_interface_mac() {
is_interface "$1" || return
cat "/sys/class/net/${1}/address"
}
show_interface_pci_info() { # pci id / model / virtual
is_interface "$1" || return
local device_path
local bus_id=""
local device_type_and_bus_id="unknown"
local driver=""
local device_fullname=""
device_path="$(readlink -f /sys/class/net/$1)"
if [[ "$device_path" == "/sys/devices/pci"* ]]; then
local pci_path
pci_path=$device_path/../..
if [[ -d "$pci_path/driver" ]] ; then
driver=$(readlink -f "$pci_path/driver" | sed 's/\//\n/g' | tail -n 1)
fi
bus_id="$(echo "$device_path" | sed 's/\//\n/g' | tail -n 3 |sed -n 1p)"
device_type_and_bus_id="PCI: $bus_id"
if which lspci >/dev/null 2>&1 ; then
device_fullname="$( lspci -D -nn -s "$bus_id" | awk '{$1="" ; print $0}' )"
fi
elif [[ "$device_path" == *"/virtual/"* ]]; then
device_type_and_bus_id="virtual interface"
fi
echo "$device_type_and_bus_id"
[[ -n "$driver" ]] && echo "System-already-loaded driver: $driver"
[[ -n "$device_fullname" ]] && echo "$device_fullname"
echo ""
# TODO usb
}
alloc_new_vface_name() { # only for wifi
local i=0
local v_iface_name="$VIRT_NAME"
if [[ -z $VIRT_NAME ]]; then
while :; do
v_iface_name="x$i${WIFI_IFACE}"
i=$((i + 1))
is_vface_name_allocated "${v_iface_name}" || break
done
fi
mkdir -p "$COMMON_CONFDIR/vfaces"
touch "$COMMON_CONFDIR/vfaces/${v_iface_name}"
echo "${v_iface_name}"
}
dealloc_vface_name() {
rm -f "$COMMON_CONFDIR/vfaces/$1"
}
#======
get_all_mac_in_system() {
cat /sys/class/net/*/address
}
get_new_macaddr_according_to_existing() {
local REALDEV OLDMAC NEWMAC LAST_BYTE i
REALDEV=$1
OLDMAC=$(get_interface_mac "$REALDEV")
NEWMAC=""
LAST_BYTE=$(printf %d 0x${OLDMAC##*:})
for i in {10..240}; do
NEWMAC="${OLDMAC%:*}:$(printf %02x $(( ($LAST_BYTE + $i) % 256 )))"
(get_all_mac_in_system | grep "$NEWMAC" > /dev/null 2>&1) || break
done
echo "$NEWMAC"
}
generate_random_mac() {
local r1 r2 r3 r4 r5 r6
local RAND_MAC
while :; do
r1=$( printf "%02x" $(($RANDOM%256/4*4)) )
r2=$( printf "%02x" $(($RANDOM%256)) )
r3=$( printf "%02x" $(($RANDOM%256)) )
r4=$( printf "%02x" $(($RANDOM%256)) )
r5=$( printf "%02x" $(($RANDOM%256)) )
r6=$( printf "%02x" $(($RANDOM%256)) )
RAND_MAC="$r1:$r2:$r3:$r4:$r5:$r6"
( ! ip link | grep "link" | grep "$RAND_MAC" > /dev/null 2>&1 ) && \
( ! ip maddress | grep "link" | grep "$RAND_MAC" > /dev/null 2>&1 ) && \
( ! ip neigh | grep "lladdr $RAND_MAC" > /dev/null 2>&1 ) && \
( ! get_all_mac_in_system | grep "$RAND_MAC" ) && \
break
done
echo "$RAND_MAC"
}
is_ip4_lan_range_available() { # checks 192.168.x.x
( ip -4 address | grep "inet 192\.168\.$1\." > /dev/null 2>&1 ) && return 1
( ip -4 route | grep "^192\.168\.$1\." > /dev/null 2>&1 ) && return 1
( ip -4 route get 192.168.$1.0 2>&1 | grep -E "\bvia\b|\bunreachable\b" > /dev/null 2>&1 ) && \
( ip -4 route get 192.168.$1.255 2>&1 | grep -E "\bvia\b|\bunreachable\b" > /dev/null 2>&1 ) && return 0
return 1
}
is_ip6_lan_range_available() { # checks fdxx::
( ip -6 address | grep -i "inet6 fd$1:$2$3:$4$5:$6$7:" > /dev/null 2>&1 ) && return 1
( ip -6 route | grep -i "^fd$1:$2$3:$4$5:$6$7:" > /dev/null 2>&1 ) && return 1
( ip -6 route get fd$1:$2$3:$4$5:$6$7:: 2>&1 | grep -E "\bvia\b|\bunreachable\b" > /dev/null 2>&1 ) && \
( ip -6 route get fd$1:$2$3:$4$5:$6$7:ffff:ffff:ffff:ffff 2>&1 | grep -E "\bvia\b|\bunreachable\b" > /dev/null 2>&1 ) && return 0
return 1
}
generate_random_ip4() {
local random_ip4
while :; do
random_ip4=$(($RANDOM%256))
is_ip4_lan_range_available $random_ip4 && break
done
echo "192.168.$random_ip4.1"
}
generate_random_lan_ip6_prefix() {
local r1 r2 r3 r4 r5 r6 r7
while :; do
r1=$( printf "%x" $(($RANDOM%240+16)) )
r2=$( printf "%x" $(($RANDOM%240+16)) )
r3=$( printf "%x" $(($RANDOM%240+16)) )
r4=$( printf "%x" $(($RANDOM%240+16)) )
r5=$( printf "%x" $(($RANDOM%240+16)) )
r6=$( printf "%x" $(($RANDOM%240+16)) )
r7=$( printf "%x" $(($RANDOM%240+16)) )
is_ip6_lan_range_available "$r1" "$r2" "$r3" "$r4" "$r5" "$r6" "$r7" && break
done
echo "fd$r1:$r2$r3:$r4$r5:$r6$r7::"
}
# start haveged when needed
haveged_watchdog() {
local show_warn=1
while :; do
if [[ $(cat /proc/sys/kernel/random/entropy_avail) -lt 1000 ]]; then
if ! which haveged > /dev/null 2>&1; then
if [[ $show_warn -eq 1 ]]; then
echo "WARN: Low entropy detected. We recommend you to install \`haveged'" 1>&2
show_warn=0
fi
elif ! pidof haveged > /dev/null 2>&1; then # TODO judge zombie ?
echo "Low entropy detected, starting haveged" 1>&2
# boost low-entropy
haveged -w 1024 -p $COMMON_CONFDIR/haveged.pid
fi
fi
sleep 2
done
}
pid_watchdog() {
local PID="$1"
local SLEEP="$2"
local ERR_MSG="$3"
local ST
while true
do
if [[ -e "/proc/$PID" ]]; then
ST="$(cat "/proc/$PID/status" | grep "^State:" | awk '{print $2}')"
if [[ "$ST" != 'Z' ]]; then
sleep "$SLEEP"
continue
fi
fi
die "$ERR_MSG"
done
}
#========
get_pid_by_dbus_name() {
local DBUS_NAME="$1"
local pid r
which dbus-send >/dev/null 2>&1 || return 1
pid="$( dbus-send --system --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.GetConnectionUnixProcessID string:$DBUS_NAME 2>/dev/null | grep " uint32 " | awk '{print $2}' )"
r=$?
echo "$pid"
return $r
}
is_same_netns() {
local pid2="$1"
[[ ! -f /proc/$$/ns/net ]] && return 0 # no netns feature. treat as same
[[ "$(readlink /proc/$$/ns/net)" == "$(readlink /proc/$pid2/ns/net)" ]] && return 0
return 1
}
#-----------------
# only support NetworkManager >= 0.9.9
is_nm_running() {
NM_PID="$(get_pid_by_dbus_name "org.freedesktop.NetworkManager")"
[[ ! -n "$NM_PID" ]] && return 1 # not running
if (which nmcli >/dev/null 2>&1 ) && (nmcli -t -f RUNNING g 2>&1 | grep -E '^running$' >/dev/null 2>&1 ) ; then
if is_same_netns "$NM_PID"; then
return 0
fi
fi
NM_PID= # cancel value if treat as not running
return 1 # not running
}
nm_knows() {
(nmcli dev show "$1" | grep -E "^GENERAL.STATE:" >/dev/null 2>&1 ) && return 0 # nm sees
return 1 # nm doesn't see this interface
}
nm_get_manage() { # get an interface's managed state
local s
s=$(nmcli dev show "$1" | grep -E "^GENERAL.STATE:") || return 2 # no such interface
(echo "$s" | grep "unmanaged" >/dev/null 2>&1) && return 1 # unmanaged
return 0 # managed
}
nm_set_unmanaged() {
while ! nm_knows "$1" ; do # wait for virtual wifi interface seen by NM
sleep 0.5
done
if nm_get_manage "$1" ;then
echo "Set $1 unmanaged by NetworkManager"
nmcli dev set "$1" managed no || die "Failed to set $1 unmanaged by NetworkManager"
NM_UNM_LIST=$1
sleep 1
fi
}
nm_set_managed() {
nmcli dev set "$1" managed yes
NM_UNM_LIST=
}
nm_restore_manage() {
if [[ $NM_UNM_LIST ]]; then
echo "Restore $NM_UNM_LIST managed by NetworkManager"
nm_set_managed "$NM_UNM_LIST"
sleep 0.5
fi
}
#-------
is_firewalld_running() {
FIREWALLD_PID="$(get_pid_by_dbus_name "org.fedoraproject.FirewallD1")"
[[ ! -n "$FIREWALLD_PID" ]] && return 1 # not running
if (which firewall-cmd >/dev/null 2>&1 ) && [[ "$(firewall-cmd --state 2>&1)" == "running" ]] ; then
if is_same_netns "$FIREWALLD_PID"; then
echo "firewalld is running ($(firewall-cmd --version))"
return 0
fi
fi
FIREWALLD_PID= # cancel value if treat as not running
return 1 # not running
}
firewalld_add_tmpzone() {
# TMP_FIREWALLD_ZONE="lrt${$}${SUBNET_IFACE}"
TMP_FIREWALLD_ZONE="trusted"
# firewall-cmd --new-zone=$TMP_FIREWALLD_ZONE || die "Failed creating temporary firewalld zone"
echo "Adding $SUBNET_IFACE to firewalld '$TMP_FIREWALLD_ZONE' zone"
firewall-cmd --zone=$TMP_FIREWALLD_ZONE --add-interface=$SUBNET_IFACE >/dev/null || die "Failed adding interface to firewalld temporary zone"
}
firewalld_del_tmpzone() {
if [[ -n "$TMP_FIREWALLD_ZONE" ]];then
echo "Removing $SUBNET_IFACE from firewalld '$TMP_FIREWALLD_ZONE' zone"
firewall-cmd --zone=$TMP_FIREWALLD_ZONE --remove-interface=$SUBNET_IFACE >/dev/null
# firewall-cmd --delete-zone=$TMP_FIREWALLD_ZONE
fi
}
#=========
CUSTOM_CHAINS_4_filter=
CUSTOM_CHAINS_4_nat=
CUSTOM_CHAINS_6_filter=
CUSTOM_CHAINS_6_nat=
iptb()
{
local FoS=$1 # 4 | 6
shift
local Vis=$1 # 'v' | 'n'
shift
local T=$1 # table
shift
local ACT=$1 # action: I | A | N . On undo: I or A -> D , N -> F+X
shift
local CH=$1 # chain
shift
[[ "$IPV6" -ne 1 && "$FoS" == "6" ]] && return
local CMD_HEAD=""
local MOUTH=""
local NECK=""
local HAND_UN_NC=0
local TAIL=""
local FULL=""
local ADD_TO_UNDO=1
local arr_name w
for arr_name in CUSTOM_CHAINS_4_filter CUSTOM_CHAINS_4_nat CUSTOM_CHAINS_6_filter CUSTOM_CHAINS_6_nat
do
local arr_content
eval arr_content=\"\${$arr_name}\"
#echo $arr_content
for w in $arr_content
do
if [[ "$arr_name" =~ "$FoS" && "$arr_name" =~ "$T" && "$w" == "$CH" ]]; then
ADD_TO_UNDO=0
fi
done
done
[[ "$FoS" == "4" ]] && CMD_HEAD="iptables -w "
[[ "$FoS" == "6" ]] && CMD_HEAD="ip6tables -w "
[[ "$Vis" == 'v' ]] && MOUTH="-v"
NECK="-t ${T}"
if [[ "$ACT" == "N" ]]; then
eval CUSTOM_CHAINS_${FoS}_${T}=\"\${CUSTOM_CHAINS_${FoS}_${T}} ${CH}\"
HAND_UN_NC=1
fi
[[ ! "$NETFILTER_XT_MATCH_COMMENT" == "0" ]] && TAIL="-m comment --comment lrt${$}${SUBNET_IFACE}"
if [[ "$ADD_TO_UNDO" -eq 1 ]]; then
if [[ "$ACT" == "I" || "$ACT" == "A" ]]; then
echo "$CMD_HEAD $NECK -D ${CH} $@ $TAIL" >> $CONFDIR/undo_iptables.sh
fi
if [[ "$HAND_UN_NC" -eq 1 ]]; then
echo "$CMD_HEAD $NECK -F ${CH} $@ $TAIL" >> $CONFDIR/undo_iptables_2.sh
echo "$CMD_HEAD $NECK -X ${CH} $@ $TAIL" >> $CONFDIR/undo_iptables_2.sh
fi
fi
FULL="$CMD_HEAD $MOUTH $NECK -${ACT} ${CH} $@ $TAIL"
#echo $FULL
$FULL
return $?
}
disable_unwanted_forwarding() {
for iv in "${IP_VERs[@]}"; do
if [[ "$INTERNET_IFACE" ]]; then
iptb "$iv" n filter I FORWARD \
-i "$SUBNET_IFACE" ! -o "$INTERNET_IFACE" \
-j REJECT || die
iptb "$iv" n filter I FORWARD \
! -i "$INTERNET_IFACE" -o "$SUBNET_IFACE" \
-j REJECT || die
fi
if [[ "$SHARE_METHOD" == 'redsocks' || "$SHARE_METHOD" == 'none' \
|| ( "$iv" -eq "4" && "$NO4" -eq 1 ) ]];then
iptb "$iv" n filter I FORWARD -i "$SUBNET_IFACE" -j REJECT || die
iptb "$iv" n filter I FORWARD -o "$SUBNET_IFACE" -j REJECT || die
fi
done
}
start_nat() {
local SUBNET_NET
local iv
echo
echo "iptables: NAT "
for iv in "${IP_VERs[@]}"; do
[[ "$iv" -eq "4" && ! $NO4 -eq 0 ]] && continue
[[ "$iv" -eq "4" ]] && SUBNET_NET="$SUBNET_NET4"
[[ "$iv" -eq "6" ]] && SUBNET_NET="$SUBNET_NET6"
if [[ -n "$INTERNET_IFACE" ]]; then # only one Internet interface
# masquerade subnet -> internet
iptb "$iv" v nat I POSTROUTING -s "$SUBNET_NET" ! -d "$SUBNET_NET" \
-o "$INTERNET_IFACE" \
-j MASQUERADE || die
# forward subnet -> internet
iptb "$iv" v filter I FORWARD -i "$SUBNET_IFACE" -s "$SUBNET_NET" \
-o $INTERNET_IFACE \
-j ACCEPT || die
# forward any -> subnet
iptb "$iv" v filter I FORWARD -o "$SUBNET_IFACE" -d "$SUBNET_NET" \
-i "$INTERNET_IFACE" \
-j ACCEPT || die
else # any interface can be Internet
# masquerade subnet -> any(!subnet)
iptb "$iv" v nat I POSTROUTING -s "$SUBNET_NET" ! -d "$SUBNET_NET" \
! -o "$SUBNET_IFACE" \
-j MASQUERADE || die
# forward subnet -> any
iptb "$iv" v filter I FORWARD -i "$SUBNET_IFACE" -s "$SUBNET_NET" \
-j ACCEPT || die
# forward any -> subnet
iptb "$iv" v filter I FORWARD -o "$SUBNET_IFACE" -d "$SUBNET_NET" \
-j ACCEPT || die
fi
done
}
start_ban_lan() {
local arr_nets_to_protect
local ICMP_NAME
local iv s
echo
echo "iptables: Disallow clients to access LAN"
for iv in "${IP_VERs[@]}"; do
# ban forwarding for subnet
iptb "$iv" n filter N lrt${$}${SUBNET_IFACE}-BLF || die
# TODO: allow '--dhcp-dns(6)' address port 53, which can be something needed, e.g. a VPN's internal private IP
if [[ "$iv" -eq "4" ]]; then
arr_nets_to_protect=("0.0.0.0/8" "10.0.0.0/8" "100.64.0.0/10" "127.0.0.0/8" "169.254.0.0/16" "172.16.0.0/12" "192.168.0.0/16" "224.0.0.0/4" "255.255.255.255")
ICMP_NAME="icmp"
elif [[ "$iv" -eq "6" ]]; then
arr_nets_to_protect=("fc00::/7" "fe80::/10" "ff00::/8" "::1" "::/128" "::ffff:0:0/96" "::ffff:0:0:0/96")
ICMP_NAME="icmpv6"
fi
for s in "${arr_nets_to_protect[@]}"; do
iptb "$iv" v filter I lrt${$}${SUBNET_IFACE}-BLF -d "$s" -j REJECT || die
done
iptb "$iv" n filter I FORWARD -i ${SUBNET_IFACE} -j lrt${$}${SUBNET_IFACE}-BLF || die
# ban input from subnet
iptb "$iv" n filter N lrt${$}${SUBNET_IFACE}-BLI || die
iptb "$iv" v filter I lrt${$}${SUBNET_IFACE}-BLI -i ${SUBNET_IFACE} ! -p "$ICMP_NAME" -j REJECT || die # ipv6 need icmp to function. TODO: maybe we can block some unneeded icmp to improve security
iptb "$iv" n filter I INPUT -i ${SUBNET_IFACE} -j lrt${$}${SUBNET_IFACE}-BLI || die
done
}
allow_dns_port() {
local SUBNET_NET
local GATEWAY
local PROTs
local iv pt
echo
echo "iptables: allow DNS"
for iv in "${IP_VERs[@]}"; do
[[ "$iv" -eq "4" ]] && GATEWAY="$GATEWAY4"
[[ "$iv" -eq "6" ]] && GATEWAY="$GATEWAY6"
[[ "$iv" -eq "4" ]] && SUBNET_NET="$SUBNET_NET4"
[[ "$iv" -eq "6" ]] && SUBNET_NET="$SUBNET_NET6"
PROTs=("tcp" "udp")
for pt in "${PROTs[@]}"; do
iptb "$iv" v filter I INPUT -i "$SUBNET_IFACE" -s "$SUBNET_NET" -d "$GATEWAY" -p "$pt" -m "$pt" --dport 53 -j ACCEPT || die
done
done
}
start_catch_dns() {
local GATEWAY
local PROTs
local iv pt
echo
echo "iptables: redirect DNS queries to this host"
for iv in "${IP_VERs[@]}"; do
[[ "$iv" -eq "4" ]] && GATEWAY="$GATEWAY4"
[[ "$iv" -eq "6" ]] && GATEWAY="$GATEWAY6"
PROTs=("tcp" "udp")
for pt in "${PROTs[@]}"; do
iptb "$iv" v nat I PREROUTING -i "$SUBNET_IFACE" ! -d "$GATEWAY" -p "$pt" -m "$pt" --dport 53 -j REDIRECT --to-ports 53 || die
done
done
}
allow_dhcp() {
echo
echo "iptables: allow dhcp"
iptb 4 v filter I INPUT -i ${SUBNET_IFACE} -p udp -m udp --dport 67 -j ACCEPT || die
iptb 6 v filter I INPUT -i ${SUBNET_IFACE} -p udp -m udp --dport 547 -j ACCEPT || die
}
# TODO: use 'DNAT' instead of '--to-ports' to support other IP
start_redsocks() {
local SUBNET_NET
local arr_nets_to_ignore
local s iv
echo
echo "iptables: transparent proxy non-LAN TCP and UDP(not tested) traffic to port ${TP_PORT}"
for iv in "${IP_VERs[@]}"; do
[[ "$iv" -eq "4" && ! $NO4 -eq 0 ]] && continue
[[ "$iv" -eq "4" ]] && SUBNET_NET="$SUBNET_NET4"
[[ "$iv" -eq "6" ]] && SUBNET_NET="$SUBNET_NET6"
iptb "$iv" n nat N lrt${$}${SUBNET_IFACE}-TP || die
if [[ "$iv" -eq "4" ]]; then
arr_nets_to_ignore=("0.0.0.0/8" "10.0.0.0/8" "100.64.0.0/10" "127.0.0.0/8" "169.254.0.0/16" "172.16.0.0/12" "192.168.0.0/16" "224.0.0.0/4" "255.255.255.255")
elif [[ "$iv" -eq "6" ]];then
arr_nets_to_ignore=("fc00::/7" "fe80::/10" "ff00::/8" "::1" "::")
fi
for s in "${arr_nets_to_ignore[@]}"; do
iptb "$iv" n nat A lrt${$}${SUBNET_IFACE}-TP -d "$s" -j RETURN || die
done
iptb "$iv" v nat A lrt${$}${SUBNET_IFACE}-TP -p tcp -j REDIRECT --to-ports ${TP_PORT} || die
iptb "$iv" v nat A lrt${$}${SUBNET_IFACE}-TP -p udp -j REDIRECT --to-ports ${TP_PORT} || die
iptb "$iv" v nat I PREROUTING -i "$SUBNET_IFACE" -s "$SUBNET_NET" -j lrt${$}${SUBNET_IFACE}-TP || die
iptb "$iv" v filter I INPUT -i "$SUBNET_IFACE" -s "$SUBNET_NET" -p tcp -m tcp --dport ${TP_PORT} -j ACCEPT || die
iptb "$iv" v filter I INPUT -i "$SUBNET_IFACE" -s "$SUBNET_NET" -p udp -m udp --dport ${TP_PORT} -j ACCEPT || die
done
}
#---------------------------------------
backup_ipv6_bits() {
mkdir "$CONFDIR/sys_6_conf_iface" || die "Failed making dir to save interface IPv6 status"
cp "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/disable_ipv6" \
"/proc/sys/net/ipv6/conf/$SUBNET_IFACE/accept_ra" \
"/proc/sys/net/ipv6/conf/$SUBNET_IFACE/use_tempaddr" \
"/proc/sys/net/ipv6/conf/$SUBNET_IFACE/addr_gen_mode" \
"$CONFDIR/sys_6_conf_iface/" || die "Failed backing up interface ipv6 bits"
if [[ "$SHARE_METHOD" == 'redsocks' ]] ; then
cp "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/forwarding" \
"$CONFDIR/sys_6_conf_iface/" || die "Failed backking up interface ipv6 bits"
fi
}
set_ipv6_bits() {
if [[ $IPV6 -eq 1 ]]; then
echo 0 > "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/disable_ipv6"
echo 0 > "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/accept_ra"
echo 0 > "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/use_tempaddr"
echo 0 > "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/addr_gen_mode"
else
echo 1 > "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/disable_ipv6"
fi
}
restore_ipv6_bits() {
if [[ -d "$CONFDIR/sys_6_conf_iface" ]]; then
cp -f "$CONFDIR/sys_6_conf_iface/*" "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/"
fi
}
set_interface_mac() {
local INTERFACE
local MAC
INTERFACE=$1
MAC=$2
ip link set dev "${INTERFACE}" address "${MAC}"
}
backup_interface_status() {
# virtual wifi interface will be destroyed, so no need to save status
# backup interface up or down status
(ip link show "${SUBNET_IFACE}" |grep -q "state UP") && SUBNET_IFACE_ORIGINAL_UP_STATUS=1
# save interface old mac
#if [[ -n "$NEW_MACADDR" ]]; then
OLD_MACADDR=$(get_interface_mac "$SUBNET_IFACE")
#echo "Saved ${SUBNET_IFACE} old MAC address ${OLD_MACADDR} into RAM"
#fi
backup_ipv6_bits
# TODO : ? backup ip and others???
# nm managing status is saved when nm_set_unmanaged()
}
restore_interface_status() {
# virtual wifi interface will be destroyed, so no need to restore status
# don't use [[ $VWIFI_IFACE ]] to judge, if creating virtual wifi failed, VWIFI_IFACE is empty
[[ "$WIFI_IFACE" && "$NO_VIRT" -eq 0 ]] && return
restore_ipv6_bits
if [[ -n "$OLD_MACADDR" && "$(get_interface_mac "$SUBNET_IFACE")" != "$OLD_MACADDR" ]] ; then
echo "Restoring ${SUBNET_IFACE} to old MAC address ${OLD_MACADDR} ..."
set_interface_mac "${SUBNET_IFACE}" "${OLD_MACADDR}" || echo "Failed restoring ${SUBNET_IFACE} to old MAC address ${OLD_MACADDR}" >&2
fi
nm_restore_manage
[[ $SUBNET_IFACE_ORIGINAL_UP_STATUS -eq 1 ]] && ip link set up dev "${SUBNET_IFACE}" && echo "Restore ${SUBNET_IFACE} to link up"
}
#---------------------------------------
kill_processes() { # for this instance
#echo "Killing processes"
local x pid
for x in $CONFDIR/*.pid; do
# even if the $CONFDIR is empty, the for loop will assign
# a value in $x. so we need to check if the value is a file
if [[ -f $x ]] && sleep 0.3 && [[ -f $x ]]; then
pid=$(cat "$x")
pn=$( ps -p "$pid" -o comm= )
#echo "Killing $pid $pn ... "
pkill -P "$pid"
kill "$pid" 2>/dev/null && ( echo "Killed $(basename "$x") $pid $pn" && rm "$x" ) || echo "Failed to kill $(basename "$x") $pid $pn, it may have exited"
fi
done
}
_cleanup() {
local x
ip addr flush "${SUBNET_IFACE}"
rm -rf "$CONFDIR"
ip link set down dev "${SUBNET_IFACE}"
firewalld_del_tmpzone
if [[ $VWIFI_IFACE ]]; then # the subnet interface (virtual wifi interface) will be removed
iw dev "${VWIFI_IFACE}" del
dealloc_vface_name "$VWIFI_IFACE"
fi
restore_interface_status
if ! has_running_instance; then
echo "Exiting: This is the only running instance"
# kill common processes
for x in $COMMON_CONFDIR/*.pid; do
[[ -f $x ]] && kill -9 $(cat "$x") && rm "$x"
done
rm -d "$COMMON_CONFDIR/vfaces"
rm -d "$COMMON_CONFDIR"
rm -d "$TMPDIR"
else
echo "Exiting: This is NOT the only running instance"
fi
}
clean_iptables() {
[[ -f $CONFDIR/undo_iptables.sh ]] && bash $CONFDIR/undo_iptables.sh
[[ -f $CONFDIR/undo_iptables_2.sh ]] && bash $CONFDIR/undo_iptables_2.sh
}
cleanup() {
trap "" SIGINT SIGUSR1 SIGUSR2 EXIT SIGTERM
echo
echo
echo "Doing cleanup.. "
kill_processes
echo "Undoing iptables changes .."
clean_iptables > /dev/null
_cleanup 2> /dev/null
#pgid=$(ps opgid= $$ |awk '{print $1}' )
#echo "Killing PGID $pgid ..."
#kill -15 -$pgid
#sleep 1
echo "Cleaning up done"
#kill -9 -$pgid
}
# NOTE function die() is designed NOT to be used before init_trap() executed
die() { # SIGUSR2
echo "Error occured"
[[ -n "$1" ]] && echo -e "\nERROR: $1\n" >&2
# send die signal to the main process
[[ $BASHPID -ne $$ ]] && kill -USR2 $$ || cleanup
exit 1
}
clean_exit() { # SIGUSR1
# send clean_exit signal to the main process
[[ $BASHPID -ne $$ ]] && kill -USR1 $$ || cleanup
exit 0
}
init_trap(){
trap "cleanup" EXIT
trap "clean_exit" SIGINT SIGUSR1 SIGTERM
trap "die" SIGUSR2
}
init_conf_dirs() {
mkdir -p "$TMPDIR" || die "Couldn't make linux-router's temporary dir"
chmod 755 "$TMPDIR" 2>/dev/null
cd "$TMPDIR" || die "Couldn't change directory to linux-router's temporary path"
CONFDIR="$(mktemp -d $TMPDIR/lnxrouter.${TARGET_IFACE}.conf.XXXXXX)" || die "Instance couldn't make config dir" # config dir for one instance
chmod 755 "$CONFDIR"
#echo "Config dir: $CONFDIR"
echo $$ > "$CONFDIR/pid"
COMMON_CONFDIR="$TMPDIR/lnxrouter_common.conf" # config dir for all instances
mkdir -p "$COMMON_CONFDIR"
}
#== functions to deal with running instances
list_running_conf() {
local x
for x in $TMPDIR/lnxrouter.*; do
if [[ -f $x/pid && -f $x/subn_iface && -d /proc/$(cat $x/pid) ]]; then
echo "$x"
fi
done
}
list_running() {
local IFACE subn_iface x
for x in $(list_running_conf); do
IFACE=${x#*.}
IFACE=${IFACE%%.*}
subn_iface=$(cat $x/subn_iface)
if [[ "$IFACE" == "$subn_iface" ]]; then
echo $(cat $x/pid) $IFACE
else
echo $(cat $x/pid) $IFACE '('$(cat $x/subn_iface)')'
fi
done
}
get_subn_iface_from_pid() {
list_running | awk '{print $1 " " $NF}' | tr -d '\(\)' | grep -E "^${1} " | cut -d' ' -f2
}
get_pid_from_subn_iface() {
list_running | awk '{print $1 " " $NF}' | tr -d '\(\)' | grep -E " ${1}$" | cut -d' ' -f1
}
get_confdir_from_pid() {
local IFACE x
for x in $(list_running_conf); do
if [[ $(cat $x/pid) == "$1" ]]; then
echo "$x"
break
fi
done
}
#======================================================
print_clients_from_leases() { # MAC|IP|HOST|lease
local LEASE_FILE="$1"
local FILEC
local line
local LEASEstr LEASEstamp
FILEC="$(cat "$LEASE_FILE" | grep -v -E "^duid\b" | sed -r '/^\s*$/d' )"
# TODO: duid is somewhat related to ipv6. I don't know about it. Not sure excluding it miss some info or not
echo "$FILEC" | while read -r line
do
#echo aa$line
LEASEstamp="$(echo "$line" | awk '{print $1}')"
MAC="$(echo "$line" | awk '{print $2}')"
IP="$(echo "$line" | awk '{print $3}' | sed 's/\[//g' | sed 's/\]//g')"
HOST="$(echo "$line" | awk '{print $4}' | sed 's/*/?/g' | sed 's/|/_/g' | sed 's/ /_/g' )"
if [[ -n "$MAC" ]]; then
LEASEstr="$(date -d @${LEASEstamp} +%m-%d_%X)"
echo "$MAC|$IP|$HOST|lease_$LEASEstr"
fi
done
}
print_interface_neighbors_via_iproute() { # MAC|IP|_|STATUS
local IFACE=$1
local line
ip n | grep -E "\bdev $IFACE\b" | sed 's/ /|/g' | while read -r line
do
local MAC IP STATUS
IP="$(echo "$line" | awk -F'|' '{print $1}')"
if [[ "$(echo "$line" | awk -F'|' '{print $4}')" == "lladdr" ]]; then # has mac
# if has mac, $4="lladdr" and $5=macaddress and $6+=status
MAC="$(echo "$line" | awk -F'|' '{print $5}')"
STATUS="$(echo "$line" | awk -F'|' '$1="";$2="";$3="";$4="";$5="";{print}' | awk '{$1=$1;print}'| sed 's/ /,/g')"
else # no mac
# if no mac, $4="" and $5+=status
MAC="?"
STATUS="$(echo "$line" | awk -F'|' '$1="";$2="";$3="";$4="";{print}' | awk '{$1=$1;print}' | sed 's/ /,/g')"
fi
if [[ -n "$IP" && ( "$MAC" != "?" || "$STATUS" != "FAILED" ) ]]; then
echo "$MAC|$IP|?|$STATUS"
fi
done
}
print_interface_neighbors_via_iw() { # MAC|_|_|signal
local IFACE=$1
local MAC SIGNAL
iw dev "$IFACE" station dump | awk '($1 ~ /Station$/) {print $2}' | while read -r MAC
do
if [[ -n "$MAC" ]]; then
SIGNAL="$(iw dev "$IFACE" station get "$MAC" | grep "signal:" | awk '{print $2}')"
echo "${MAC}|?|?|${SIGNAL}_dBm"
fi
done
}
list_clients() { # passive mode. (use 'arp-scan' or 'netdiscover' if want active mode)
local IFACE pid
local CONFDIR
local output=""
# If number (PID) is given, get the associated wifi iface
if [[ "$1" =~ ^[1-9][0-9]*$ ]]; then
pid="$1"
IFACE=$(get_subn_iface_from_pid "$pid")
if [[ -z "$IFACE" ]] ; then
echo "'$pid' is not the pid of a running $PROGNAME instance." >&2
exit 1
fi
else # non-number given
IFACE="$1"
if ( ! is_interface "$IFACE" ) ; then
echo "'$IFACE' is not an interface or PID" >&2
exit 1
fi
pid=$(get_pid_from_subn_iface "$IFACE")
if [[ -n "$pid" ]] ; then # if this interface is hosted by us
CONFDIR=$(get_confdir_from_pid "$pid")
output="$(print_clients_from_leases "$CONFDIR/dnsmasq.leases" )"
else # this interface NOT hosted by us
echo "Tip: '$IFACE' is not an interface hosted by $PROGNAME" >&2
fi
fi
output="$(echo "$output" ; print_interface_neighbors_via_iw "$IFACE") "
output="$(echo "$output" ; print_interface_neighbors_via_iproute "$IFACE")"
output="$(echo "$output" | sort -k 1 -k 2 -t '|' | uniq | sed -r '/^\s*$/d')"
echo "$IFACE ($(get_interface_mac "$IFACE")) neighbors:"
local fmt="%-19s%-41s%-20s%s" # string length: MAC 17, ipv4 15, ipv6 39, hostname ?
printf "$fmt\n" "MAC" "IP" "HOSTNAME" "INFO"
local line
echo "$output"| while read -r line
do
if [[ -n "$line" ]]; then
echo "$line" | awk -F'|' "{printf \"$fmt\n\",\$1,\$2,\$3,\$4}"
fi
done
# TODO : merge same mac and same ip line
}
has_running_instance() {
local PID x
for x in $TMPDIR/lnxrouter.*; do
if [[ -f $x/pid ]]; then
PID=$(cat $x/pid)
if [[ -d /proc/$PID ]]; then
return 0
fi
fi
done
return 1
}
is_running_pid() {
list_running | grep -E "^${1} " > /dev/null 2>&1
}
send_stop() {
local x
# send stop signal to specific pid
if is_running_pid "$1"; then
kill -USR1 "$1"
return
fi
# send stop signal to specific interface
for x in $(list_running | grep -E " \(?${1}( |\)?\$)" | cut -f1 -d' '); do
kill -USR1 "$x"
done
}
## ========================================================
## ========================================================
# decide linux-router's global temporary path for all instances
# this is different and should be before config-saving dir. The latter is for one instance
decide_tmpdir(){
local TMPD
if [[ -d /dev/shm ]]; then
TMPD=/dev/shm
elif [[ -d /run/shm ]]; then
TMPD=/run/shm
else
TMPD=/tmp
fi
#TMPDIR=$TMPD/lnxrouter_tmp
echo "$TMPD/lnxrouter_tmp"
}
#======
check_other_functions(){
if [[ $LIST_RUNNING -eq 1 ]]; then
echo -e "List of running $PROGNAME instances:\n"
list_running
exit 0
fi
if [[ -n "$LIST_CLIENTS_ID" ]]; then
list_clients "$LIST_CLIENTS_ID"
exit 0
fi
##### root test ##### NOTE above don't require root ##########
if [[ $(id -u) -ne 0 ]]; then
echo "ERROR: Need root to continue" >&2
exit 1
fi
###### NOTE below require root ##########
if [[ -n "$STOP_ID" ]]; then
echo "Trying to kill $PROGNAME instance associated with $STOP_ID..."
send_stop "$STOP_ID"
exit 0
fi
}
daemonizing_check(){
if [[ $DAEMONIZE -eq 1 && $RUNNING_AS_DAEMON -eq 0 ]]; then
echo "Running as Daemon..."
# run a detached lnxrouter
RUNNING_AS_DAEMON=1 setsid "$0" "${ARGS[@]}" &
exit 0
fi
}
#============================
check_wifi_settings() {
if ! ( which iw > /dev/null 2>&1 && iw dev "$WIFI_IFACE" info > /dev/null 2>&1 ); then
echo "WARN: Can't use 'iw' to operate interfce '$WIFI_IFACE', trying 'iwconfig' (not as good as 'iw') ... (Did you spell the interface name right?)" >&2
USE_IWCONFIG=1
fi
if [[ $USE_IWCONFIG -eq 1 ]]; then
if ! (which iwconfig > /dev/null 2>&1 && iwconfig "$WIFI_IFACE" > /dev/null 2>&1); then
echo "ERROR: Can't use 'iwconfig' to operate interfce '$WIFI_IFACE'" >&2
exit 1
fi
fi
if [[ $FREQ_BAND != 2.4 && $FREQ_BAND != 5 ]]; then
echo "ERROR: Invalid frequency band" >&2
exit 1
fi
if [[ $FREQ_BAND != 5 && $CHANNEL -gt 14 ]]; then
echo "Channel number is greater than 14, assuming 5GHz frequency band"
FREQ_BAND=5
fi
if ! can_be_ap "${WIFI_IFACE}"; then
echo "ERROR: Your adapter does not support AP (master) mode" >&2
exit 1
fi
if ! can_be_sta_and_ap "${WIFI_IFACE}"; then
if is_interface_wifi_connected "${WIFI_IFACE}"; then
echo "ERROR: Your adapter can not be a station (i.e. be connected) and an AP at the same time" >&2
exit 1
elif [[ $NO_VIRT -eq 0 ]]; then
echo "WARN: Your adapter does not fully support AP virtual interface, enabling --no-virt" >&2
NO_VIRT=1
fi
fi
HOSTAPD=$(which hostapd)
if [[ $(get_adapter_kernel_module "${WIFI_IFACE}") =~ ^(8192[cd][ue]|8723a[sue])$ ]]; then
if ! strings "$HOSTAPD" | grep -m1 rtl871xdrv > /dev/null 2>&1; then
echo "ERROR: You need to patch your hostapd with rtl871xdrv patches." >&2
exit 1
fi
if [[ $DRIVER != "rtl871xdrv" ]]; then
echo "WARN: Your adapter needs rtl871xdrv, enabling --driver=rtl871xdrv" >&2
DRIVER=rtl871xdrv
fi
fi
if [[ ${#SSID} -lt 1 || ${#SSID} -gt 32 ]]; then
echo "ERROR: Invalid SSID length ${#SSID} (expected 1..32)" >&2
exit 1
fi
if [[ $USE_PSK -eq 0 ]]; then
if [[ ${#PASSPHRASE} -gt 0 && ${#PASSPHRASE} -lt 8 ]] || [[ ${#PASSPHRASE} -gt 63 ]]; then
echo "ERROR: Invalid passphrase length ${#PASSPHRASE} (expected 8..63)" >&2
exit 1
fi
elif [[ ${#PASSPHRASE} -gt 0 && ${#PASSPHRASE} -ne 64 ]]; then
echo "ERROR: Invalid pre-shared-key length ${#PASSPHRASE} (expected 64)" >&2
exit 1
fi
if [[ $(get_adapter_kernel_module "${WIFI_IFACE}") =~ ^rtl[0-9].*$ ]]; then
if [[ $WPA_VERSION == '1' || $WPA_VERSION == '1+2' ]]; then
echo "WARN: Realtek drivers usually have problems with WPA1, WPA2 is recommended" >&2
fi
echo "WARN: If AP doesn't work, read https://github.com/oblique/create_ap/blob/master/howto/realtek.md" >&2
fi
if [[ -z $VIRT_NAME ]]; then
if [[ ${#WIFI_IFACE} -gt 13 ]]; then
echo "WARN: $WIFI_IFACE has ${#WIFI_IFACE} characters which might be too long. If AP doesn't work, see --virt-name and https://github.com/garywill/linux-router/issues/44" >&2
fi
elif [[ ${#VIRT_NAME} -gt 15 ]]; then
echo "WARN: option --virt-name $VIRT_NAME has ${#VIRT_NAME} characters which might be too long, consider making it shorter in case of errors" >&2
fi
if [[ ! -z $VIRT_NAME ]] && is_vface_name_allocated "$VIRT_NAME"; then
echo "WARN: interface $VIRT_NAME aleady exists, this will cause an error"
fi
}
check_if_new_mac_valid() {
if ! is_unicast_macaddr "$NEW_MACADDR"; then
echo "ERROR: The first byte of MAC address (${NEW_MACADDR}) must be even" >&2
exit 1
fi
if [[ $(get_all_mac_in_system | grep -c "${NEW_MACADDR}") -ne 0 ]]; then
echo "WARN: MAC address '${NEW_MACADDR}' already exists" >&2
fi
}
decide_target_interface() {
# TARGET_IFACE is a existing physical interface
if [[ "$CONN_IFACE" ]]; then
echo "$CONN_IFACE"
elif [[ "$WIFI_IFACE" ]]; then
echo "$WIFI_IFACE"
else
echo "No target interface specified" >&2
return 1
fi
}
decide_ip_addresses() {
if [[ ! -n $GATEWAY4 ]]; then
GATEWAY4="$(generate_random_ip4)"
echo "Use random LAN IPv4 address $GATEWAY4"
elif [[ ! "$GATEWAY4" =~ "." ]]; then
GATEWAY4="192.168.${GATEWAY4}.1"
fi
if [[ $IPV6 -eq 1 && ! -n $PREFIX6 ]]; then
PREFIX6="$(generate_random_lan_ip6_prefix)"
echo "Use random LAN IPv6 address ${PREFIX6}${IID6}"
elif [[ ! "$PREFIX6" =~ ":" ]]; then
PREFIX6="fd00:0:0:${PREFIX6}::"
fi
if [[ $IPV6 -eq 1 ]]; then
GATEWAY6="${PREFIX6}${IID6}"
fi
SUBNET_NET4="${GATEWAY4%.*}.0/24"
[[ $IPV6 -eq 1 ]] && SUBNET_NET6="${PREFIX6}/64"
}
prepare_wifi_interface() {
if [[ $USE_IWCONFIG -eq 0 ]]; then
iw dev "${WIFI_IFACE}" set power_save off
fi
if [[ $NO_VIRT -eq 0 ]]; then
## Will generate virtual wifi interface
# TODO move this to check_wifi_settings() ?
if is_interface_wifi_connected "${WIFI_IFACE}"; then
WIFI_IFACE_FREQ=$(iw dev "${WIFI_IFACE}" link | grep -i freq | awk '{print $2}' | sed 's/\.00*$//g') # NOTE we assume integer currently, which can be right, or wrong in the future
WIFI_IFACE_CHANNEL=$(ieee80211_frequency_to_channel "${WIFI_IFACE_FREQ}")
echo "${WIFI_IFACE} already working in channel ${WIFI_IFACE_CHANNEL} (${WIFI_IFACE_FREQ} MHz)"
if [[ $CHANNEL == default ]]; then
echo "Use wifi adapter current channel $WIFI_IFACE_CHANNEL as target channel"
CHANNEL=$WIFI_IFACE_CHANNEL
fi
if [[ $WIFI_IFACE_CHANNEL -ne $CHANNEL ]]; then
echo "WARN: Wifi adapter already working in channel ${WIFI_IFACE_CHANNEL}, which is different than target channel $CHANNEL" >&2
fi
fi
echo "Creating a virtual WiFi interface... "
VWIFI_IFACE=$(alloc_new_vface_name)
if iw dev "${WIFI_IFACE}" interface add "${VWIFI_IFACE}" type __ap; then
# Successfully created virtual wifi interface
# if NM running, it will give the new virtual interface a random MAC. MAC will go back after setting NM unmanaged
sleep 2
echo "${VWIFI_IFACE} created"
else
VWIFI_IFACE=
if [[ ! -z ${VIRT_NAME} ]] && [[ ${#VIRT_NAME} -gt 15 ]]; then
die "Failed creating virtual WiFi interface. This is likely because you have set a long name for your virtual interface using --virt-name, try making it shorter'"
elif [[ -z ${VIRT_NAME} ]] && [[ ${#WIFI_IFACE} -gt 13 ]]; then
die "Failed creating virtual WiFi interface. This is likely because your interface name is too long. Try using '--virt-name <shorter interface name>'"
else
die "Failed creating virtual WiFi interface. Maybe your WiFi adapter does not fully support virtual interfaces. Try again with '--no-virt'"
fi
fi
AP_IFACE=${VWIFI_IFACE}
else # no virtual wifi interface, use wifi device interface itself
AP_IFACE=${WIFI_IFACE}
fi
if [[ $CHANNEL == default ]]; then
echo "Channel not specified, use default"
if [[ $FREQ_BAND == 2.4 ]]; then
CHANNEL=1
else
CHANNEL=36
fi
fi
}
decide_subnet_interface() {
if [[ $WIFI_IFACE ]]; then
echo "${AP_IFACE}"
else
echo "${TARGET_IFACE}"
fi
}
dealwith_mac() {
local VMAC
if [[ -n "$NEW_MACADDR" ]] ; then # user choose to set subnet mac
echo "Setting ${SUBNET_IFACE} new MAC address ${NEW_MACADDR} ..."
set_interface_mac "${SUBNET_IFACE}" "${NEW_MACADDR}" || die "Failed setting new MAC address"
elif [[ $VWIFI_IFACE ]]; then # user didn't choose to set mac, but using virtual wifi interface
VMAC=$(get_new_macaddr_according_to_existing "${WIFI_IFACE}")
if [[ "$VMAC" ]]; then
echo "Assigning MAC address $VMAC to virtual interface $VWIFI_IFACE according to $WIFI_IFACE ..."
set_interface_mac "$VWIFI_IFACE" "$VMAC"
fi
fi
}
write_hostapd_conf() {
cat <<- EOF > "$CONFDIR/hostapd.conf"
beacon_int=100
ssid=${SSID}
interface=${AP_IFACE}
driver=${DRIVER}
channel=${CHANNEL}
ctrl_interface=$CONFDIR/hostapd_ctrl
ctrl_interface_group=0
ignore_broadcast_ssid=$HIDDEN
ap_isolate=$ISOLATE_CLIENTS
EOF
if [[ -n "$COUNTRY" ]]; then
cat <<- EOF >> "$CONFDIR/hostapd.conf"
country_code=${COUNTRY}
ieee80211d=1
EOF
fi
if [[ $FREQ_BAND == 2.4 ]]; then
echo "hw_mode=g" >> "$CONFDIR/hostapd.conf"
else
echo "hw_mode=a" >> "$CONFDIR/hostapd.conf"
fi
if [[ $MAC_FILTER -eq 1 ]]; then
cat <<- EOF >> "$CONFDIR/hostapd.conf"
macaddr_acl=${MAC_FILTER}
accept_mac_file=${MAC_FILTER_ACCEPT}
EOF
fi
if [[ $HOTSPOT20 -eq 1 ]]; then
echo "hs20=1" >> "$CONFDIR/hostapd.conf"
fi
if [[ $IEEE80211N -eq 1 ]]; then
cat <<- EOF >> "$CONFDIR/hostapd.conf"
ieee80211n=1
ht_capab=${HT_CAPAB}
EOF
fi
if [[ $REQUIREHT -eq 1 ]]; then
echo "require_ht=1" >> "$CONFDIR/hostapd.conf"
fi
if [[ $IEEE80211AC -eq 1 ]]; then
echo "ieee80211ac=1" >> "$CONFDIR/hostapd.conf"
fi
if [[ $REQUIREVHT -eq 1 ]]; then
echo "require_vht=1" >> "$CONFDIR/hostapd.conf"
fi
if [[ -n "$VHT_CAPAB" ]]; then
echo "vht_capab=${VHT_CAPAB}" >> "$CONFDIR/hostapd.conf"
fi
if [[ $VHTCHANNELWIDTH -gt 0 ]]; then
cat <<- EOF >> "$CONFDIR/hostapd.conf"
vht_oper_chwidth=${VHTCHANNELWIDTH}
EOF
fi
if [[ $VHTSEG0CHINDEX -gt 0 ]]; then
cat <<- EOF >> "$CONFDIR/hostapd.conf"
vht_oper_centr_freq_seg0_idx=${VHTSEG0CHINDEX}
EOF
fi
if [[ $VHTSEG1CHINDEX -gt 0 ]]; then
cat <<- EOF >> "$CONFDIR/hostapd.conf"
vht_oper_centr_freq_seg1_idx=${VHTSEG1CHINDEX}
EOF
fi
if [[ $IEEE80211N -eq 1 ]] || [[ $IEEE80211AC -eq 1 ]]; then
echo "wmm_enabled=1" >> "$CONFDIR/hostapd.conf"
fi
if [[ -n "$PASSPHRASE" ]]; then
[[ "$WPA_VERSION" == "1+2" ]] && WPA_VERSION=3
if [[ $USE_PSK -eq 0 ]]; then
WPA_KEY_TYPE=passphrase
else
WPA_KEY_TYPE=psk
fi
cat <<- EOF >> "$CONFDIR/hostapd.conf"
wpa=${WPA_VERSION}
wpa_${WPA_KEY_TYPE}=${PASSPHRASE}
wpa_key_mgmt=WPA-PSK
wpa_pairwise=CCMP
rsn_pairwise=CCMP
EOF
else
echo "WARN: WiFi is not protected by password" >&2
fi
chmod 600 "$CONFDIR/hostapd.conf"
}
write_dnsmasq_conf() {
local i
if grep "^nobody:" /etc/group >/dev/null 2>&1 ; then
NOBODY_GROUP="nobody"
else
NOBODY_GROUP="nogroup"
fi
mkfifo "$CONFDIR/dnsmasq.log" || die "Failed creating pipe file for dnsmasq"
chown nobody "$CONFDIR/dnsmasq.log" || die "Failed changing dnsmasq log file owner"
cat "$CONFDIR/dnsmasq.log" &
cat <<- EOF > "$CONFDIR/dnsmasq.conf"
user=nobody
group=$NOBODY_GROUP
bind-dynamic
listen-address=${GATEWAY4}
interface=$SUBNET_IFACE
except-interface=lo
no-dhcp-interface=lo
dhcp-range=${GATEWAY4%.*}.10,${GATEWAY4%.*}.250,255.255.255.0
dhcp-option-force=option:router,${GATEWAY4}
#log-dhcp
log-facility=$CONFDIR/dnsmasq.log
bogus-priv
domain-needed
EOF
# 'log-dhcp'(Extra logging for DHCP) shows too much logs.
# if use '-d', 'log-facility' should = /dev/null
if [[ $SHARE_METHOD == "none" ]]; then
echo "no-resolv" >> "$CONFDIR/dnsmasq.conf"
echo "no-poll" >> "$CONFDIR/dnsmasq.conf"
fi
if [[ "$DHCP_DNS" != "no" ]]; then
if [[ "$DHCP_DNS" == "gateway" ]]; then
dns_offer="$GATEWAY4"
else
dns_offer="$DHCP_DNS"
fi
echo "dhcp-option-force=option:dns-server,${dns_offer}" >> "$CONFDIR/dnsmasq.conf"
fi
if [[ ! "$dnsmasq_NO_DNS" -eq 0 ]]; then
echo "port=0" >> "$CONFDIR/dnsmasq.conf"
fi
[[ -n "$MTU" ]] && echo "dhcp-option-force=option:mtu,${MTU}" >> "$CONFDIR/dnsmasq.conf"
[[ $ETC_HOSTS -eq 0 ]] && echo no-hosts >> "$CONFDIR/dnsmasq.conf"
[[ -n "$ADDN_HOSTS" ]] && echo "addn-hosts=${ADDN_HOSTS}" >> "$CONFDIR/dnsmasq.conf"
if [[ "$THISHOSTNAME" ]]; then
[[ "$THISHOSTNAME" == "-" ]] && THISHOSTNAME="$(cat /etc/hostname)"
echo "interface-name=$THISHOSTNAME,$SUBNET_IFACE" >> "$CONFDIR/dnsmasq.conf"
fi
if [[ ! "$SHOW_DNS_QUERY" -eq 0 ]]; then
echo log-queries=extra >> "$CONFDIR/dnsmasq.conf"
fi
if [[ $DNS ]]; then
DNS_count=$(echo "$DNS" | awk -F, '{print NF}')
for (( i=1;i<=DNS_count;i++ )); do
sep_ip_port "$(echo "$DNS" | cut -d, -f$i)" DNS_IP DNS_PORT
[[ "$DNS_PORT" ]] && DNS_PORT_D="#$DNS_PORT"
echo "server=${DNS_IP}${DNS_PORT_D}" >> "$CONFDIR/dnsmasq.conf"
done
cat <<- EOF >> "$CONFDIR/dnsmasq.conf"
no-resolv
no-poll
EOF
fi
if [[ $DNS_NOCACHE -eq 1 ]]; then
echo "cache-size=0" >> "$CONFDIR/dnsmasq.conf"
echo "no-negcache" >> "$CONFDIR/dnsmasq.conf"
fi
if [[ $IPV6 -eq 1 ]];then
cat <<- EOF >> "$CONFDIR/dnsmasq.conf"
listen-address=${GATEWAY6}
enable-ra
#quiet-ra
dhcp-range=interface:${SUBNET_IFACE},::,::ffff:ffff:ffff:ffff,constructor:${SUBNET_IFACE},ra-stateless,64
EOF
if [[ "$DHCP_DNS6" != "no" ]]; then
if [[ "$DHCP_DNS6" == "gateway" ]]; then
dns_offer6="[$GATEWAY6]"
else
dns_offer6="$DHCP_DNS6"
fi
echo "dhcp-option=option6:dns-server,${dns_offer6}" >> "$CONFDIR/dnsmasq.conf"
fi
fi
}
run_wifi_ap_processes() {
if [[ $NO_HAVEGED -eq 0 ]]; then
haveged_watchdog &
HAVEGED_WATCHDOG_PID=$!
echo "$HAVEGED_WATCHDOG_PID" > "$CONFDIR/haveged_watchdog.pid"
echo
echo "haveged_watchdog PID: $HAVEGED_WATCHDOG_PID"
fi
# start access point
#echo "hostapd command-line interface: hostapd_cli -p $CONFDIR/hostapd_ctrl"
# start hostapd (use stdbuf when available for no delayed output in programs that redirect stdout)
STDBUF_PATH=$(which stdbuf)
if [ $? -eq 0 ]; then
STDBUF_PATH=$STDBUF_PATH" -oL"
fi
echo
echo "Starting hostapd"
if which complain > /dev/null 2>&1; then
complain hostapd
fi
# hostapd '-P' works only when use '-B' (run in background)
$STDBUF_PATH hostapd $HOSTAPD_DEBUG_ARGS -P "$CONFDIR/hostapd.pid" "$CONFDIR/hostapd.conf" &
HOSTAPD_PID=$!
echo "$HOSTAPD_PID" > "$CONFDIR/hostapd.pid"
echo "hostapd PID: $HOSTAPD_PID"
#while [[ ! -f $CONFDIR/hostapd.pid ]]; do
# sleep 1
#done
#echo -n "hostapd PID: " ; cat $CONFDIR/hostapd.pid
pid_watchdog "$HOSTAPD_PID" 10 "hostapd failed. (tip: try '--hostapd-debug' to get some debug info)" &
sleep 3
}
start_dnsmasq() {
echo
echo "Starting dnsmasq"
if which complain > /dev/null 2>&1; then
# openSUSE's apparmor does not allow dnsmasq to read files.
# remove restriction.
complain dnsmasq
fi
# Using '-d'(no daemon) dnsmasq will not turn into 'nobody'
# '-x' works only when no '-d'
dnsmasq -k -C "$CONFDIR/dnsmasq.conf" -x "$CONFDIR/dnsmasq.pid" -l "$CONFDIR/dnsmasq.leases" &
#####DNSMASQ_PID=$! # only when with '-d'
######echo "dnsmasq PID: $DNSMASQ_PID" # only when with '-d'
i=0; while [[ ! -f "$CONFDIR/dnsmasq.pid" ]]; do
sleep 1
i=$((i + 1))
if [[ $i -gt 10 ]]; then die "Couldn't get dnsmasq PID" ; fi
done
DNSMASQ_PID="$(cat "$CONFDIR/dnsmasq.pid" )"
echo "dnsmasq PID: $DNSMASQ_PID"
######(wait $DNSMASQ_PID ; die "dnsmasq failed") & # wait can't deal with non-child
pid_watchdog "$DNSMASQ_PID" 9 "dnsmasq failed" &
sleep 2
}
check_rfkill_unblock_wifi() {
local PHY
if which rfkill > /dev/null 2>&1 ; then
PHY=$(get_interface_phy_device "${SUBNET_IFACE}")
[[ -n $PHY ]] && rfkill unblock $(rfkill | grep "$PHY" | awk '{print $1}') >/dev/null 2>&1
fi
}
#=========== Above are functions ======================
#=========== Executing begin ==============================
# if empty option, show usage and exit
check_empty_option "$@"
# TODO: are some global variables are still defined in those following code?
define_global_variables
ARGS=( "$@" )
parse_user_options "$@"
# TODO: detect user option conflict
TMPDIR="$(decide_tmpdir)"
# if user choose to deal with running instances, will output some info then exit after this
# NOTE above don't require root
check_other_functions
# NOTE below require root
# if user choose to daemonize, will start new background process and exit this
daemonizing_check
# check if wifi will work on this system and user settings
[[ $WIFI_IFACE ]] && check_wifi_settings
[[ -n "$NEW_MACADDR" ]] && check_if_new_mac_valid # check NEW_MACADDR. will exit if not valid
# checks finished
## ===== Above don't echo anything if no warning or error====================
## ========================================================
phead
phead2
echo
echo "PID: $$"
TARGET_IFACE="$(decide_target_interface)" || exit 1 # judge wired (-i CONN_IFACE) or wireless hotspot (--ap $WIFI_IFACE)
echo "Target interface is ${TARGET_IFACE} ($(get_interface_mac "$TARGET_IFACE")) "
show_interface_pci_info "$TARGET_IFACE"
if [[ "$MAC_USE_RANDOM" -eq 1 ]] ; then
NEW_MACADDR="$(generate_random_mac)"
echo "Use random MAC address $NEW_MACADDR"
fi
decide_ip_addresses # ip 4 & 6 lan addresses
# if user choose to make DHCP to tell clients to use other DNS, we don't have to serve DNS
[[ $DHCP_DNS != 'gateway' && $DHCP_DNS6 != 'gateway' ]] && dnsmasq_NO_DNS=1
#===========================================================
#==== begin to do some change on config files and system===
init_trap
# NOTE function die() is designed not to be used before init_trap() executed
init_conf_dirs # CONFDIR , COMMON_CONFDIR . make dir
[[ $WIFI_IFACE ]] && prepare_wifi_interface # this will create virtual ap interface (if needed) and set VWIFI_IFACE and AP_IFACE (if success)
SUBNET_IFACE="$(decide_subnet_interface)" # SUBNET_IFACE can be TARGET_IFACE (wired) or AP_IFACE (ap) .this is after prepare_wifi_interface()
echo "$SUBNET_IFACE" > "$CONFDIR/subn_iface"
# if virtual wifi interface, will be destroyed, so only need to save status when not
[[ -z $VWIFI_IFACE ]] && backup_interface_status
# TODO: should these 2 before calling prepare_wifi_interface ? in check_wifi_settings() ?
# set iw country code
if [[ $WIFI_IFACE && -n "$COUNTRY" && $USE_IWCONFIG -eq 0 ]]; then
iw reg set "$COUNTRY" || die "Failed setting country code"
fi
# judge channel availability after changing country code
if [[ $WIFI_IFACE ]] ; then
can_transmit_to_channel "${AP_IFACE}" ${CHANNEL} || die "Your adapter can not transmit to channel ${CHANNEL}, frequency band ${FREQ_BAND}GHz."
fi
[[ $WIFI_IFACE ]] && write_hostapd_conf
#===================================================
#===================================================
# set interface unmanaged by networkManager
if is_nm_running && nm_knows "$TARGET_IFACE"; then # if nm knows target iface, should know subnet iface too. but need to wait until nm finds subnet iface (waiting code is in nm_set_unmanaged()
nm_set_unmanaged "${SUBNET_IFACE}" # will write NM_UNM_LIST
fi
[[ $NO_DNSMASQ -eq 0 ]] && write_dnsmasq_conf
#===========================
# initialize subnet interface
# take subnet interface down first
ip link set down dev "${SUBNET_IFACE}" || die "Failed setting ${SUBNET_IFACE} down"
# flush old IPs of subnet interface
ip addr flush "${SUBNET_IFACE}" || die "Failed flush ${SUBNET_IFACE} IP"
dealwith_mac # setting MAC should be after setting NM unmanaged
[[ $WIFI_IFACE ]] && check_rfkill_unblock_wifi
echo
iptables --version
echo "Notice: Not showing all operations done to iptables rules"
if [[ "$IPV6" -eq 0 ]]; then
IP_VERs=("4")
else
IP_VERs=("4" "6")
fi
disable_unwanted_forwarding
# bring subnet interface up
ip link set up dev "${SUBNET_IFACE}" || die "Failed bringing ${SUBNET_IFACE} up"
# hostapd , haveged
[[ $WIFI_IFACE ]] && run_wifi_ap_processes
# add ipv4 address to subnet interface
ip -4 addr add ${GATEWAY4}/24 broadcast ${GATEWAY4%.*}.255 dev ${SUBNET_IFACE} || die "Failed setting ${SUBNET_IFACE} IPv4 address"
set_ipv6_bits
# add ipv6 address to subnet interface
if [[ $IPV6 -eq 1 ]] ; then
ip -6 addr add ${GATEWAY6}/64 dev ${SUBNET_IFACE} || die "Failed setting ${SUBNET_IFACE} IPv6 address"
fi
# enable Internet sharing
if [[ "$SHARE_METHOD" == "none" ]]; then
echo "No Internet sharing"
[[ "$BANLAN" -eq 1 ]] && start_ban_lan
elif [[ "$SHARE_METHOD" == "nat" ]]; then
[[ "$INTERNET_IFACE" && "$dnsmasq_NO_DNS" -eq 0 ]] && echo -e "\nWARN: You specified Internet interface but this host is providing local DNS. In some unexpected case (eg. mistaken configurations), queries may leak to other interfaces, which you should be aware of.\n" >&2
start_nat
[[ "$BANLAN" -eq 1 ]] && start_ban_lan
echo 1 > "/proc/sys/net/ipv4/ip_forward" || die "Failed enabling system ipv4 forwarding" # TODO maybe uneeded in '--no4' mode
if [[ $IPV6 -eq 1 ]]; then
echo 1 > "/proc/sys/net/ipv6/conf/all/forwarding" || die "Failed enabling system ipv6 forwarding" # TODO if '-o' used, set only 2 interfaces' bits
fi
# to enable clients to establish PPTP connections we must
# load nf_nat_pptp module
modprobe nf_nat_pptp > /dev/null 2>&1 && echo "Loaded kernel module nf_nat_pptp"
elif [[ "$SHARE_METHOD" == "redsocks" ]]; then
if [[ $IPV6 -eq 1 ]]; then
echo 1 > "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/forwarding" || die "Failed enabling $SUBNET_IFACE ipv6 forwarding" # to set NA router bit
fi
[[ "$dnsmasq_NO_DNS" -eq 0 && ! $DNS ]] && echo -e "\nWARN: You are using in transparent proxy mode but this host is providing local DNS. In some unexpected case (eg. mistaken configurations), queries may leak to other interfaces, which you should be aware of.\n" >&2
[[ "$BANLAN" -eq 1 ]] && start_ban_lan
start_redsocks
fi
# start dhcp + dns (optional)
# allow dns port input even if we don't run dnsmasq
# user can serve their own dns server
[[ "$DHCP_DNS" == "gateway" || "$DHCP_DNS6" == "gateway" ]] && allow_dns_port
[[ "$CATCH_DNS" -eq 1 ]] && start_catch_dns
[[ $NO_DNSMASQ -eq 0 ]] && ( allow_dhcp ; start_dnsmasq )
echo ""
is_firewalld_running && firewalld_add_tmpzone
echo
echo "== Setting up completed, now linux-router should be working =="
#============================================================
#============================================================
#============================================================
show_qr() {
local T S P H
S="$SSID"
if [[ -n "$PASSPHRASE" ]]; then
T="WPA"
P="$PASSPHRASE"
else
T="nopass"
fi
[[ "$HIDDEN" -eq 1 ]] && H="true"
echo "Scan QR code on phone to connect to WiFi"
qrencode -m 2 -t ANSIUTF8 "WIFI:T:${T};S:${S};P:${P};H:${H};"
echo "Use this command to save QR code to image file:"
echo " qrencode -m 2 -o <file> \"WIFI:T:${T};S:${S};P:${P};H:${H};\""
echo
}
[[ "$QR" -eq 1 ]] && show_qr
# need loop to keep this script running
bash -c "while :; do sleep 8000 ; done " &
KEEP_RUNNING_PID=$!
echo "$KEEP_RUNNING_PID" > "$CONFDIR/keep_running.pid"
wait "$KEEP_RUNNING_PID"
clean_exit