#!/bin/bash VERSION=0.5.4 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 Set this host's IPv4 address, netmask is 24 -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:1:2:3::) 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 --list-running Show running instances --list-clients List clients of an instance --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 -n --ap wlan0 MyAccessPoint -p MyPassPhrase $PROGNAME -i eth1 --tp --dns EOF } check_empty_option(){ if [[ "$1" == "" ]]; then usage exit 0 fi } define_global_variables(){ #======== Global variables for 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= SUBNET_IFACE= # which interface to create network 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 SHARE_METHOD=nat TP_PORT= # transparent proxy port DNS= # upstream DNS USE_RANDOM_MAC=0 NEW_MACADDR= OLD_MACADDR= DAEMONIZE=0 # wifi hotspot HIDDEN=0 # hidden wifi hotspot WIFI_IFACE= VWIFI_IFACE= AP_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 USE_IWCONFIG=0 # automatically decided QR=0 # show wifi qr #-- to deal with a running instance 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 } 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 USE_RANDOM_MAC=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 ;; --list-running) shift LIST_RUNNING=1 ;; --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() { 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_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 PHY PHY=$(get_phy_device "$1") [[ $? -ne 0 ]] && return 1 iw phy $PHY 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_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_macaddr() { is_interface "$1" || return cat "/sys/class/net/${1}/address" } alloc_new_vface() { # 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() { rm -f $COMMON_CONFDIR/vfaces/$1 } #====== get_all_macaddrs() { cat /sys/class/net/*/address } get_new_macaddr() { local OLDMAC NEWMAC LAST_BYTE i OLDMAC=$(get_macaddr "$1") LAST_BYTE=$(printf %d 0x${OLDMAC##*:}) for i in {10..240}; do NEWMAC="${OLDMAC%:*}:$(printf %02x $(( ($LAST_BYTE + $i) % 256 )))" (get_all_macaddrs | grep "$NEWMAC" > /dev/null 2>&1) || break done echo "$NEWMAC" } generate_random_mac() { local r1 r2 r3 r4 r5 r6 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_macaddrs | grep $RAND_MAC ) && \ break done NEW_MACADDR=$RAND_MAC } is_ip4_range_available() { ( 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_range_available() { ( 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_range_available $random_ip4 && break done GATEWAY="192.168.$random_ip4.1" } generate_random_ip6() { 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_range_available $r1 $r2 $r3 $r4 $r5 $r6 $r7 && break done PREFIX6="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 echo "Low entropy detected, starting haveged" 1>&2 # boost low-entropy haveged -w 1024 -p $COMMON_CONFDIR/haveged.pid fi fi sleep 2 done } #======== # only support NetworkManager >= 0.9.9 nm_initcheck() { if (which nmcli >/dev/null 2>&1 ) && (nmcli -t -f RUNNING g 2>&1 | grep -E '^running$' >/dev/null 2>&1 ) ; then NM_RUNNING=1 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_() { iptables -w $@ -m comment --comment "lnxrouter-$$-$SUBNET_IFACE" return $? } ip6tables_() { ip6tables -w $@ -m comment --comment "lnxrouter-$$-$SUBNET_IFACE" 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 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 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 port access" 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: stop allowing 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 port access" 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: stop 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 } 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_interface_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 backking up interface ipv6 bits" } set_interface_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_interface_ipv6_bit() { 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} } #--------------------------------------- kill_processes() { #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 if [[ "$WIFI_IFACE" && "$NO_VIRT" -eq 0 ]]; then # the subnet interface (virtual wifi interface) will be removed ip link set down dev ${AP_IFACE} iw dev ${VWIFI_IFACE} del dealloc_vface $VWIFI_IFACE else # the subnet interface will not be removed, so need to restore settings about it restore_interface_ipv6_bit if [[ -n "$NEW_MACADDR" ]] ; then set_interface_mac ${SUBNET_IFACE} ${OLD_MACADDR} && echo "Restore ${SUBNET_IFACE} to old MAC address ${OLD_MACADDR}" fi nm_restore_manage fi 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 } 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 } #== 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_client_by_mac() { local line ipaddr hostname local mac="$1" if [[ -f "$CONFDIR/dnsmasq.leases" ]]; then line=$(grep " $mac " "$CONFDIR/dnsmasq.leases" | tail -n 1) ipaddr=$(echo "$line" | cut -d' ' -f3) hostname=$(echo "$line" | cut -d' ' -f4) fi [[ -z "$ipaddr" ]] && ipaddr="*" [[ -z "$hostname" ]] && hostname="*" printf "%-20s %-18s %s\n" "$mac" "$ipaddr" "$hostname" } print_clients_in_leases() { local line ipaddr hostname local mac if [[ -f "$CONFDIR/dnsmasq.leases" ]]; then while read line do mac=$(echo "$line" | cut -d' ' -f2) ipaddr=$(echo "$line" | cut -d' ' -f3) hostname=$(echo "$line" | cut -d' ' -f4) printf "%-20s %-18s %s\n" "MAC" "IP" "Hostname" printf "%-20s %-18s %s\n" "$mac" "$ipaddr" "$hostname" done < "$CONFDIR/dnsmasq.leases" fi } list_clients() { local subn_iface pid # If PID is given, get the associated wifi iface if [[ "$1" =~ ^[1-9][0-9]*$ ]]; then pid="$1" subn_iface=$(get_subn_iface_from_pid "$pid") [[ -z "$subn_iface" ]] && die "'$pid' is not the pid of a running $PROGNAME instance." fi [[ -z "$subn_iface" ]] && subn_iface="$1" [[ -z "$pid" ]] && pid=$(get_pid_from_subn_iface "$subn_iface") [[ -z "$pid" ]] && die "'$subn_iface' is not used from $PROGNAME instance.\n\ Maybe you need to pass the virtual interface instead.\n\ Use --list-running to find it out." [[ -z "$CONFDIR" ]] && CONFDIR=$(get_confdir_from_pid "$pid") if [[ -f "$CONFDIR/hostapd.conf" && $USE_IWCONFIG -eq 0 ]]; then local awk_cmd='($1 ~ /Station$/) {print $2}' local client_list=$(iw dev "$subn_iface" station dump | awk "$awk_cmd") if [[ -z "$client_list" ]]; then echo "No clients connected" return fi printf "%-20s %-18s %s\n" "MAC" "IP" "Hostname" local mac for mac in $client_list; do print_client_by_mac $mac done else echo "Listing clients via DNS lease file. non-DHCPed clients won't be showed" print_clients_in_leases fi } 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 init_tmpdir(){ if [[ -d /dev/shm ]]; then TMPD=/dev/shm elif [[ -d /run/shm ]]; then TMPD=/run/shm else TMPD=/tmp fi TMPDIR=$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 if [[ $(id -u) -ne 0 ]]; then echo "You must run it as root." >&2 exit 1 fi 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 [[ $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_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_macaddrs | grep -c ${NEW_MACADDR}) -ne 0 ]]; then echo "WARN: MAC address '${NEW_MACADDR}' already exists" >&2 fi } decide_target_interface() { TARGET_IFACE= # This is the existing physical interface to use if [[ $CONN_IFACE ]]; then TARGET_IFACE=$CONN_IFACE elif [[ $WIFI_IFACE ]]; then TARGET_IFACE=$WIFI_IFACE else echo "No target interface specified" 1>&2 exit 1 fi echo "Target interface is ${TARGET_IFACE}" } decide_ip_addresses() { if [[ ! -n $GATEWAY ]]; then generate_random_ip4 echo "Use random IPv4 address $GATEWAY" fi if [[ $IPV6 -eq 1 && ! -n $PREFIX6 ]]; then generate_random_ip6 echo "Use random IPv6 address ${PREFIX6}${IID6}" 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 ## Generate virtual wifi interface VWIFI_IFACE=$(alloc_new_vface) if is_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 VIRTDIEMSG="Maybe your WiFi adapter does not fully support virtual interfaces. Try again with --no-virt." echo "Creating a virtual WiFi interface... " if iw dev ${WIFI_IFACE} interface add ${VWIFI_IFACE} type __ap; then echo "${VWIFI_IFACE} created." sleep 2 else VWIFI_IFACE= die "$VIRTDIEMSG" fi OLD_MACADDR=$(get_macaddr ${VWIFI_IFACE}) if [[ -z "$NEW_MACADDR" && $(get_all_macaddrs | grep -c ${OLD_MACADDR}) -ne 1 ]]; then NEW_MACADDR=$(get_new_macaddr ${VWIFI_IFACE}) fi AP_IFACE=${VWIFI_IFACE} else OLD_MACADDR=$(get_macaddr ${WIFI_IFACE}) AP_IFACE=${WIFI_IFACE} fi } decide_subnet_interface() { if [[ $WIFI_IFACE ]]; then SUBNET_IFACE=${AP_IFACE} else SUBNET_IFACE=${TARGET_IFACE} 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 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=/dev/stdout 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 ( while [ -e /proc/$HOSTAPD_PID ]; do sleep 10; done ; die "hostapd exited" ) & 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 echo -n "dnsmasq PID: " ; cat "$CONFDIR/dnsmasq.pid" ######(wait $DNSMASQ_PID ; die "dnsmasq failed") & # wait can't deal with non-child ( while [ -e "/proc/$DNSMASQ_PID" ]; do sleep 10; done ; die "dnsmasq exited" ) & sleep 2 } check_if_need_rfkill_unblock_wifi() { if which rfkill > /dev/null 2>&1 ; then PHY=$(get_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 "$@" # check if networkManager running nm_initcheck init_tmpdir # if user choose to deal with running instances, will exit after this check_other_functions # 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 [[ "$USE_RANDOM_MAC" -eq 1 ]] && generate_random_mac [[ -n "$NEW_MACADDR" ]] && check_if_new_mac_valid # checks finished ## ===== Above don't echo anything if no warning or error==================== ## ======================================================== echo "PID: $$" decide_target_interface [[ "$USE_RANDOM_MAC" -eq 1 ]] && echo "Use random MAC address $NEW_MACADDR" decide_ip_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=== trap "cleanup" EXIT trap "clean_exit" SIGINT SIGUSR1 SIGTERM trap "die" SIGUSR2 mkdir -p "$TMPDIR" 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)" chmod 755 "$CONFDIR" #echo "Config dir: $CONFDIR" echo $$ > "$CONFDIR/pid" COMMON_CONFDIR="$TMPDIR/lnxrouter_common.conf" mkdir -p "$COMMON_CONFDIR" [[ $WIFI_IFACE ]] && prepare_wifi_interface decide_subnet_interface echo "$SUBNET_IFACE" > "$CONFDIR/subn_iface" if [[ $WIFI_IFACE && -n "$COUNTRY" && $USE_IWCONFIG -eq 0 ]]; then iw reg set "$COUNTRY" || die "Failed setting country code" fi 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 $SUBNET_IFACE; then nm_set_unmanaged ${SUBNET_IFACE} fi [[ $NO_DNSMASQ -eq 0 ]] && write_dnsmasq_conf #=========================== # initialize subnet interface # put subnet interface down first ip link set down dev ${SUBNET_IFACE} || die "Failed setting ${SUBNET_IFACE} down" # flush IPs of subnet interface ip addr flush ${SUBNET_IFACE} || die "Failed flush ${SUBNET_IFACE} IP" # set subnet mac if needed if [[ -n "$NEW_MACADDR" ]] ; then set_interface_mac ${SUBNET_IFACE} ${NEW_MACADDR} || die "Failed setting new MAC address" fi [[ $WIFI_IFACE ]] && check_if_need_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" backup_interface_ipv6_bits set_interface_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, 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 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" fi [[ "$dnsmasq_NO_DNS" -eq 0 && ! $DNS ]] && echo -e "\nWARN: You are using transparent proxy 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};\"" } [[ "$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