blob: 6ea84218690ccf76ab163cc38a54eaf1c3efe1ab [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"
[ -z "$CC" ] && CC=gcc
check_super_user() {
[ "`id -u`" = 0 ] && return
echo "This action must be run with super user" 1>&2
exit 1
}
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=
dns_server_port=53
replace_sysctl_conf=
replace_resolv_conf=
add_login_logo=1
run_sniproxy=
fallback_name_server=
time_zone=
dns_config_update_url=
mon_initial_ping_duration=60
mon_ping_duration=3600
mon_minimal_ping_wait=3600
wg_private_key=
wg_use_peer_resolver=
wg_periodic_rebind_port=
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}_tunnel_type=wireguard"
eval "peer_${current_peer}_public_key="
eval "peer_${current_peer}_bind="
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="
eval "peer_${current_peer}_metric_offset=0"
current_peer_ip6addr_index=0
continue
;;
\[*\]*)
section="${line%%\]*}"
section="${section#\[}"
current_peer=
continue
;;
esac
case "$section" in
""|wireguard|peer)
if ! parse_key_value "$line"; then
printf "Parsing error at line %s, missing value?\\n" $nlines 1>&2
return 1
fi
case "$section.$key" in
.node_address|.ipv4_default_router|.ipv6_default_router|.fallback_name_server|.time_zone|.dns_server_port|.dns_config_update_url)
eval "$key=\"\$value\""
;;
.set_hostname|.run_dns_server|.replace_sysctl_conf|.replace_resolv_conf|.add_login_logo|.run_sniproxy)
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
;;
monitor.initial_ping_duration|monitor.ping_duration|monitor.minimal_ping_wait)
eval "mon_$key=\"\$value\""
;;
wireguard.private_key|wireguard.use_peer_resolver|wireguard.periodic_rebind_port)
eval "wg_$key=\"\$value\""
;;
peer.tunnel_type|peer.public_key|peer.bind|peer.bind_port|peer.remote|peer.mtu|peer.metric_offset)
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 ] && [ "$7" = 0 ]; then
default_route_interface="$1"
return
fi
done
false
} < /proc/net/route
make_sure_nonempty_file_ends_with_new_line() {
[ -f "$1" ] || return
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"
}
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 [ "${PS1-}" ]; then'|"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)
case "$if_statement" in
'if [ "${PS1-}" ]; then')
cat << EOT
if [ -n "\${PS1-}" ]; then
# Interactive shell
[ -n "\${BASH-}" ] && [ "\$BASH" != "/bin/sh" ] && [ -f /etc/bash.bashrc ] && . /etc/bash.bashrc
fi
EOT
;;
"")
echo fi
;;
*)
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
esac
if_statement=
;;
"export PATH")
;;
*)
case "$if_statement" in
'if [ "${PS1-}" ]; then')
;;
"")
printf %s\\n "$line"
;;
*)
printf '%s\n%s\n' "$if_statement" "$line"
;;
esac
;;
esac done < /etc/profile > "$new_profile"
cat "$new_profile" > /etc/profile
rm -f "$new_profile"
}
configure_zsh() {
[ -d /etc/zsh ] || return 0
make_sure_nonempty_file_ends_with_new_line /etc/zsh/zlogin || true
append_unique_line "source /etc/profile" /etc/zsh/zlogin
}
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"
}
configure_sshd() {
if ! make_sure_nonempty_file_ends_with_new_line /etc/ssh/sshd_config; then
echo "Missing /etc/ssh/sshd_config" 1>&2
return 1
fi
local sshd_config_sed_flags=
grep -Eq "^[[:space:]]*#?UseDNS[[:space:]]+yes" /etc/ssh/sshd_config && sshd_config_sed_flags="-e 's/^([[:space:]]*)#?(UseDNS[[:space:]]+).+/\\1\\2no/'" || true
grep -Eq "^[[:space:]]*#?PasswordAuthentication[[:space:]]+no" /etc/ssh/sshd_config && sshd_config_sed_flags="-e 's/^([[:space:]]*)#?(PasswordAuthentication[[:space:]]+).+/\\1\\2yes/'" || true
if grep -Eq "^[[:space:]]*#?PrintMotd[[:space:]]+" /etc/ssh/sshd_config; then
sshd_config_sed_flags="-e 's/^([[:space:]]*)#?(PrintMotd[[:space:]]+).+/\\1\\2yes/'"
else
append_unique_line "PrintMotd yes" /etc/ssh/sshd_config
fi
[ -z "$sshd_config_sed_flags" ] && return
rm -f /etc/ssh/sshd_config.new || return
eval "sed -E $sshd_config_sed_flags /etc/ssh/sshd_config" > /etc/ssh/sshd_config.new || return
mv -f /etc/ssh/sshd_config.new /etc/ssh/sshd_config
}
configure_pam_sshd() {
[ -f /etc/pam.d/sshd ] || return 0
rm -f /etc/pam.d/sshd.new || return
sed -E '/[[:space:]]pam_(motd|limits)\.so/d' /etc/pam.d/sshd > /etc/pam.d/sshd.new || return
if cmp -s /etc/pam.d/sshd.new /etc/pam.d/sshd; then
rm -f /etc/pam.d/sshd.new
else
mv -f /etc/pam.d/sshd.new /etc/pam.d/sshd
fi
}
# 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_systemd_link() {
[ -d /lib/systemd/network ] || return 0
[ -d /etc/systemd/network ] || mkdir /etc/systemd/network || return
if
grep --extended-regexp --files-with-matches \
-e '^[[:space:]]*NamePolicy[[:space:]]*=[[:space:]]*mac($|[[:space:]])' \
-e '^[[:space:]]*AlternativeNamesPolicy[[:space:]]*=' \
/lib/systemd/network/*.link \
2> /dev/null | {
fail() {
kill $$
exit 1
}
changed=
while read -r f; do
override_f="/etc${f#/lib}"
[ -c "$override_f" ] && continue
[ -f "$override_f" ] && continue
rm -f "$override_f" || fail
ln -sf /dev/null "$override_f" || fail
changed=1
[ "$override_f" != /etc/systemd/network/99-default.link ] && continue
[ -f /etc/systemd/network/70-default.link ] && continue
rm -f /etc/systemd/network/70-default.link || fail
cat > /etc/systemd/network/70-default.link << EOT
[Match]
OriginalName=*
[Link]
NamePolicy=keep kernel database onboard slot path
MACAddressPolicy=persistent
EOT
done
[ -n "$changed" ]
}
then
update-initramfs -u
fi
}
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
# Still configuring 10.0.0.10 for bird(8)
cat >> /etc/network/interfaces << EOT
iface lo:dns1 inet static
address 10.10.10.10/32
iface lo:dns2 inet static
address 10.0.0.10/32
EOT
}
configure_iptables() {
for f in /etc/systemd/system/iptables.service /etc/systemd/system/iptables-legacy.service /etc/systemd/system/iptables-nft.service ""; do
[ -f "$f" ] && break
[ -n "$f" ] || cp "$PROGRAM_DIR/systemd-units/iptables.service" /etc/systemd/system/ || return
done
[ -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 udp -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
*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A FORWARD -o rncn-+ -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
-A PREROUTING -i $default_route_interface -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG SYN -j CONNMARK --set-mark 22
-A OUTPUT -p tcp -m connmark --mark 22 -j MARK --set-mark 22
COMMIT
EOT
}
configure_ip6tables() {
[ -z "$ipv6_default_router" ] && return
if [ ! -f /etc/systemd/system/ip6tables.service ]; then
rm -f /etc/systemd/system/ip6tables.service || return
cp "$PROGRAM_DIR/systemd-units/ip6tables.service" /etc/systemd/system/ || return
fi
[ -d /etc/iptables ] || mkdir /etc/iptables || return
[ -f /etc/iptables/ip6tables.rules ] || cat > /etc/iptables/ip6tables.rules << EOT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -o $default_route_interface -s fd00:0db8::/32 -j MASQUERADE
-A POSTROUTING -o CloudflareWARP -s fd00:0db8::/32 -j MASQUERADE
COMMIT
*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A FORWARD -o rncn-+ -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
COMMIT
EOT
}
configure_locales() {
[ -h /etc/default/locale ] || rm -f /etc/default/locale
grep --fixed-strings --line-regexp --quiet "zh_TW.GB18030 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 || true
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
}
configure_time_zone() {
[ -z "$time_zone" ] && return
if [ ! -f "/usr/share/zoneinfo/$time_zone" ]; then
printf 'Time zone %s not available\n' "$time_zone" 1>&2
return 1
fi
ln -snf "/usr/share/zoneinfo/$time_zone" /etc/localtime
}
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.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
net.ipv6.conf.$default_route_interface.accept_ra=2
net.core.wmem_default=16384
net.core.rmem_default=262144
net.core.rmem_max=268435456
net.core.wmem_max=268435456
net.ipv4.tcp_rmem=8192 262144 268435456
net.ipv4.tcp_wmem=4096 16384 268435456
net.ipv4.route.max_size=2147483647
net.ipv4.route.gc_thresh=-1
net.ipv4.route.gc_interval=60
net.ipv4.route.gc_timeout=300
net.ipv4.route.gc_elasticity=8
net.ipv6.route.max_size=2147483647
net.ipv6.route.gc_thresh=-1
net.ipv6.route.gc_interval=60
net.ipv6.route.gc_timeout=300
net.ipv6.route.gc_elasticity=8
EOT
sysctl -p /etc/sysctl.conf || true
}
install_bsd_ping() {
if [ ! -f "$PROGRAM_DIR/src/bsdping/build.sh" ] || [ ! -f "$PROGRAM_DIR/src/bsdping/ping.c" ]; then
echo "Missing BSD ping source code" 1>&2
return 1
fi
[ -f /usr/local/bin/ping.bsd ] && [ /usr/local/bin/ping.bsd -nt "$PROGRAM_DIR/src/bsdping/ping.c" ] && return
sh "$PROGRAM_DIR/src/bsdping/build.sh" -o /usr/local/bin/ping.bsd || return
chown root:root /usr/local/bin/ping.bsd || return
setcap cap_net_raw=+ep /usr/local/bin/ping.bsd || chmod 4755 /usr/local/bin/ping.bsd || return
[ -d /usr/local/share/man/man8 ] || mkdir -p /usr/local/share/man/man8 || return
cp -p -- "$PROGRAM_DIR/src/bsdping/ping.8" /usr/local/share/man/man8/ping.bsd.8 || return
[ -h /usr/local/bin/ping4.bsd ] || ln -s ping.bsd /usr/local/bin/ping4.bsd || true
}
install_sniproxy() {
[ -z "$run_sniproxy" ] && return
apt-get install --no-upgrade --no-install-recommends --yes sniproxy || return
# sniproxy.service that distributed in Debian had a bug, where it is
# missing option '-f' to sniproxy(8) to keep it running in foreground:
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1032295
# While the bug has been fixed in trixie/sid, it still presents in
# latest stable and oldstable releases. Install own copy of
# sniproxy.service to avoid the bug.
rm -f /etc/systemd/system/sniproxy.service || return
cp "$PROGRAM_DIR/systemd-units/sniproxy.service" /etc/systemd/system/
if ! grep -Fqs LD_PRELOAD=bifcsocket.so /etc/default/sniproxy; then
rm -f /etc/default/sniproxy || return
cat > /etc/default/sniproxy << EOT
# Additional options that are passed to the Daemon.
#DAEMON_ARGS="-c /etc/sniproxy.conf"
# Bind connect (client) sockets to the specified interface.
LD_PRELOAD=bifcsocket.so
BIND_INTERFACE=$default_route_interface
#BIND_INTERFACE=CloudflareWARP
EOT
fi
if ! grep -q "^# sniproxy configuration for RNCN" /etc/sniproxy.conf; then
rm -f /etc/sniproxy.conf || return
cat > /etc/sniproxy.conf << EOT
# sniproxy configuration for RNCN
user daemon
error_log {
# Log to the daemon syslog facility
syslog daemon
# Alternatively we could log to file
#filename /var/log/sniproxy/sniproxy.log
# Control the verbosity of the log
priority notice
}
resolver {
nameserver ${fallback_name_server:-$DEFAULT_FALLBACK_NAME_SERVER}
}
listener $node_address:443 {
proto tls
access_log {
filename /var/log/sniproxy/https_access.log
priority notice
}
}
# The default table for listeners without a specified table
table {
.* *
}
EOT
fi
[ -d /var/log/sniproxy ] || mkdir /var/log/sniproxy || return
chown daemon /var/log/sniproxy
}
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;
filter import_from_ospf_filter {
if net = 0.0.0.0/0 then reject;
$clip_import_from_ospf_filter
accept;
}
protocol device {
}
protocol direct {
ipv4;
ipv6;
interface "rncn-*";
interface "lo" 10.100.254.0/24;
interface "lo" 10.100.253.0/24;
interface "lo" 10.10.10.10;
interface "lo" 10.0.0.10;
$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
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;
include "ipv$ip_vers-export-to-ospf-filter";
import filter import_from_ospf_filter;
};
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 20;
wait 40;
dead 80;
cost 20; # ospf-v$v-backbone-interface-cost:$interface
};
EOT
done
cat << EOT
$clip_bird_ospf_default_area
};
}
EOT
[ -e /etc/bird/ipv$ip_vers-export-to-ospf-filter ] || echo "export filter {ospf_metric1 = 0; accept;};" > /etc/bird/ipv$ip_vers-export-to-ospf-filter
done
} > /etc/bird/bird.conf.template
write_rncn_link_status_monitor_sh() {
cat << EOT
#!/bin/sh
PING_PAYLOAD_SIZE=104
export PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
export LANG=C
EOT
cat "$PROGRAM_DIR/src/rncn-link-status-monitor.calc_metric.sh"
cat << EOT
set -e
wait_time=$mon_initial_ping_duration
while true; do
sleep $mon_minimal_ping_wait & sleep_pid=\$!
EOT
local peer my_in_tunnel_addr peer_in_tunnel_addr interface
for peer in $peers; do
eval "my_in_tunnel_addr=\"\$peer_${peer}_ip4_addr\""
eval "peer_in_tunnel_addr=\"\$peer_${peer}_remote_ip4_addr\""
eval "peer_metric_offset=\"\$peer_${peer}_metric_offset\""
printf ' calc_metric "%s" "%s" $wait_time %s & ping_%s_pid=$!\n' \
"$my_in_tunnel_addr" "$peer_in_tunnel_addr" "$peer_metric_offset" $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
wait \$sleep_pid
wait_time=$mon_ping_duration
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_ifupdown_cloned_interfaces() {
local need_cloning=
local peer tunnel_type
for peer in $peers; do
eval "tunnel_type=\"\$peer_${peer}_tunnel_type\""
case "$tunnel_type" in
gre|gre-fou)
need_cloning=1
break
;;
esac
done
[ -z "$need_cloning" ] && return
[ -d /etc/network/interfaces.d ] || mkdir /etc/network/interfaces.d || return
if ! grep -Eq '^[[:space:]]*source[[:space:]]+(/etc/network/)?interfaces\.d/\*$' /etc/network/interfaces; then
make_sure_nonempty_file_ends_with_new_line /etc/network/interfaces || true
echo "source /etc/network/interfaces.d/*" >> /etc/network/interfaces || return
fi
local interface bind remote mtu ip4_addr ip6_addr i
rm -f /etc/network/interfaces.d/rncn-tunnels || return
for peer in $peers; do
eval "tunnel_type=\"\$peer_${peer}_tunnel_type\""
eval "interface=\"rncn-\$peer_${peer}_name\""
eval "bind=\"\$peer_${peer}_bind\""
eval "remote=\"\$peer_${peer}_remote\""
eval "mtu=\"\$peer_${peer}_mtu\""
eval "ip4_addr=\"\$peer_${peer}_ip4_addr\""
case "$tunnel_type" in
gre)
if [ -z "$bind" ] || [ -z "$remote" ]; then
printf "Both 'bind' and 'remote' must be specified for peer %s that uses GRE tunnel\\n" "$peer" 1>&2
return 1
fi
if [ "${bind%:*}" != "$bind" ] || [ "${remote%:*}" != "$remote" ]; then
printf 'Incorrect configuration for peer %s: GRE tunnel cannot have port number\n' "$peer" 1>&2
return 1
fi
cat << EOT
auto $interface
iface $interface inet static
pre-up ip tunnel add $interface mode gre local $bind remote $remote
post-down ip tunnel del $interface
EOT
;;
gre-fou)
if [ -z "$bind" ] || [ -z "$remote" ]; then
printf "Both 'bind' and 'remote' must be specified for peer %s that uses GRE/FOU tunnel\\n" "$peer" 1>&2
return 1
fi
if [ "${bind%:*}" = "$bind" ] || [ "${remote%:*}" = "$remote" ]; then
printf 'Incorrect configuration for peer %s: GRE/FOU tunnel addresses must have port numbers\n' "$peer" 1>&2
return 1
fi
cat << EOT
auto $interface
iface $interface inet static
pre-up ip link add $interface type gre local ${bind%:*} remote ${remote%:*} encap fou encap-sport ${bind##*:} encap-dport ${remote##*:}
pre-up ip fou add port ${bind##*:} local ${bind%:*} peer ${remote%:*} peer_port ${remote##*:} ipproto 47
post-down ip fou del port ${bind##*:} local ${bind%:*} peer ${remote%:*} peer_port ${remote##*:} ipproto 47
post-down ip link del $interface
EOT
;;
*)
continue
;;
esac
[ -n "$mtu" ] && printf ' pre-up ifconfig %s mtu %s\n' "$interface" "$mtu"
printf ' address %s/31\n' "$ip4_addr"
cat << EOT
up echo 0 > /proc/sys/net/ipv4/conf/$interface/send_redirects
up echo 0 > /proc/sys/net/ipv4/conf/$interface/accept_redirects
address $ip4_addr/31
EOT
i=0
while
eval "ip6_addr=\"\$peer_${peer}_ip6_addr_$i\""
[ -n "$ip6_addr" ]
do
printf 'iface %s inet6 static\n' "$interface"
if [ $i = 0 ]; then
cat << EOT
up echo 0 > /proc/sys/net/ipv6/conf/$interface/accept_redirects
up echo 1 > /proc/sys/net/ipv6/conf/$interface/forwarding
EOT
fi
printf ' address %s/127\n' "$ip6_addr"
i=$((i+1))
done
done > /etc/network/interfaces.d/rncn-tunnels
}
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 "[ \"\$peer_${peer}_tunnel_type\" != wireguard ]" && continue
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
cat << EOT
PostUp = echo 0 > /proc/sys/net/ipv4/conf/%i/send_redirects
PostUp = echo 0 > /proc/sys/net/ipv4/conf/%i/accept_redirects
EOT
[ -n "$first_ip6_addr" ] && cat << EOT
PostUp = echo 0 > /proc/sys/net/ipv6/conf/%i/accept_redirects
PostUp = echo 1 > /proc/sys/net/ipv6/conf/%i/forwarding
PostUp = ip6tables -t nat -A POSTROUTING -s `print_peer_in_tunnel_inet6_address $first_ip6_addr` -o $default_route_interface -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 $default_route_interface -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
}
install_wireguard_peer_resolver() {
[ -z "$wg_use_peer_resolver" ] && return
cat > /etc/systemd/system/wireguard-peer-resolver.service << EOT
[Unit]
Description = WireGuard peer name resolver
After = wg-quick.target network-online.target
[Service]
Type = simple
ExecStart = /bin/sh "$PROGRAM_DIR/src/wireguard-peer-resolver.sh"
[Install]
WantedBy=multi-user.target
EOT
}
install_wireguard_periodic_rebind_port() {
[ -z "$wg_periodic_rebind_port" ] && return
cat > /etc/systemd/system/wireguard-periodic-rebind-port.service << EOT
[Unit]
Description = WireGuard periodic rebind port
After = wg-quick.target network-online.target
[Service]
Type = simple
ExecStart = /bin/sh "$PROGRAM_DIR/src/wireguard-periodic-rebind-port.sh"
[Install]
WantedBy=multi-user.target
EOT
}
write_resolv_conf() {
[ -z "$replace_resolv_conf" ] && return
rm -f /etc/resolv.conf
echo "nameserver 10.10.10.10" > /etc/resolv.conf || 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 udp -m udp --dport 53 -j DNAT --to-destination "${fallback_name_server:-$DEFAULT_FALLBACK_NAME_SERVER}"
fi
}
install_rncn_dns() {
[ -z "$run_dns_server" ] && return
[ -d "$PROGRAM_DIR/dns" ] || mkdir "$PROGRAM_DIR/dns" || return
#apt-get install --no-upgrade --no-install-recommends --yes cpp || return
local now
now="`date +%s`" || return
if ! mtim="`date -r \"$PROGRAM_DIR/dns/Country.mmdb\" +%s 2> /dev/null`" || [ $((now-mtim)) -gt 86400 ]; then
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"
fi
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
ExecStartPre=-/sbin/ifup lo:dns1 lo:dns2
ExecStartPre=-/sbin/iptables -t nat -A PREROUTING -d 10.0.0.10 -p udp -m udp --dport $dns_server_port -j DNAT --to-destination 10.10.10.10
ExecStart="$PROGRAM_DIR/dns/mihomo" -d "$PROGRAM_DIR/dns"
ExecStopPost=-/sbin/iptables -t nat -D PREROUTING -d 10.0.0.10 -p udp -m udp --dport $dns_server_port -j DNAT --to-destination 10.10.10.10
ExecStopPost=-/sbin/ifdown lo:dns1 lo:dns2
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_server_port' '$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
[ -f "$PROGRAM_DIR/dns/config.yaml" ] && return
. "$PROGRAM_DIR/src/rncn-dns-updater.functions.sh"
check_and_update_config "$dns_server_port" "$dns_config_update_url" "$PROGRAM_DIR/dns/config.yaml"
}
install_preload_objects() {
local gnu_system_type="`dpkg-architecture --query DEB_HOST_MULTIARCH`" || true
for obj in bifsocket bifcsocket noinet6; do
f="/usr/lib/$gnu_system_type/$obj.so"
[ -f "$f" ] && continue
rm -f "$f"
$CC -Os -fno-common $CFLAGS -Wall -fPIC $LDFLAGS --shared "$PROGRAM_DIR/src/$obj.c" -l dl -o "$f" || return
done
}
write_ssh_known_hosts() {
[ -f "$PROGRAM_DIR/config/ssh_known_hosts" ] || return 0
set -f
make_sure_nonempty_file_ends_with_new_line /etc/ssh/ssh_known_hosts || true
while read line; do
line="${line%%\#*}"
[ -z "$line" ] && continue
set -- $line
[ $# -lt 3 ] && continue
cut -d " " -f 1,2,3 /etc/ssh/ssh_known_hosts | grep --fixed-strings --line-regexp --quiet "$1 $2 $3" && continue
printf %s\\n "$*"
done < "$PROGRAM_DIR/config/ssh_known_hosts" >> /etc/ssh/ssh_known_hosts
set +f
}
LOGO_DATA="2,4 0,8 2,11 0,3 2,5 0,3 2,5 0,4 2,9 0,2 2,5 0,3 2,5
1,3 2,3 0,5 1,2 2,3 1,5 2,3 0 1,2 2,5 0 1,2 2,3 0,4 2,3 1,5 2,3 1,2 2,5 0 1,2 2,3
0,2 1,3 2,3 0,4 1 2,3 0,4 1 2,3 0,2 1 2,6 0 1 2,3 0,3 2,3 0,5 1,3 0,2 1 2,6 0 1 2,3
0,4 1,3 2,3 0,2 1 2,10 0,3 1 2,3 1 2,3 1 2,3 0,2 1 2,3 0,10 1 2,3 1 2,3 1 2,3
0,5 2,3 1 0,3 1 2,3 1,3 2,3 0,4 1 2,3 1,2 2,6 0,2 1 2,3 0,10 1 2,3 1,2 2,6
0,3 2,3 1 0,5 1 2,3 0,2 1,2 2,3 0,3 1 2,3 0 1,2 2,5 0,2 1,2 2,3 0,5 2,3 0 1 2,3 0 1,2 2,5
2,4 1 0,7 2,5 0,2 1,2 2,4 0 2,5 0 1,2 2,5 0,2 1,2 2,9 0,2 2,5 0 1,2 2,5 0 2,10
1,3 0,8 1,5 0,4 1,4 0 1,5 0,3 1,5 0,4 1,9 0,2 1,5 0,3 1,5 0 1,10"
write_login_logo() {
[ -z "$add_login_logo" ] && return
grep -q ^terminal_supports_utf_8 /etc/profile && return
{
cat << EOT
terminal_supports_utf_8() {
case "\$TERM" in
"")
false
;;
ansi)
false
;;
linux*)
false
;;
sun*)
false
;;
dtterm)
false
;;
*)
true
;;
esac
}
if [ -z "\$SUDO_USER" ] && window_size="\`stty size 0>&2 2> /dev/null\`" && columns="\${window_size#* }" && [ "\$columns" != "\$window_size" ]; then
if [ "\$columns" -ge 78 ]; then
_locale="\${LC_CTYPE-\$LANG}"
_locale="\${_locale%%@*}"
_charset="\${_locale#*.}"
if [ "\$_charset" != "\$_locale" ] && [ "\$_charset" = UTF-8 ] && terminal_supports_utf_8; then
EOT
printf %s " printf '%s\\n'"
printf %s\\n "$LOGO_DATA" | while read line; do
printf ' \\\n "'
for draw in $line; do
color=${draw%,*}
[ $draw = $color ] && count=1 || count=${draw#*,}
case $color in
0)
c=\
;;
1)
c=â–‘
;;
2)
c=â–ˆ
;;
*)
echo e!
exit 1
;;
esac
while [ $count -gt 0 ]; do
printf %s "$c"
count=$((count-1))
done
done
printf \"
done
printf '\n else\n'
printf %s\\n "$LOGO_DATA" | while read line; do
printf " printf '"
for draw in $line; do
color=${draw%,*}
[ $draw = $color ] && count=1 || count=${draw#*,}
case $color in
0)
printf '\\033[0m'
;;
1)
printf '\\033[100m'
;;
2)
printf '\\033[103m'
;;
*)
echo e!
exit 1
;;
esac
while [ $count -gt 0 ]; do
printf \
count=$((count-1))
done
done
printf "\\\\033[0m\\\\n'\\n"
done
cat << EOT
fi
unset _locale _charset
printf '%s\\n' "" \\
" Welcome to RNCN.NET Network Infrastructure Service" \\
EOT
printf ' "'
local padding_len=$(((78-${#node_name}+1)/2))
while [ $padding_len -gt 0 ]; do
printf \
padding_len=$((padding_len-1))
done
cat << EOT
$node_name"
else
printf 'Welcome to RNCN node:\\n%s\\n' "$node_name"
fi
fi 1>&2
unset window_size
EOT
} >> /etc/profile
}
write_no_vm_drop_caches_service_file() {
[ -f /etc/systemd/system/no-vm-drop-caches.service ] && return
rm -f /etc/systemd/system/no-vm-drop-caches.service
cp "$PROGRAM_DIR/systemd-units/no-vm-drop-caches.service" /etc/systemd/system/
}
# Just write a systemd unit file, the actual program could be missing
write_iperf3_service_file() {
[ -f /etc/systemd/system/iperf3.service ] && return
rm -f /etc/systemd/system/iperf3.service
cat > /etc/systemd/system/iperf3.service << EOT
[Unit]
Description=iperf3 server
Documentation=man:iperf3(1)
After=network.target auditd.service
[Service]
Type=simple
Restart=always
RestartSec=15
User=iperf3
ExecStart=/usr/bin/iperf3 --version4 --bind $node_address --server --interval 0
SuccessExitStatus=1
[Install]
WantedBy=multi-user.target
EOT
}
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
if [ ! -f "$PROGRAM_DIR/src/bsdping/build.sh" ] || [ ! -f "$PROGRAM_DIR/src/bsdping/ping.c" ]; then
echo "Warning: Missing BSD ping source code"
echo "You may need to run 'git submodule update --init' to fetch it"
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
check_super_user
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 || true
append_unique_line "set enable-bracketed-paste off" /etc/inputrc
make_sure_nonempty_file_ends_with_new_line /etc/bash.bashrc || true
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
configure_zsh
apt-get install --no-upgrade --no-install-recommends --yes openssh-server readline-common wireguard-tools bird2 iptables net-tools sudo jq bc locales bind9-dnsutils bind9-host dpkg-dev gcc libc6-dev wget
configure_locales
install_bsd_ping
install_sniproxy
configure_sshd
configure_pam_sshd
create_rncn_unprivileged_user
configure_sudo_pam
configure_sudoers
configure_systemd_link
configure_lookback_interface
configure_iptables
configure_ip6tables
configure_time_zone
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/bird.service" /etc/systemd/system/
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_ifupdown_cloned_interfaces
write_wireguard_configuration_files
install_wireguard_peer_resolver
install_wireguard_periodic_rebind_port
install_rncn_dns
install_preload_objects
write_ssh_known_hosts
write_login_logo
write_no_vm_drop_caches_service_file
write_iperf3_service_file
;;
enable-services)
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
check_super_user
systemctl daemon-reload || exit
for peer in $peers; do
eval "[ \"\$peer_${peer}_tunnel_type\" != wireguard ]" && continue
eval "interface=\"rncn-\$peer_${peer}_name\""
systemctl enable --now wg-quick@$interface
done
[ -n "$wg_use_peer_resolver" ] && systemctl enable --now wireguard-peer-resolver
[ -n "$wg_periodic_rebind_port" ] && systemctl enable --now wireguard-periodic-rebind-port
[ -f /etc/systemd/system/iptables-legacy.service ] || [ -f /etc/systemd/system/iptables-nft.service ] || systemctl enable --now iptables
[ -n "$ipv6_default_router" ] && systemctl enable --now ip6tables
systemctl enable --now rncn-bird-link-status-monitor bird
[ -n "$ra_list" ] && systemctl enable --now rncn-bird-ra-updater
if [ -n "$run_dns_server" ]; then
systemctl enable --now rncn-dns
[ -n "$dns_config_update_url" ] && systemctl enable --now rncn-dns-updater
fi
[ -n "$run_sniproxy" ] && systemctl enable --now sniproxy
systemctl enable --now no-vm-drop-caches
true
;;
*)
printf "Unknown subcommand '%s'\\n" "$1" 1>&2
exit 255
;;
esac