| #!/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 |
| |
| # 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= |
| ipv4_default_router= |
| ipv6_default_router= |
| run_dns_unlock_server= |
| replace_sysctl_conf= |
| 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) |
| eval "$key=\"\$value\"" |
| ;; |
| .run_dns_unlock_server|.replace_sysctl_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_ipv4_route_targets="$local_wan_ipv4_route_targets$line |
| " |
| ;; |
| esac |
| done < "$1/main.cfg" |
| for key in node_address ipv4_default_router ipv6_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 |
| } |
| |
| 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_unlock_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 |
| } |
| |
| 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" |
| } |
| |
| 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 |
| } |
| |
| 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 \} |
| 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 |
| rm -f /etc/bird/bird.conf.old |
| mv /etc/bird/bird.conf /etc/bird/bird.conf.old |
| 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" |
| done |
| } |
| |
| install_rncn_dns() { |
| [ -z "$run_dns_unlock_server" ] && return |
| |
| GEOIP_MMDB_URL=https://cdn.libz.so/gh/JohnnySun/geoip/release/Country.mmdb |
| #GEOIP_MMDB_URL=https://github.com/JohnnySun/geoip/raw/release/Country.mmdb |
| wget "$GEOIP_MMDB_URL" --output-document $PROGRAM_DIR/rncn_dns/Country.mmdb |
| cp -f $PROGRAM_DIR/rncn_dns/Country.mmdb $PROGRAM_DIR/rncn_dns/dns/ |
| cp -f $PROGRAM_DIR/rncn_dns/Country.mmdb $PROGRAM_DIR/rncn_dns/media_unlocker/ |
| cp -f $PROGRAM_DIR/rncn_dns/Country.mmdb $PROGRAM_DIR/rncn_dns/media_unlock_server/ |
| if [ ! -f "$PROGRAM_DIR/rncn_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/rncn_dns/mihomo" |
| chmod 755 "$PROGRAM_DIR/rncn_dns/mihomo" |
| fi |
| ln -sf $PROGRAM_DIR/rncn_dns /etc/ |
| cp -f $SUPPORT_FILE_DIR/rncn_dns/rncn-dns.service /etc/systemd/system/ |
| cp -f $SUPPORT_FILE_DIR/rncn_dns/rncn-media-unlocker.service /etc/systemd/system/ |
| cp -f $SUPPORT_FILE_DIR/rncn_dns/rncn-dns-updater.service /etc/systemd/system/ |
| cp -f $SUPPORT_FILE_DIR/rncn_dns/rncn-media-unlock-server.service /etc/systemd/system/ |
| } |
| |
| |
| 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 [ "`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'`" |
| sed -i -E "s/(127\\.0\\.1\\.1[[:space:]])/\\1$escaped_node_name/" /etc/hosts |
| else |
| make_sure_nonempty_file_ends_with_new_line /etc/hosts |
| printf '127.0.1.1 %s\n' "$node_name" > /etc/hosts |
| fi |
| fi |
| make_sure_nonempty_file_ends_with_new_line /etc/inputrc |
| append_unique_line /etc/inputrc "set enable-bracketed-paste off" |
| make_sure_nonempty_file_ends_with_new_line /etc/bash.bashrc |
| append_unique_line /etc/bash.bashrc "set +H" |
| append_unique_line /etc/bash.bashrc HISTCONTROL=ignoreboth |
| append_unique_line /etc/bash.bashrc "shopt -s histappend" |
| modify_etc_profile |
| apt-get install -y wireguard-tools bird2 iptables net-tools sudo git jq bc |
| create_rncn_unprivileged_user |
| configure_sudo_pam |
| configure_sudoers |
| configure_lookback_interface |
| [ -d /etc/bird ] || mkdir /etc/bird || exit |
| write_bird_conf_template |
| write_rncn_link_status_monitor_sh |
| write_ra_list |
| write_ra_updater |
| write_wireguard_configuration_files |
| install_rncn_dns |
| # TODO |
| ;; |
| 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 |