#!/bin/bash VERSION=0.7.6b 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: -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 --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). 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/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 |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) Using both above two will enable '--no-dns' --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 --dns-nocache DNS server no cache --mac Set MAC address --random-mac Use random MAC address --tp Transparent proxy, redirect non-LAN TCP and UDP(not tested) 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 (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 Set name of virtual interface -c Specify channel (default: use current, or 1 / 36) --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 --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 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 capabilities --vht-ch-width 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 index of VHT center frequency for primary segment. Use with '--vht-ch-width' --vht-seg1-ch 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 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' Examples: $PROGNAME -i eth1 $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 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 # 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}" ]] } 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 '" 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 \"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