blob: 49217c876201b4b4c28bff47992b1cb189c51ac8 [file] [log] [blame] [raw]
#!/bin/sh
# Copyright 2015-2024 Rivoreo
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
# IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
PROGRAM_DIR="${0%/*}"
[ "$PROGRAM_DIR" = "$0" ] && PROGRAM_DIR="$PWD"
RNCN_STATE_FILE="$PROGRAM_DIR/.nodename"
RNCN_UNPRIVILEGED_USER=rncn-noaccess
GEOIP_MMDB_URL=https://cdn.libz.so/gh/JohnnySun/geoip/release/Country.mmdb
DEFAULT_FALLBACK_NAME_SERVER=8.8.4.4
# Override Debian default PATH
PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin
[ -h /bin ] || PATH="$PATH:/bin"
[ -h /sbin ] || PATH="$PATH:/sbin"
export PATH="$PATH:/usr/games"
parse_key_value() {
case "$1" in
*=*)
key="${1%%=*}"
key="${key%%[ ]*}"
value="${1#*=}"
local trim_value
while trim_value="${value#[ ]}" && [ "$trim_value" != "$value" ]; do value="$trim_value"; done
;;
*\ *|*\ *)
key="${1%%[ ]*}"
value="${1#*[ ]}"
;;
*)
false
;;
esac
}
parse_boolean() {
local value
eval "value=\"\$$1\""
case "$value" in
[Oo][Ff][Ff]|[Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|0|"")
eval "$1="
;;
[Oo][Nn]|[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1)
eval "$1=1"
;;
*)
false
;;
esac
}
print_peer_in_tunnel_address() {
local ORIG_IFS="$IFS"
IFS=.
set -- $1
IFS="$ORIG_IFS"
[ $# = 4 ] || return
printf "%s.%s.%s." $1 $2 $3
echo $(($4+($4%2?-1:1)))
}
print_peer_in_tunnel_inet6_address() {
local last_number="${1##*:}"
if [ -n "$last_number" ]; then
last_number="0x$last_number"
else
last_number=0
fi
printf '%s:%x\n' "${1%:*}" $((last_number+(last_number%2?-1:1)))
}
# Usage: load_config <config-dir>
load_config() {
node_address=
set_hostname=
ipv4_default_router=
ipv6_default_router=
run_dns_server=
replace_sysctl_conf=
replace_resolv_conf=
fallback_name_server=
dns_config_update_url=
wg_private_key=
peers=
ra_list=
ipv4_ra_exclusion=
ipv6_ra_exclusion=
local_wan_ipv4_route_targets=
local_wan_ipv6_route_targets=
local section=
local current_peer= current_peer_ip6addr_index
local key value line nlines=0
while read -r line; do
nlines=$((nlines+1))
case "$line" in
""|\;*|\#*)
continue
;;
\[peer\ *\]*)
section=peer
peer_name="${line#\[peer }"
peer_name="${peer_name%%\]*}"
current_peer="`printf %s \"$peer_name\" | sed -E 's/[.-]/_/g'`"
peers="$peers $current_peer"
eval "peer_${current_peer}_name=\"\$peer_name\""
eval "peer_${current_peer}_public_key="
eval "peer_${current_peer}_bind_port="
eval "peer_${current_peer}_remote="
eval "peer_${current_peer}_mtu="
eval "peer_${current_peer}_ip4_addr="
eval "peer_${current_peer}_ip6_addr_0="
current_peer_ip6addr_index=0
continue
;;
\[*\]*)
section="${line%%\]*}"
section="${section#\[}"
current_peer=
continue
;;
esac
case "$section" in
""|peer)
if ! parse_key_value "$line"; then
printf "Parsing error at line %s, missing value?\\n" $nlines 1>&2
#continue
return 1
fi
case "$section.$key" in
.node_address|.ipv4_default_router|.ipv6_default_router|.wg_private_key|.fallback_name_server|.dns_config_update_url)
eval "$key=\"\$value\""
;;
.set_hostname|.run_dns_server|.replace_sysctl_conf|.replace_resolv_conf)
eval "$key=\"\$value\""
if ! parse_boolean "$key"; then
printf "Expect a boolean value for %s at line %s, got unrecognizable '%s'\\n" "$key" $nlines "$value" 1>&2
return 1
fi
;;
peer.public_key|peer.bind_port|peer.remote|peer.mtu)
eval "peer_${current_peer}_$key=\"\$value\""
;;
peer.ip4_addr)
eval "peer_${current_peer}_$key=\"\$value\""
if ! value="`print_peer_in_tunnel_address \"$value\"`"; then
printf "Invalid IPv4 address at line %s\\n" $nlines 1>&2
return 1
fi
eval "peer_${current_peer}_remote_ip4_addr=\"\$value\""
;;
peer.ip6_addr)
eval "peer_${current_peer}_ip6_addr_$current_peer_ip6addr_index=\"\$value\""
current_peer_ip6addr_index=$((current_peer_ip6addr_index+1))
;;
esac
;;
ra-list)
ra_list="$ra_list$line
"
;;
ipv4-ra-exclusion)
ipv4_ra_exclusion="$ipv4_ra_exclusion$line
"
;;
ipv6-ra-exclusion)
ipv6_ra_exclusion="$ipv6_ra_exclusion$line
"
;;
wan-ipv4-route-targets)
local_wan_ipv4_route_targets="$local_wan_ipv4_route_targets$line
"
;;
wan-ipv6-route-targets)
local_wan_ipv6_route_targets="$local_wan_ipv6_route_targets$line
"
;;
esac
done < "$1/main.cfg"
for key in node_address ipv4_default_router wg_private_key; do
eval "[ -n \"\$$key\" ]" && continue
printf "Missing %s\\n" "$key" 1>&2
return 1
done
[ -z "$peers" ] && echo "Warning: no tunnel peer defined" 1>&2
if [ -n "$ra_list" ]; then
bird_conf_include_ipv4_ra=' include "ipv4-ra";'
bird_conf_include_ipv6_ra=' include "ipv6-ra";'
else
bird_conf_include_ipv4_ra=
bird_conf_include_ipv6_ra=
fi
for f in "$1/clips"/*; do
[ -f "$f" ] || continue
eval "clip_${f##*/}=\"\`cat \\\"\$f\\\"\`\""
done
}
get_default_route_interface() {
local line
# Skip header
read -r line
while read -r line; do
set -- $line
if [ "$2" = 00000000 ]; then
default_route_interface="$1"
return
fi
done
false
} < /proc/net/route
modify_etc_profile() {
if [ -h /etc/profile ]; then
echo "/etc/profile is a symbolic link, this is unexpected" 1>&2
return 1
fi
if [ ! -f /etc/profile ]; then
echo "/etc/profile didn't exist as a regular file, this is unexpected" 1>&2
return 1
fi
grep --fixed-strings --line-regexp --quiet -- "export SYSTEMD_PAGER=" /etc/profile && return
make_sure_nonempty_file_ends_with_new_line /etc/profile
local new_profile IFS= line if_statement=
new_profile="`mktemp`" || return
while read -r line; do case "$line" in
"if [ "*" -eq 0 ]; then")
if_statement="$line"
;;
" PATH="*)
if [ -z "$if_statement" ]; then
echo "Warning: Unexpected format in /etc/profile"
rm -f "$new_profile"
append_unique_line "export PATH=$PATH" /etc/profile
append_unique_line "export SYSTEMD_PAGER=" /etc/profile
append_unique_line "export SYSTEMD_URLIFY=off" /etc/profile
return
fi
;;
else)
[ -z "$if_statement" ] && echo else
;;
fi)
if [ -n "$if_statement" ]; then
if_statement=
cat << EOT
export PATH=$PATH
export SYSTEMD_PAGER=
export SYSTEMD_URLIFY=off
if [ "\${LANG%%.*}" = zh_CN ]; then
# Workaround for bug 939445
export LANG="zh_TW\${LANG#zh_CN}"
[ -z "\$LANGUAGE" ] && export LANGUAGE=zh_CN:zh_TW:en_US:en_GB
elif [ -z "\$LANG" ]; then case "\$TERM" in
linux)
export LANG=C.UTF-8
;;
*)
#export LANG=zh_TW.UTF-8
export LANG=en_GB.UTF-8
;;
esac fi
EOT
else
echo fi
fi
;;
"export PATH")
;;
*)
[ -n "$if_statement" ] && printf %s\\n "$if_statement"
printf %s\\n "$line"
;;
esac done < /etc/profile > "$new_profile"
cat "$new_profile" > /etc/profile
rm -f "$new_profile"
}
create_rncn_unprivileged_user() {
adduser --system --gecos "Unprivileged user for running RNCN tasks" --no-create-home "$RNCN_UNPRIVILEGED_USER" && return
adduser --system --comment "Unprivileged user for running RNCN tasks" --no-create-home "$RNCN_UNPRIVILEGED_USER"
}
# Silence log messages from pam_unix.so for sudo(1) to $RNCN_UNPRIVILEGED_USER
configure_sudo_pam() {
[ -f /etc/pam.d/sudo ] || return 0
local IFS=
local pass=
rm -f /etc/pam.d/sudo.new
while read -r line; do
case "$line" in
@include*)
[ -z "$pass" ] && printf 'session [success=done default=ignore] pam_succeed_if.so quiet ruser = root user = %s\n' "$RNCN_UNPRIVILEGED_USER"
pass=1
;;
*/limits.conf*)
continue
;;
session*pam_limits.so*)
continue
;;
session*pam_env.so*envfile=/etc/default/locale*)
continue
;;
esac
printf %s\\n "$line"
done < /etc/pam.d/sudo > /etc/pam.d/sudo.new
mv -f /etc/pam.d/sudo.new /etc/pam.d/sudo
}
configure_sudoers() {
local sudoers=/etc/sudoers new_sudoers
append_or_set_words() {
if [ -n "$new_value" ]; then
eval "$1="
append_value="$new_value"
elif [ -z "$append_value" ]; then
return
fi
while new_value="${append_value#[ ]}" && [ "$new_value" != "$append_value" ]
do append_value="$new_value"
done
while new_value="${append_value%[ ]}" && [ "$new_value" != "$append_value" ]
do append_value="$new_value"
done
append_value="${append_value#\"}"
append_value="${append_value%\"}"
eval "$1=\"\$$1
\$append_value\""
}
new_sudoers="`mktemp`"
trap 'rm -f "$new_sudoers"' EXIT
chmod 600 "$new_sudoers"
local payload="Defaults !set_utmp"
printf %s\\n "$payload" | while read -r line; do
grep --fixed-strings --line-regexp --quiet "$line" "$sudoers" || exit
done && payload=
local i=0
local last_defaults_line=
local has_env_reset=
local has_always_set_home=
local env_keep=
local line
while read -r line; do
i=$((i+1))
case "$line" in
Defaults[\ \ ]*)
append_value=
new_value=
last_defaults_line=$i
option_name="${line#Defaults[ ]}"
option_name=${option_name#${option_name%%[\!a-z]*}}
if [ "${option_name%%+=*}" != "$option_name" ]; then
append_value="${option_name#*+=}"
option_name=${option_name%%+=*}
elif [ "${option_name%%=*}" != "$option_name" ]; then
new_value="${option_name#*=}"
option_name=${option_name%%=*}
fi
option_name=${option_name%${option_name##*[a-z]}}
case $option_name in
env_reset|always_set_home|use_pty)
eval "has_$option_name=$i"
;;
!env_reset|!always_set_home|!use_pty)
eval "has_${option_name#!}="
;;
env_keep)
append_or_set_words env_keep
;;
esac
;;
esac
done < "$sudoers"
for i in env_reset always_set_home; do
eval "value=\$has_$i"
[ -n "$value" ] && continue
echo "Defaults $i" | visudo -cq -f - || continue
payload="Defaults $i
$payload"
done
local e add_env_keep=
for e in SYSTEMD_PAGER SYSTEMD_URLIFY SSH_CLIENT SSH_CONNECTION; do
for k in $env_keep; do [ $k = $e ] && continue 2; done
[ -n "$add_env_keep" ] && add_env_keep="$add_env_keep $e" || add_env_keep="$e"
done
[ -n "$add_env_keep" ] && payload="$payload
Defaults env_keep += \"$add_env_keep\""
[ -n "$has_use_pty" ] && payload="$payload
Defaults !use_pty"
if [ -n "$last_defaults_line" ]; then
i=0
while read -r line; do
i=$((i+1))
[ -n "$has_use_pty" ] && [ $i = $has_use_pty ] && printf \#
printf %s\\n "$line"
if [ $i = $last_defaults_line ]; then
echo
printf %s\\n "$payload"
fi
done
else
cat
printf %s\\n "$payload"
fi < "$sudoers" > "$new_sudoers"
if ! visudo -c -f "$new_sudoers"; then
echo "Error configuring sudoers" 1>&2
rm -f "$new_sudoers"
return 1
fi
cat "$new_sudoers" > "$sudoers"
rm -f "$new_sudoers"
trap - EXIT
}
configure_lookback_interface() {
grep -Eq "^iface lo:.+ inet static" /etc/network/interfaces && return
cat >> /etc/network/interfaces << EOT
auto lo:rncn
iface lo:rncn inet static
address $node_address/32
EOT
ifup lo:rncn || true
[ -z "$run_dns_server" ] && return
cat >> /etc/network/interfaces << EOT
auto lo:dns1
iface lo:dns1 inet static
address 10.10.10.10/32
auto lo:dns2
iface lo:dns2 inet static
address 10.0.0.10/32
EOT
ifup lo:dns1 lo:dns2 || true
}
configure_iptables() {
[ -f /etc/systemd/system/iptables.service ] || cp "$PROGRAM_DIR/systemd-units/iptables.service" /etc/systemd/system/ || return
[ -d /etc/iptables ] || mkdir /etc/iptables || return
[ -f /etc/iptables/iptables.rules ] || cat > /etc/iptables/iptables.rules << EOT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A OUTPUT -o $default_route_interface -d 10.10.10.10 -p tcp -m udp --dport 53 -j DNAT --to-destination ${fallback_name_server:-$DEFAULT_FALLBACK_NAME_SERVER}
-A POSTROUTING -o $default_route_interface -s 10.100.0.0/16 -j MASQUERADE
-A POSTROUTING -o CloudflareWARP -s 10.100.0.0/16 -j MASQUERADE
COMMIT
EOT
}
make_sure_nonempty_file_ends_with_new_line() {
local len
len=`wc -c < "$1"` || return
[ "$len" -lt 1 ] && return
[ "`dd bs=1 \"if=$1\" skip=$((len-1)) 2> /dev/null | wc -l`" -eq 1 ] && return
echo >> "$1"
}
append_unique_line() {
grep --fixed-strings --line-regexp --quiet -- "$1" "$2" && return
printf %s\\n "$1" >> "$2"
}
configure_locales() {
[ -h /etc/default/locale ] || rm -f /etc/default/locale
grep --fixed-strings --line-regexp --quiet zh_TW.GB18030 /etc/locale.gen && return
sed -i -E 's/^# +((en_GB|zh_CN|zh_TW)\.UTF\-8|zh_CN\.GB18030) /\1 /' /etc/locale.gen
make_sure_nonempty_file_ends_with_new_line /etc/locale.gen
append_unique_line "en_GB.UTF-8 UTF-8" /etc/locale.gen
append_unique_line "zh_CN.UTF-8 UTF-8" /etc/locale.gen
append_unique_line "zh_TW.UTF-8 UTF-8" /etc/locale.gen
append_unique_line "zh_CN.GB18030 GB18030" /etc/locale.gen
append_unique_line "zh_TW.GB18030 GB18030" /etc/locale.gen
locale-gen || true
}
install_and_apply_sysctl_config() {
[ -z "$replace_sysctl_conf" ] && return
cat > /etc/sysctl.conf << EOT
###################################################################
# Functions previously found in netbase
#
# Uncomment the next two lines to enable Spoof protection (reverse-path filter)
# Turn on Source Address Verification in all interfaces to
# prevent some spoofing attacks
#net.ipv4.conf.default.rp_filter=1
#net.ipv4.conf.all.rp_filter=1
# Uncomment the next line to enable TCP/IP SYN cookies
# See http://lwn.net/Articles/277146/
# Note: This may impact IPv6 TCP sessions too
#net.ipv4.tcp_syncookies=1
# Uncomment the next line to enable packet forwarding for IPv4
net.ipv4.ip_forward=1
# Uncomment the next line to enable packet forwarding for IPv6
# Enabling this option disables Stateless Address Autoconfiguration
# based on Router Advertisements for this host
net.ipv6.conf.all.forwarding=1
net.ipv6.conf.default.forwarding = 1
###################################################################
# Additional settings - these settings can improve the network
# security of the host and prevent against some network attacks
# including spoofing attacks and man in the middle attacks through
# redirection. Some network environments, however, require that these
# settings are disabled so review and enable them as needed.
#
# Do not accept ICMP redirects (prevent MITM attacks)
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
# _or_
# Accept ICMP redirects only for gateways listed in our default
# gateway list (enabled by default)
# net.ipv4.conf.all.secure_redirects = 1
#
# Do not send ICMP redirects (we are not a router)
net.ipv4.conf.all.send_redirects = 0
#
# Do not accept IP source route packets (we are not a router)
#net.ipv4.conf.all.accept_source_route = 0
#net.ipv6.conf.all.accept_source_route = 0
#
# Log Martian Packets
#net.ipv4.conf.all.log_martians = 1
#
###################################################################
# Magic system request Key
# 0=disable, 1=enable all, >1 bitmask of sysrq functions
# See https://www.kernel.org/doc/html/latest/admin-guide/sysrq.html
# for what other values do
#kernel.sysrq=438
net.core.default_qdisc = fq
net.core.rmem_max = 67108848
net.core.wmem_max = 67108848
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 4096
net.ipv4.tcp_congestion_control = bbr
net.ipv4.tcp_rmem = 16384 16777216 536870912
net.ipv4.tcp_wmem = 16384 16777216 536870912
net.ipv4.tcp_adv_win_scale = -2
net.ipv4.tcp_sack = 1
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_fastopen = 3
net.ipv6.conf.all.accept_ra=2
EOT
sysctl -p /etc/sysctl.conf || true
}
write_bird_conf_template() {
cat << EOT
log syslog all;
#log "/var/log/bird.log" { debug, trace, info, remote, warning, error, auth, fatal, bug };
router id $node_address;
protocol device {
}
protocol direct {
ipv4;
ipv6;
interface "rncn-*";
interface "lo" 10.100.254.0/24;
$clip_bird_direct_interfaces
}
# The Kernel protocol is not a real routing protocol. Instead of communicating
# with other routers in the network, it performs synchronization of BIRD
# routing tables with the OS kernel. One instance per table.
# This doesn't include on-link routes by default, use 'direct' protocol for
# that.
protocol kernel {
ipv4 {
import all;
export all;
};
# Learn alien routes from the kernel, excluding kernel maintained
# entries.
learn;
}
# Another instance for IPv6, skipping default options
protocol kernel {
ipv6 { export all; };
}
# Static routes (Again, there can be multiple instances, for different address
# families and to disable/enable various groups of static routes on the fly).
protocol static {
ipv4; # Again, IPv4 channel with default options
$bird_conf_include_ipv4_ra
# RNCN DNS
route 10.10.10.10/32 via "lo";
route 10.0.0.10/32 via "lo";
# END OF RNCN DNS
EOT
local target
for target in $local_wan_ipv4_route_targets; do
printf ' route %s via %s;\n' "$target" "$ipv4_default_router"
done
cat << EOT
}
EOT
if [ -n "$ipv6_default_router" ]; then
cat << EOT
protocol static {
ipv6;
$bird_conf_include_ipv6_ra
EOT
for target in $local_wan_ipv6_route_targets; do
printf ' route %s via %s;\n' "$target" "$ipv6_default_router"
done
echo \}
fi
local v ip_vers peer interface
for v in 2 3; do
case $v in
2) ip_vers=4; ;;
3) ip_vers=6; ;;
esac
cat << EOT
protocol ospf v$v {
ipv$ip_vers {
export all;
import all;
};
ecmp yes;
area 0 {
EOT
for peer in $peers; do
eval "interface=\"rncn-\$peer_${peer}_name\""
cat << EOT
interface "$interface" {
type pointopoint;
authentication none;
hello 5;
wait 10;
dead 40;
cost 20; # ospf-v$v-backbone-interface-cost:$interface
};
EOT
done
cat << EOT
};
}
EOT
done
} > /etc/bird/bird.conf.template
write_rncn_link_status_monitor_sh() {
cat << EOT
#!/bin/sh
export LANG=C
EOT
cat "$PROGRAM_DIR/src/rncn-link-status-monitor.calc_metric.sh"
cat << EOT
set -e
while true; do
EOT
local peer peer_in_tunnel_addr interface
for peer in $peers; do
#eval "my_in_tunnel_addr=\"\$peer_${peer}_ip4_addr\""
#peer_in_tunnel_addr="`print_peer_in_tunnel_addr \"$my_in_tunnel_addr\"`"
eval "peer_in_tunnel_addr=\"\$peer_${peer}_remote_ip4_addr\""
printf ' calc_metric "%s" & ping_%s_pid=$!\n' "$peer_in_tunnel_addr" $peer
done
printf " wait"
for peer in $peers; do printf ' $ping_%s_pid' $peer; done
echo
for peer in $peers; do
eval "peer_in_tunnel_addr=\"\$peer_${peer}_remote_ip4_addr\""
printf ' %s_cost="`cat /tmp/cost.%s`" && rm -f /tmp/cost.%s || exit\n' $peer "$peer_in_tunnel_addr" "$peer_in_tunnel_addr"
done
cat << EOT
if [ -f /etc/bird/bird.conf ]; then
rm -f /etc/bird/bird.conf.old
mv /etc/bird/bird.conf /etc/bird/bird.conf.old
elif [ -e /etc/bird/bird.conf ]; then
rm -f /etc/bird/bird.conf
fi
sed -E \\
EOT
for peer in $peers; do
eval "interface=\"rncn-\$peer_${peer}_name\""
printf ' -e "s/ cost [0-9]+; (# ?ospf-v[23]-backbone-interface-cost:%s)$/ cost $%s_cost; \\\\1/" \\\n' $interface $peer
done
cat << EOT
/etc/bird/bird.conf.template > /etc/bird/bird.conf
birdc configure > /dev/null
done
EOT
} > /etc/bird/rncn-link-status-monitor.sh
write_ra_list() {
[ -z "$ra_list" ] && return
printf %s "$ra_list" > /etc/bird/ra.list
[ -n "$ipv4_ra_exclusion" ] && printf %s "$ipv4_ra_exclusion" > /etc/bird/ipv4-ra-exclusion
[ -n "$ipv6_ra_exclusion" ] && printf %s "$ipv6_ra_exclusion" > /etc/bird/ipv6-ra-exclusion
true
}
write_ra_updater() {
cat << EOT
#!/bin/sh
LOCAL_IP4_ROUTER='$ipv4_default_router'
LOCAL_IP6_ROUTER='$ipv6_default_router'
EOT
cat "$PROGRAM_DIR/src/rncn-ra-updater.sh"
} > /etc/bird/rncn-ra-updater.sh
write_wireguard_configuration_files() {
[ -d /etc/wireguard ] || mkdir /etc/wireguard || return
local peer interface public_key bind_port remote mtu ip4_addr ip6_addr first_ip6_addr i
for peer in $peers; do
eval "interface=\"rncn-\$peer_${peer}_name\""
eval "public_key=\"\$peer_${peer}_public_key\""
eval "bind_port=\"\$peer_${peer}_bind_port\""
eval "remote=\"\$peer_${peer}_remote\""
eval "mtu=\"\$peer_${peer}_mtu\""
eval "ip4_addr=\"\$peer_${peer}_ip4_addr\""
rm -f "/etc/wireguard/$interface.conf"
{
cat << EOT
[Interface]
SaveConfig = false
PrivateKey = $wg_private_key
EOT
[ -n "$bind_port" ] && printf 'ListenPort = %s\n' "$bind_port"
[ -n "$mtu" ] && printf 'MTU = %s\n' "$mtu"
cat << EOT
Table = off
FwMark = 0xca6c
Address = $ip4_addr/31
EOT
first_ip6_addr=
i=0
while
eval "ip6_addr=\"\$peer_${peer}_ip6_addr_$i\""
[ -n "$ip6_addr" ]
do
printf 'Address = %s/127\n' "$ip6_addr"
[ -z "$first_ip6_addr" ] && first_ip6_addr="$ip6_addr"
i=$((i+1))
done
[ -n "$first_ip6_addr" ] && cat << EOT
PostUp = ip6tables -t nat -A POSTROUTING -s `print_peer_in_tunnel_inet6_address $first_ip6_addr` -o eth0 -j MASQUERADE
PostUp = ip6tables -t nat -A POSTROUTING -o %i -s fd00::/8 -d fd00::/8 -j ACCEPT
PostUp = ip6tables -t nat -A POSTROUTING -o %i -s fd00::/8 -d fc00::/8 -j ACCEPT
PostUp = ip6tables -t nat -A POSTROUTING -o %i -s fc00::/8 -d fd00::/8 -j ACCEPT
PostUp = ip6tables -t nat -A POSTROUTING -o %i -s fc00::/8 -d fc00::/8 -j ACCEPT
PostUp = ip6tables -t nat -A POSTROUTING -o %i -j MASQUERADE
PostUp = route -6 add ::/0 dev %i metric 2048
PostDown = ip6tables -t nat -D POSTROUTING -s `print_peer_in_tunnel_inet6_address $first_ip6_addr` -o eth0 -j MASQUERADE
PostDown = ip6tables -t nat -D POSTROUTING -o %i -s fd00::/8 -d fd00::/8 -j ACCEPT
PostDown = ip6tables -t nat -D POSTROUTING -o %i -s fd00::/8 -d fc00::/8 -j ACCEPT
PostDown = ip6tables -t nat -D POSTROUTING -o %i -s fc00::/8 -d fd00::/8 -j ACCEPT
PostDown = ip6tables -t nat -D POSTROUTING -o %i -s fc00::/8 -d fc00::/8 -j ACCEPT
PostDown = ip6tables -t nat -D POSTROUTING -o %i -j MASQUERADE
EOT
cat << EOT
[Peer]
PublicKey = $public_key
EOT
[ -n "$remote" ] && cat << EOT
Endpoint = $remote
PersistentKeepAlive = 50
EOT
echo "AllowedIPs = 0.0.0.0/0, ::/0"
} > "/etc/wireguard/$interface.conf"
chmod 640 "/etc/wireguard/$interface.conf"
done
}
write_resolv_conf() {
[ -z "$replace_resolv_conf" ] && return
rm -f /etc/resolv.conf
{
echo "nameserver 10.10.10.10"
if [ -n "$fallback_name_server" ]; then
printf 'nameserver %s\n' "$fallback_name_server"
fi
} > /etc/resolv.conf || return
[ -n "$fallback_name_server" ] && return
if ! iptables -t nat --list-rules | grep -Eq '^-A OUTPUT -d 10\.10\.10\.10/32 -o .+ -p udp -m udp --dport 53 -j DNAT --to-destination '; then
iptables -t nat -A OUTPUT -o "$default_route_interface" -d 10.10.10.10 -p tcp -m udp --dport 53 -j DNAT --to-destination "$DEFAULT_FALLBACK_NAME_SERVER"
fi
}
install_rncn_dns() {
[ -z "$run_dns_server" ] && return
[ -d "$PROGRAM_DIR/dns" ] || mkdir "$PROGRAM_DIR/dns" || return
rm -f "$PROGRAM_DIR/dns/Country.mmdb.new"
wget "$GEOIP_MMDB_URL" --output-document "$PROGRAM_DIR/dns/Country.mmdb.new"
rm -f "$PROGRAM_DIR/dns/Country.mmdb"
mv "$PROGRAM_DIR/dns/Country.mmdb.new" "$PROGRAM_DIR/dns/Country.mmdb"
if [ ! -f "$PROGRAM_DIR/dns/mihomo" ]; then
local go_arch
case "`uname -m`" in
i?86)
go_arch=386
;;
x86_64)
go_arch=amd64
;;
armv7*l*)
go_arch=armv7
;;
aarch64)
go_arch=arm64
;;
riscv64)
go_arch=riscv64
;;
*)
echo "Unsupported machine type" 1>&2
return 1
;;
esac
wget https://github.com/MetaCubeX/mihomo/releases/download/v1.18.6/mihomo-linux-$go_arch-v1.18.6.gz -O - | gzip -d > "$PROGRAM_DIR/dns/mihomo"
chmod 755 "$PROGRAM_DIR/dns/mihomo"
fi
rm -f /etc/systemd/system/rncn-dns.service
cat > /etc/systemd/system/rncn-dns.service << EOT
[Unit]
Description=RNCN DNS
After=network-online.target
[Service]
Type=simple
ExecStart="$PROGRAM_DIR/dns/mihomo" -d "$PROGRAM_DIR/dns"
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOT
[ -z "$dns_config_update_url" ] && return
rm -f "$PROGRAM_DIR/dns/rncn-dns-updater.sh"
{
printf '#!/bin/sh\n\n'
cat "$PROGRAM_DIR/src/rncn-dns-updater.functions.sh"
cat << EOT
while true; do
check_and_update_config '$dns_config_update_url' '$PROGRAM_DIR/dns/config.yaml' rncn-dns
sleep 600
done
EOT
} > "$PROGRAM_DIR/dns/rncn-dns-updater.sh"
rm -f /etc/systemd/system/rncn-dns-updater.service
cat > /etc/systemd/system/rncn-dns-updater.service << EOT
[Unit]
Description=RNCN DNS Updater
After=network-online.target rncn-dns.service rncn-media-unlocker.service
[Service]
Type=simple
ExecStart=/bin/sh "$PROGRAM_DIR/dns/rncn-dns-updater.sh"
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOT
. "$PROGRAM_DIR/src/rncn-dns-updater.functions.sh"
check_and_update_config "$dns_config_update_url" "$PROGRAM_DIR/dns/config.yaml"
}
if [ $# = 0 ]; then
cat << EOF
Available subcommands:
init
install
enable-services
EOF
exit 255
fi
case "$1" in
init)
if [ $# != 2 ]; then
printf 'Usage: %s init <node-name>\n' "$0" 1>&2
exit 255
fi
if [ -f "$RNCN_STATE_FILE" ]; then
echo "This node appears already initiailzed" 1>&2
exit 1
fi
if [ ! -d "$PROGRAM_DIR/config/$2" ]; then
printf "No configuration found for %s\\n" "$2" 1>&2
exit 1
fi
printf %s\\n "$2" > "$RNCN_STATE_FILE"
;;
install)
if [ ! -f "$RNCN_STATE_FILE" ] || ! read node_name < "$RNCN_STATE_FILE" || [ -z "$node_name" ]; then
printf "Use '%s init <node-name>' to select a configuration for this node first\\n" "$0" 1>&2
exit 1
fi
if ! load_config "$PROGRAM_DIR/config/$node_name"; then
echo "Failed to load node configuration" 1>&2
exit 1
fi
apt-get --yes remove cloud-init
apt-get --yes remove systemd-resolved
set -e
if [ -n "$set_hostname" ] && [ "`hostname`" != "$node_name" ] && [ "`hostname -f`" != "$node_name" ]; then
rm -f /etc/hostname
printf %s\\n "$node_name" > /etc/hostname
if grep -Eq '^127\.0\.1\.1[[:space:]]' /etc/hosts; then
escaped_node_name="`printf %s \"$node_name\" | sed 's/\\./\\\\\\./g'`"
if ! grep -Eq "^127\\.0\\.1\\.1[[:space:]]$escaped_node_name( |\$)" /etc/hosts; then
sed -i -E "s/(127\\.0\\.1\\.1[[:space:]])/\\1$escaped_node_name /" /etc/hosts
fi
else
make_sure_nonempty_file_ends_with_new_line /etc/hosts
printf '127.0.1.1 %s\n' "$node_name" > /etc/hosts
fi
hostname --file /etc/hostname
fi
if ! get_default_route_interface; then
echo "Failed to determine default route interface" 1>&2
exit 1
fi
make_sure_nonempty_file_ends_with_new_line /etc/inputrc
append_unique_line "set enable-bracketed-paste off" /etc/inputrc
make_sure_nonempty_file_ends_with_new_line /etc/bash.bashrc
append_unique_line "set +H" /etc/bash.bashrc
append_unique_line HISTCONTROL=ignoreboth /etc/bash.bashrc
append_unique_line "shopt -s histappend" /etc/bash.bashrc
modify_etc_profile
apt-get install -y wireguard-tools bird2 iptables net-tools sudo git jq bc locales
configure_locales
create_rncn_unprivileged_user
configure_sudo_pam
configure_sudoers
configure_lookback_interface
configure_iptables
write_resolv_conf
install_and_apply_sysctl_config
[ -d /etc/bird ] || mkdir /etc/bird || exit
write_bird_conf_template
write_rncn_link_status_monitor_sh
cp -f "$PROGRAM_DIR/systemd-units/rncn-bird-link-status-monitor.service" /etc/systemd/system/
write_ra_list
write_ra_updater
cp -f "$PROGRAM_DIR/systemd-units/rncn-bird-ra-updater.service" /etc/systemd/system/
write_wireguard_configuration_files
install_rncn_dns
;;
enable-services)
if [ ! -f "$RNCN_STATE_FILE" ]; then
printf "Use '%s init <node-name>' to select a configuration for this node first\\n" "$0" 1>&2
exit 1
fi
# TODO
;;
*)
printf "Unknown subcommand '%s'\\n" "$1" 1>&2
exit 255
;;
esac