From 828a4b1b05c326074f9c3fd579b98dcb0b942d0f Mon Sep 17 00:00:00 2001 From: Gabriel Evan Date: Thu, 1 Jul 2021 19:13:07 +0700 Subject: [PATCH] Original script --- lnxrouter | 2119 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2119 insertions(+) create mode 100755 lnxrouter diff --git a/lnxrouter b/lnxrouter new file mode 100755 index 0000000..064c1d3 --- /dev/null +++ b/lnxrouter @@ -0,0 +1,2119 @@ +#!/bin/bash + +# TODO: showing version (or git commit) on running +VERSION=0.6.0 +PROGNAME="$(basename $0)" + +export LC_ALL=C + +SCRIPT_UMASK=0122 +umask $SCRIPT_UMASK + +usage() { + cat << EOF +linux-router $VERSION (https://github.com/garywill/linux-router) +Released under LGPL, with no warranty. Use on your own risk. + +Usage: $PROGNAME + +Options: + -h, --help Show this help + --version Print version number + + -i Interface to make NATed sub-network, + and to provide Internet to + (To create Wifi hotspot use '--ap' instead) + -o 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 (See Notice 1) + --ban-priv Disallow clients to access my private network + + -g 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) + (See Notice 1). Usually used with '-6' + + --p6 Set IPv6 LAN address prefix (length 64) + (example: 'fd00:0:0:5::' or '5' shortly) + Using this enables '-6' + + --dns || + DNS server's upstream DNS. + Use ',' to seperate multiple servers + (default: use /etc/resolve.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 + --dhcp-dns |no + Set IPv4 DNS offered by DHCP (default: this host) + --dhcp-dns6 |no + Set IPv6 DNS offered by DHCP (RA) + (default: this host) + (Note IPv6 addresses need '[]' around) + --hostname 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 DNS server will take into account additional + hosts file + + --mac Set MAC address + --random-mac Use random MAC address + + --tp Transparent proxy, + redirect non-LAN TCP and UDP traffic to port. + (usually used with '--dns') + + Wifi hotspot options: + --ap + Create Wifi access point + -p, --password + Wifi password + --qr Show Wifi QR code in terminal + + --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 + -c Channel number (default: 1) + --country Set two-letter country code for regularity + (example: US) + --freq-band Set frequency band: 2.4 or 5 (default: 2.4) + --driver Choose your WiFi adapter driver (default: nl80211) + -w '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 1 or 2. Passes -d or -dd to hostapd + --isolate-clients Disable wifi communication between clients + + --ieee80211n Enable IEEE 802.11n (HT) + --ieee80211ac Enable IEEE 802.11ac (VHT) + --ht_capab HT capabilities (default: [HT40+]) + --vht_capab VHT capabilities + + --no-haveged Do not run haveged automatically when needed + + Instance managing: + --daemon Run in background + -l, --list-running Show running instances + --lc, --list-clients + List clients of an instance. Or list neighbors of + an interface, even if it isn't handled by us. + (passive mode) + --stop Stop a running instance + For you can use PID or subnet interface name. + You can get them with '--list-running' + + Notice 1: This script assume your host's default policy won't forward + packets, so the script won't explictly ban forwarding in any + mode. In some unexpected case may cause unwanted packets + leakage between 2 networks, which you should be aware of if you + want isolated network + +Examples: + $PROGNAME -i eth1 + $PROGNAME --ap wlan0 MyAccessPoint + $PROGNAME --ap wlan0 MyAccessPoint -p MyPassPhrase + $PROGNAME -i eth1 --tp --dns +EOF +} + +check_empty_option(){ + if [[ "$1" == "" ]]; then + usage + exit 0 + fi +} + + +define_global_variables(){ + # user options + GATEWAY= # 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= + 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= + + + ##### wifi hotspot + # user options + HIDDEN=0 # hidden wifi hotspot + WIFI_IFACE= + CHANNEL=default + WPA_VERSION=2 + MAC_FILTER=0 + MAC_FILTER_ACCEPT=/etc/hostapd/hostapd.accept + IEEE80211N=0 + IEEE80211AC=0 + HT_CAPAB='[HT40+]' + VHT_CAPAB= + 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 + 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= + NM_RUNNING=0 + NM_UNM_LIST= # it's called "list" but for now one interface + XT_COMMENT=1 +} + +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 + echo "" + echo "WARN: Since you're using in this mode, make sure you've read Notice 1" >&2 + echo "" + ;; + -n) + shift + SHARE_METHOD=none + echo "" + echo "WARN: Since you're using in this mode, make sure you've read Notice 1" >&2 + echo "" + ;; + --ban-priv) + shift + BANLAN=1 + ;; + --tp) + shift + TP_PORT="$1" + SHARE_METHOD=redsocks + shift + ;; + + + -g) + shift + GATEWAY="$1" + shift + ;; + -6) + shift + IPV6=1 + ;; + --no4) + shift + NO4=1 + echo "" + echo "WARN: Since you're using in this mode, make sure you've read Notice 1" >&2 + echo "" + ;; + --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 + ;; + + --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 + ;; + -w) + shift + WPA_VERSION="$1" + [[ "$WPA_VERSION" == "2+1" ]] && WPA_VERSION=1+2 + shift + ;; + + --ieee80211n) + shift + IEEE80211N=1 + ;; + --ieee80211ac) + shift + IEEE80211AC=1 + ;; + --ht_capab) + shift + HT_CAPAB="$1" + shift + ;; + --vht_capab) + shift + VHT_CAPAB="$1" + shift + ;; + --driver) + shift + DRIVER="$1" + shift + ;; + --no-virt) + shift + NO_VIRT=1 + ;; + + --country) + shift + COUNTRY="$1" + shift + ;; + --freq-band) + shift + FREQ_BAND="$1" + shift + ;; + --no-haveged) + shift + NO_HAVEGED=1 + ;; + --hostapd-debug) + shift + if [ "x$1" = "x1" ]; then + HOSTAPD_DEBUG_ARGS="-d" + elif [ "x$1" = "x2" ]; 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 + # input 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}" ]] +} + +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 + if [[ $FREQ_BAND == 2.4 ]]; then + CHANNEL_INFO=$(get_adapter_info ${IFACE} | grep " 24[0-9][0-9] MHz \[${CHANNEL_NUM}\]") + else + CHANNEL_INFO=$(get_adapter_info ${IFACE} | grep " \(49[0-9][0-9]\|5[0-9]\{3\}\) MHz \[${CHANNEL_NUM}\]") + fi + [[ -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_5ghz_frequency() { + [[ $1 =~ ^(49[0-9]{2})|(5[0-9]{3})$ ]] +} + +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" +} + +alloc_new_vface_name() { # only for wifi + local i=0 + local v_iface_name= + while :; do + v_iface_name="x$i${WIFI_IFACE}" + if ! is_interface ${v_iface_name} && [[ ! -f $COMMON_CONFDIR/vfaces/${v_iface_name} ]]; then + mkdir -p $COMMON_CONFDIR/vfaces + touch $COMMON_CONFDIR/vfaces/${v_iface_name} + echo "${v_iface_name}" + return + fi + i=$((i + 1)) + done +} + +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 + +} +#======== + + +# only support NetworkManager >= 0.9.9 +is_nm_running() { + if (which nmcli >/dev/null 2>&1 ) && (nmcli -t -f RUNNING g 2>&1 | grep -E '^running$' >/dev/null 2>&1 ) ; then + echo 1 + else + echo 0 + fi +} + +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 +} +#========= +iptables_() +{ + if [[ $XT_COMMENT -eq 1 ]]; then + iptables -w $@ -m comment --comment "lnxrouter-$$-$SUBNET_IFACE" + else + iptables -w $@ + fi + return $? +} +ip6tables_() +{ + if [[ $XT_COMMENT -eq 1 ]]; then + ip6tables -w $@ -m comment --comment "lnxrouter-$$-$SUBNET_IFACE" + else + ip6tables -w $@ + fi + return $? +} + +start_nat() { + if [[ $INTERNET_IFACE ]]; then + IPTABLES_NAT_OUT="-o ${INTERNET_IFACE}" + IPTABLES_NAT_IN="-i ${INTERNET_IFACE}" + MASQUERADE_NOTOUT="" + else + MASQUERADE_NOTOUT="! -o ${SUBNET_IFACE}" + fi + echo + echo "iptables: NAT " + if [[ $NO4 -eq 0 ]]; then + iptables_ -v -t nat -I POSTROUTING -s ${GATEWAY%.*}.0/24 $IPTABLES_NAT_OUT $MASQUERADE_NOTOUT ! -d ${GATEWAY%.*}.0/24 -j MASQUERADE || die + iptables_ -v -I FORWARD -i ${SUBNET_IFACE} $IPTABLES_NAT_OUT -s ${GATEWAY%.*}.0/24 -j ACCEPT || die + iptables_ -v -I FORWARD -o ${SUBNET_IFACE} $IPTABLES_NAT_IN -d ${GATEWAY%.*}.0/24 -j ACCEPT || die + fi + if [[ $IPV6 -eq 1 ]]; then + ip6tables_ -v -t nat -I POSTROUTING -s ${PREFIX6}/64 $IPTABLES_NAT_OUT $MASQUERADE_NOTOUT ! -d ${PREFIX6}/64 -j MASQUERADE || die + ip6tables_ -v -I FORWARD -i ${SUBNET_IFACE} $IPTABLES_NAT_OUT -s ${PREFIX6}/64 -j ACCEPT || die + ip6tables_ -v -I FORWARD -o ${SUBNET_IFACE} $IPTABLES_NAT_IN -d ${PREFIX6}/64 -j ACCEPT || die + fi +} +stop_nat() { + echo "iptables: stop NAT" + if [[ $NO4 -eq 0 ]]; then + iptables_ -t nat -D POSTROUTING -s ${GATEWAY%.*}.0/24 $IPTABLES_NAT_OUT $MASQUERADE_NOTOUT ! -d ${GATEWAY%.*}.0/24 -j MASQUERADE + iptables_ -D FORWARD -i ${SUBNET_IFACE} $IPTABLES_NAT_OUT -s ${GATEWAY%.*}.0/24 -j ACCEPT + iptables_ -D FORWARD -o ${SUBNET_IFACE} $IPTABLES_NAT_IN -d ${GATEWAY%.*}.0/24 -j ACCEPT + fi + if [[ $IPV6 -eq 1 ]]; then + ip6tables_ -t nat -D POSTROUTING -s ${PREFIX6}/64 $IPTABLES_NAT_OUT $MASQUERADE_NOTOUT ! -d ${PREFIX6}/64 -j MASQUERADE + ip6tables_ -D FORWARD -i ${SUBNET_IFACE} $IPTABLES_NAT_OUT -s ${PREFIX6}/64 -j ACCEPT + ip6tables_ -D FORWARD -o ${SUBNET_IFACE} $IPTABLES_NAT_IN -d ${PREFIX6}/64 -j ACCEPT + fi +} + +start_ban_lan() { + echo + echo "iptables: Disallow clients to access LAN" + iptables_ -N BANLAN-f-${SUBNET_IFACE} || die + iptables_ -v -I BANLAN-f-${SUBNET_IFACE} -d 0.0.0.0/8 -j REJECT || die # TODO: use array + iptables_ -v -I BANLAN-f-${SUBNET_IFACE} -d 10.0.0.0/8 -j REJECT || die + iptables_ -v -I BANLAN-f-${SUBNET_IFACE} -d 100.64.0.0/10 -j REJECT || die + iptables_ -v -I BANLAN-f-${SUBNET_IFACE} -d 127.0.0.0/8 -j REJECT || die + iptables_ -v -I BANLAN-f-${SUBNET_IFACE} -d 169.254.0.0/16 -j REJECT || die + iptables_ -v -I BANLAN-f-${SUBNET_IFACE} -d 172.16.0.0/12 -j REJECT || die + iptables_ -v -I BANLAN-f-${SUBNET_IFACE} -d 192.168.0.0/16 -j REJECT || die + iptables_ -v -I BANLAN-f-${SUBNET_IFACE} -d 224.0.0.0/4 -j REJECT || die + iptables_ -v -I BANLAN-f-${SUBNET_IFACE} -d 255.255.255.255 -j REJECT || die + + iptables_ -I FORWARD -i ${SUBNET_IFACE} -j BANLAN-f-${SUBNET_IFACE} || die + + iptables_ -N BANLAN-i-${SUBNET_IFACE} + #iptables_ -v -I BANLAN-i-${SUBNET_IFACE} -i ${SUBNET_IFACE} -j REJECT || die + iptables_ -v -I BANLAN-i-${SUBNET_IFACE} -i ${SUBNET_IFACE} ! -p icmp -j REJECT || die + # TODO: ipv6 need icmp to function. maybe we can block some unneeded icmp to improve security + + iptables_ -I INPUT -i ${SUBNET_IFACE} -j BANLAN-i-${SUBNET_IFACE} || die + + if [[ $IPV6 -eq 1 ]]; then + ip6tables_ -N BANLAN-f-${SUBNET_IFACE} || die + ip6tables_ -v -I BANLAN-f-${SUBNET_IFACE} -d fc00::/7 -j REJECT || die + ip6tables_ -v -I BANLAN-f-${SUBNET_IFACE} -d fe80::/10 -j REJECT || die + ip6tables_ -v -I BANLAN-f-${SUBNET_IFACE} -d ff00::/8 -j REJECT || die + ip6tables_ -v -I BANLAN-f-${SUBNET_IFACE} -d ::1 -j REJECT || die + ip6tables_ -v -I BANLAN-f-${SUBNET_IFACE} -d ::/128 -j REJECT || die + ip6tables_ -v -I BANLAN-f-${SUBNET_IFACE} -d ::ffff:0:0/96 -j REJECT || die + ip6tables_ -v -I BANLAN-f-${SUBNET_IFACE} -d ::ffff:0:0:0/96 -j REJECT || die + + ip6tables_ -I FORWARD -i ${SUBNET_IFACE} -j BANLAN-f-${SUBNET_IFACE} || die + + ip6tables_ -N BANLAN-i-${SUBNET_IFACE} || die + #ip6tables_ -v -I BANLAN-i-${SUBNET_IFACE} -i ${SUBNET_IFACE} -j REJECT || die + ip6tables_ -v -I BANLAN-i-${SUBNET_IFACE} -i ${SUBNET_IFACE} ! -p icmpv6 -j REJECT || die + + ip6tables_ -I INPUT -i ${SUBNET_IFACE} -j BANLAN-i-${SUBNET_IFACE} || die + fi +} +stop_ban_lan() { + echo "iptables: Unban clients' LAN access" + + iptables_ -D FORWARD -i ${SUBNET_IFACE} -j BANLAN-f-${SUBNET_IFACE} + + iptables_ -F BANLAN-f-${SUBNET_IFACE} + iptables_ -X BANLAN-f-${SUBNET_IFACE} + + iptables_ -D INPUT -i ${SUBNET_IFACE} -j BANLAN-i-${SUBNET_IFACE} + + iptables_ -F BANLAN-i-${SUBNET_IFACE} + iptables_ -X BANLAN-i-${SUBNET_IFACE} + if [[ $IPV6 -eq 1 ]]; then + ip6tables_ -D FORWARD -i ${SUBNET_IFACE} -j BANLAN-f-${SUBNET_IFACE} + + ip6tables_ -F BANLAN-f-${SUBNET_IFACE} + ip6tables_ -X BANLAN-f-${SUBNET_IFACE} + + ip6tables_ -D INPUT -i ${SUBNET_IFACE} -j BANLAN-i-${SUBNET_IFACE} + + ip6tables_ -F BANLAN-i-${SUBNET_IFACE} + ip6tables_ -X BANLAN-i-${SUBNET_IFACE} + fi +} + +allow_dns_port() { + echo + echo "iptables: allow DNS" + iptables_ -v -I INPUT -i ${SUBNET_IFACE} -s ${GATEWAY%.*}.0/24 -d ${GATEWAY} -p tcp -m tcp --dport 53 -j ACCEPT || die + iptables_ -v -I INPUT -i ${SUBNET_IFACE} -s ${GATEWAY%.*}.0/24 -d ${GATEWAY} -p udp -m udp --dport 53 -j ACCEPT || die + if [[ $IPV6 -eq 1 ]]; then + ip6tables_ -v -I INPUT -i ${SUBNET_IFACE} -s ${PREFIX6}/64 -d ${GATEWAY6} -p tcp -m tcp --dport 53 -j ACCEPT || die + ip6tables_ -v -I INPUT -i ${SUBNET_IFACE} -s ${PREFIX6}/64 -d ${GATEWAY6} -p udp -m udp --dport 53 -j ACCEPT || die + fi +} +unallow_dns_port() { + echo "iptables: unallow DNS" + iptables_ -D INPUT -i ${SUBNET_IFACE} -s ${GATEWAY%.*}.0/24 -d ${GATEWAY} -p tcp -m tcp --dport 53 -j ACCEPT + iptables_ -D INPUT -i ${SUBNET_IFACE} -s ${GATEWAY%.*}.0/24 -d ${GATEWAY} -p udp -m udp --dport 53 -j ACCEPT + if [[ $IPV6 -eq 1 ]]; then + ip6tables_ -D INPUT -i ${SUBNET_IFACE} -s ${PREFIX6}/64 -d ${GATEWAY6} -p tcp -m tcp --dport 53 -j ACCEPT + ip6tables_ -D INPUT -i ${SUBNET_IFACE} -s ${PREFIX6}/64 -d ${GATEWAY6} -p udp -m udp --dport 53 -j ACCEPT + fi +} + +start_catch_dns() { + echo + echo "iptables: redirect all TCP/UDP packet that destination port is 53" + iptables_ -v -t nat -I PREROUTING -i ${SUBNET_IFACE} ! -d ${GATEWAY} -p udp -m udp --dport 53 -j REDIRECT --to-ports 53 || die + iptables_ -v -t nat -I PREROUTING -i ${SUBNET_IFACE} ! -d ${GATEWAY} -p tcp -m tcp --dport 53 -j REDIRECT --to-ports 53 || die + if [[ $IPV6 -eq 1 ]]; then + ip6tables_ -v -t nat -I PREROUTING -i ${SUBNET_IFACE} ! -d ${GATEWAY6} -p udp -m udp --dport 53 -j REDIRECT --to-ports 53 || die + ip6tables_ -v -t nat -I PREROUTING -i ${SUBNET_IFACE} ! -d ${GATEWAY6} -p tcp -m tcp --dport 53 -j REDIRECT --to-ports 53 || die + fi +} +stop_catch_dns() { + echo "iptables: stop redirecting DNS queries" + iptables_ -t nat -D PREROUTING -i ${SUBNET_IFACE} ! -d ${GATEWAY} -p udp -m udp --dport 53 -j REDIRECT --to-ports 53 + iptables_ -t nat -D PREROUTING -i ${SUBNET_IFACE} ! -d ${GATEWAY} -p tcp -m tcp --dport 53 -j REDIRECT --to-ports 53 + if [[ $IPV6 -eq 1 ]]; then + ip6tables_ -t nat -D PREROUTING -i ${SUBNET_IFACE} ! -d ${GATEWAY6} -p udp -m udp --dport 53 -j REDIRECT --to-ports 53 + ip6tables_ -t nat -D PREROUTING -i ${SUBNET_IFACE} ! -d ${GATEWAY6} -p tcp -m tcp --dport 53 -j REDIRECT --to-ports 53 + fi +} + +allow_dhcp() { + echo + echo "iptables: allow dhcp" + iptables_ -v -I INPUT -i ${SUBNET_IFACE} -p udp -m udp --dport 67 -j ACCEPT || die + if [[ $IPV6 -eq 1 ]]; then + ip6tables_ -v -I INPUT -i ${SUBNET_IFACE} -p udp -m udp --dport 547 -j ACCEPT || die + fi +} +unallow_dhcp() { + echo "iptables: unallow dhcp" + iptables_ -D INPUT -i ${SUBNET_IFACE} -p udp -m udp --dport 67 -j ACCEPT + if [[ $IPV6 -eq 1 ]]; then + ip6tables_ -D INPUT -i ${SUBNET_IFACE} -p udp -m udp --dport 547 -j ACCEPT + fi +} + +# TODO: use 'DNAT' instead of '--to-ports' to support other IP +start_redsocks() { + echo + echo "iptables: transparent proxy non-LAN TCP/UDP traffic to port ${TP_PORT}" + if [[ $NO4 -eq 0 ]]; then + iptables_ -t nat -N REDSOCKS-${SUBNET_IFACE} || die + iptables_ -t nat -A REDSOCKS-${SUBNET_IFACE} -d 0.0.0.0/8 -j RETURN || die + iptables_ -t nat -A REDSOCKS-${SUBNET_IFACE} -d 10.0.0.0/8 -j RETURN || die + iptables_ -t nat -A REDSOCKS-${SUBNET_IFACE} -d 100.64.0.0/10 -j RETURN || die + iptables_ -t nat -A REDSOCKS-${SUBNET_IFACE} -d 127.0.0.0/8 -j RETURN || die + iptables_ -t nat -A REDSOCKS-${SUBNET_IFACE} -d 169.254.0.0/16 -j RETURN || die + iptables_ -t nat -A REDSOCKS-${SUBNET_IFACE} -d 172.16.0.0/12 -j RETURN || die + iptables_ -t nat -A REDSOCKS-${SUBNET_IFACE} -d 192.168.0.0/16 -j RETURN || die + iptables_ -t nat -A REDSOCKS-${SUBNET_IFACE} -d 224.0.0.0/4 -j RETURN || die + iptables_ -t nat -A REDSOCKS-${SUBNET_IFACE} -d 255.255.255.255 -j RETURN || die + + iptables_ -v -t nat -A REDSOCKS-${SUBNET_IFACE} -p tcp -j REDIRECT --to-ports ${TP_PORT} || die + iptables_ -v -t nat -A REDSOCKS-${SUBNET_IFACE} -p udp -j REDIRECT --to-ports ${TP_PORT} || die + + iptables_ -v -t nat -I PREROUTING -i ${SUBNET_IFACE} -s ${GATEWAY%.*}.0/24 -j REDSOCKS-${SUBNET_IFACE} || die + + iptables_ -v -I INPUT -i ${SUBNET_IFACE} -s ${GATEWAY%.*}.0/24 -p tcp -m tcp --dport ${TP_PORT} -j ACCEPT || die + iptables_ -v -I INPUT -i ${SUBNET_IFACE} -s ${GATEWAY%.*}.0/24 -p udp -m udp --dport ${TP_PORT} -j ACCEPT || die + fi + if [[ $IPV6 -eq 1 ]]; then + ip6tables_ -t nat -N REDSOCKS-${SUBNET_IFACE} || die + ip6tables_ -t nat -A REDSOCKS-${SUBNET_IFACE} -d fc00::/7 -j RETURN || die + ip6tables_ -t nat -A REDSOCKS-${SUBNET_IFACE} -d fe80::/10 -j RETURN || die + ip6tables_ -t nat -A REDSOCKS-${SUBNET_IFACE} -d ff00::/8 -j RETURN || die + ip6tables_ -t nat -A REDSOCKS-${SUBNET_IFACE} -d ::1 -j RETURN || die + ip6tables_ -t nat -A REDSOCKS-${SUBNET_IFACE} -d :: -j RETURN || die + + ip6tables_ -v -t nat -A REDSOCKS-${SUBNET_IFACE} -p tcp -j REDIRECT --to-ports ${TP_PORT} || die + ip6tables_ -v -t nat -A REDSOCKS-${SUBNET_IFACE} -p udp -j REDIRECT --to-ports ${TP_PORT} || die + + ip6tables_ -v -t nat -I PREROUTING -i ${SUBNET_IFACE} -s ${PREFIX6}/64 -j REDSOCKS-${SUBNET_IFACE} || die + + ip6tables_ -v -I INPUT -i ${SUBNET_IFACE} -s ${PREFIX6}/64 -p tcp -m tcp --dport ${TP_PORT} -j ACCEPT || die + ip6tables_ -v -I INPUT -i ${SUBNET_IFACE} -s ${PREFIX6}/64 -p udp -m udp --dport ${TP_PORT} -j ACCEPT || die + fi +} +stop_redsocks() { + echo "iptables: stop transparent proxy" + if [[ $NO4 -eq 0 ]]; then + iptables_ -t nat -D PREROUTING -i ${SUBNET_IFACE} -s ${GATEWAY%.*}.0/24 -j REDSOCKS-${SUBNET_IFACE} + iptables_ -t nat -F REDSOCKS-${SUBNET_IFACE} + iptables_ -t nat -X REDSOCKS-${SUBNET_IFACE} + + iptables_ -D INPUT -i ${SUBNET_IFACE} -s ${GATEWAY%.*}.0/24 -p tcp -m tcp --dport ${TP_PORT} -j ACCEPT + iptables_ -D INPUT -i ${SUBNET_IFACE} -s ${GATEWAY%.*}.0/24 -p udp -m udp --dport ${TP_PORT} -j ACCEPT + fi + if [[ $IPV6 -eq 1 ]]; then + ip6tables_ -t nat -D PREROUTING -i ${SUBNET_IFACE} -s ${PREFIX6}/64 -j REDSOCKS-${SUBNET_IFACE} + ip6tables_ -t nat -F REDSOCKS-${SUBNET_IFACE} + ip6tables_ -t nat -X REDSOCKS-${SUBNET_IFACE} + + ip6tables_ -D INPUT -i ${SUBNET_IFACE} -s ${PREFIX6}/64 -p tcp -m tcp --dport ${TP_PORT} -j ACCEPT + ip6tables_ -D INPUT -i ${SUBNET_IFACE} -s ${PREFIX6}/64 -p udp -m udp --dport ${TP_PORT} -j ACCEPT + fi +} +#--------------------------------------- +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 $pid $pn" && rm $x ) || echo "Failed to kill $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} + + 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() { + + if [[ "$SHARE_METHOD" == "nat" ]]; then + stop_nat + elif [[ "$SHARE_METHOD" == "redsocks" ]]; then + stop_redsocks + fi + + if [[ "$DHCP_DNS" == "gateway" || "$DHCP_DNS6" == "gateway" ]]; then + unallow_dns_port + fi + + [[ "$CATCH_DNS" -eq 1 ]] && stop_catch_dns + + + if [[ $NO_DNSMASQ -eq 0 ]]; then + unallow_dhcp + fi + + [[ "$BANLAN" -eq 1 ]] && stop_ban_lan +} + +cleanup() { + trap "" SIGINT SIGUSR1 SIGUSR2 EXIT SIGTERM + echo + echo + echo "Doing cleanup.. " + kill_processes + clean_iptables 2> /dev/null + _cleanup 2> /dev/null + + pgid=$(ps opgid= $$ |awk '{print $1}' ) + 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.XXX)" || 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 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 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 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 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 "You must run it as root." >&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') ..." >&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 [[ $CHANNEL == default ]]; then + if [[ $FREQ_BAND == 2.4 ]]; then + CHANNEL=1 + else + CHANNEL=36 + fi + 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 +} + +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 $GATEWAY ]]; then + GATEWAY="$(generate_random_ip4)" + echo "Use random LAN IPv4 address $GATEWAY" + elif [[ ! "$GATEWAY" =~ "." ]]; then + GATEWAY="192.168.${GATEWAY}.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 +} + +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 + if is_interface_wifi_connected ${WIFI_IFACE}; then + WIFI_IFACE_FREQ=$(iw dev ${WIFI_IFACE} link | grep -i freq | awk '{print $2}') + WIFI_IFACE_CHANNEL=$(ieee80211_frequency_to_channel ${WIFI_IFACE_FREQ}) + echo "${WIFI_IFACE} already in channel ${WIFI_IFACE_CHANNEL} (${WIFI_IFACE_FREQ} MHz)" + if is_5ghz_frequency $WIFI_IFACE_FREQ; then + FREQ_BAND=5 + else + FREQ_BAND=2.4 + fi + if [[ $WIFI_IFACE_CHANNEL -ne $CHANNEL ]]; then + echo "Channel fallback to ${WIFI_IFACE_CHANNEL}" + CHANNEL=$WIFI_IFACE_CHANNEL + else + echo + 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= + die "Failed creating virtual WiFi interface. Maybe your WiFi adapter does not fully support virtual interfaces. Try again with '--no-virt'" + fi + + AP_IFACE=${VWIFI_IFACE} + else # no virtual wifi interface, use wifi device interface itself + AP_IFACE=${WIFI_IFACE} + 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 [[ $IEEE80211N -eq 1 ]]; then + cat <<- EOF >> "$CONFDIR/hostapd.conf" + ieee80211n=1 + ht_capab=${HT_CAPAB} + EOF + fi + + if [[ $IEEE80211AC -eq 1 ]]; then + echo "ieee80211ac=1" >> "$CONFDIR/hostapd.conf" + fi + + if [[ -n "$VHT_CAPAB" ]]; then + echo "vht_capab=${VHT_CAPAB}" >> "$CONFDIR/hostapd.conf" + 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() { + 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=${GATEWAY} + interface=$SUBNET_IFACE + except-interface=lo + no-dhcp-interface=lo + dhcp-range=${GATEWAY%.*}.10,${GATEWAY%.*}.250,255.255.255.0 + dhcp-option-force=option:router,${GATEWAY} + #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="$GATEWAY" + 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 [[ $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 "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" + # 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" & + sleep 3 +} + +start_dnsmasq() { + if which complain > /dev/null 2>&1; then + # openSUSE's apparmor does not allow dnsmasq to read files. + # remove restriction. + complain dnsmasq + fi + + echo + echo "Starting dnsmasq" + # 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 + +# check if networkManager running +NM_RUNNING="$(is_nm_running)" + +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==================== +## ======================================================== +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))" +# TODO: show interface type, device model and pci/usb id (hwdata pci.ids), current driver + +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 [[ $NM_RUNNING -eq 1 ]] && 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 + +# 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 ${GATEWAY}/24 broadcast ${GATEWAY%.*}.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 + +function check_iptables() { + if iptables --version | grep "nf_tables" >/dev/null 2>&1 ; then + echo -e "\nWARN: Your system is using nftables. This script is tested with iptables legacy only. If you encounter problems, visit following URL for infomation:\n https://github.com/oblique/create_ap/issues/373\n https://github.com/oblique/create_ap/issues/433\n https://github.com/garywill/linux-router/issues/18\n" >&2 + fi + + if ! lsmod | grep -E "\bxt_comment\b" >/dev/null 2>&1 ; then + XT_COMMENT=0 + fi +} + +check_iptables + +# 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, queries may leak to other interfaces!!!\n" >&2 + + start_nat + + [[ "$BANLAN" -eq 1 ]] && start_ban_lan + + echo 1 > "/proc/sys/net/ipv4/ip_forward" || die "Failed enabling system ipv4 forwarding" + + if [[ $IPV6 -eq 1 ]]; then + echo 1 > "/proc/sys/net/ipv6/conf/all/forwarding" || die "Failed enabling system ipv6 forwarding" + 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, this may cause privacy leak !!!\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 +echo "== Setting up completed, now linux-router is 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 \"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