#!/bin/bash # Colors and formatting RED='\033[0;31m' GREEN='\033[0;32m' BLUE='\033[0;34m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' # No Color BOLD='\033[1m' # Default essential ports DEFAULT_TCP_PORT=22 DEFAULT_UDP_PORT=41641 # Check if running as root if [ "$EUID" -ne 0 ]; then echo -e "${RED}${BOLD}[ERROR]${NC} Please run as root" exit 1 fi # Check for iptables-persistent installation check_iptables_persistent() { if ! dpkg -l | grep -q "iptables-persistent"; then echo -e "${YELLOW}[INFO]${NC} iptables-persistent is not installed" read -p $'\033[0;36mWould you like to install iptables-persistent? [Y/n]: \033[0m' install_choice install_choice=${install_choice:-Y} if [[ $install_choice =~ ^[Yy]$ ]]; then # Pre-seed the debconf database to avoid interactive prompt echo iptables-persistent iptables-persistent/autosave_v4 boolean true | debconf-set-selections echo iptables-persistent iptables-persistent/autosave_v6 boolean true | debconf-set-selections apt-get update if apt-get install -y iptables-persistent; then echo -e "${GREEN}${BOLD}[SUCCESS]${NC} iptables-persistent installed successfully" else echo -e "${RED}${BOLD}[ERROR]${NC} Failed to install iptables-persistent" return 1 fi else echo -e "${YELLOW}[WARNING]${NC} Continuing without iptables-persistent" return 1 fi fi return 0 } # Enhanced save function with iptables-persistent support save_iptables() { echo -e "${YELLOW}[INFO]${NC} Saving iptables rules..." # Create backup directory if it doesn't exist mkdir -p /etc/iptables/backup # Create timestamped backup timestamp=$(date +%Y%m%d_%H%M%S) backup_file="/etc/iptables/backup/rules.v4.${timestamp}" # Backup current rules if ! iptables-save > "$backup_file"; then echo -e "${RED}${BOLD}[ERROR]${NC} Failed to create backup" return 1 fi # Try saving with netfilter-persistent first if command -v netfilter-persistent &>/dev/null; then if netfilter-persistent save; then echo -e "${GREEN}${BOLD}[SUCCESS]${NC} Rules saved using netfilter-persistent" echo -e "${GREEN}[INFO]${NC} Backup saved to: ${backup_file}" return 0 fi fi # Fallback to manual save if [ -d "/etc/iptables" ]; then if iptables-save > /etc/iptables/rules.v4; then echo -e "${GREEN}${BOLD}[SUCCESS]${NC} Rules saved to /etc/iptables/rules.v4" echo -e "${GREEN}[INFO]${NC} Backup saved to: ${backup_file}" return 0 fi fi echo -e "${RED}${BOLD}[ERROR]${NC} Failed to save iptables rules" return 1 } # Enhanced restore function with iptables-persistent support restore_iptables() { echo -e "${YELLOW}[INFO]${NC} Attempting to restore previous rules..." # Try restoring from backup first local latest_backup=$(ls -t /etc/iptables/backup/rules.v4.* 2>/dev/null | head -n1) if [ -n "$latest_backup" ]; then echo -e "${YELLOW}[INFO]${NC} Restoring from latest backup: ${latest_backup}" if iptables-restore < "$latest_backup"; then echo -e "${GREEN}${BOLD}[SUCCESS]${NC} Rules restored from backup" # Save the restored rules save_iptables return 0 fi fi # Fallback to default rules file if [ -f /etc/iptables/rules.v4 ]; then echo -e "${YELLOW}[INFO]${NC} Attempting to restore from /etc/iptables/rules.v4" if iptables-restore < /etc/iptables/rules.v4; then echo -e "${GREEN}${BOLD}[SUCCESS]${NC} Rules restored from default file" return 0 fi fi echo -e "${RED}${BOLD}[ERROR]${NC} Failed to restore iptables rules" return 1 } # Add this near the beginning of the script execution check_iptables_persistent # Function to print section header print_header() { echo -e "\n${BLUE}${BOLD}$1${NC}" echo -e "${BLUE}${BOLD}$(printf '=%.0s' {1..50})${NC}\n" } # Function to display current NAT configuration display_current_config() { print_header "Current NAT Configuration" echo -e "${CYAN}PREROUTING Rules (DNAT):${NC}" if ! iptables -t nat -L PREROUTING -n -v | grep -q "DNAT"; then echo -e "${YELLOW}No DNAT rules configured${NC}" else iptables -t nat -L PREROUTING -n -v | grep "DNAT" fi echo -e "\n${CYAN}POSTROUTING Rules (SNAT/MASQUERADE):${NC}" if ! iptables -t nat -L POSTROUTING -n -v | grep -q "SNAT\|MASQUERADE"; then echo -e "${YELLOW}No SNAT/MASQUERADE rules configured${NC}" else iptables -t nat -L POSTROUTING -n -v | grep "SNAT\|MASQUERADE" fi echo -e "\n${CYAN}Forward Rules:${NC}" if ! iptables -L FORWARD -n -v | grep -q "ACCEPT\|DROP"; then echo -e "${YELLOW}No specific forwarding rules configured${NC}" else iptables -L FORWARD -n -v | grep "ACCEPT\|DROP" fi echo # Empty line for spacing } # Function to get IP and interface pairs (excluding localhost) get_interfaces() { ip -4 addr | awk '/inet / && !/127.0.0.1/ {print $2 "\t" $NF}' | sed 's/\/[0-9]*//g' } # Get interface name from selected IP get_interface_name() { local ip=$1 ip -4 addr | grep "$ip" | awk '{print $NF}' } # Function to get Tailscale IPs get_tailscale_devices() { tailscale status | grep -v '^#' | awk 'NF>1 {print $2 " - " $1}' } # Function to get interface details for Tailscale device get_tailscale_device_name() { local ip=$1 tailscale status | grep "$ip" | awk '{print $2}' } # Function to get subnet from IP get_subnet() { local ip=$1 ip -4 addr | grep "$ip" | awk '{print $2}' } # Display current configuration at startup display_current_config # Step 1: Get source IP print_header "Step 1: Select source IP address" echo -e "${CYAN}Available interfaces:${NC}" echo -e "${YELLOW}0)${NC} Enter custom IP" # Display interface options declare -a interfaces while IFS=$'\t' read -r ip interface; do interfaces+=("$ip - $interface") done < <(get_interfaces) for i in "${!interfaces[@]}"; do echo -e "${YELLOW}$((i + 1)))${NC} ${interfaces[$i]}" done echo -e "${YELLOW}C)${NC} Cancel" echo -e "${CYAN}Press Enter to select option 1${NC}" read -p $'\033[0;36mSelect an option: \033[0m' choice # Default to option 1 if Enter is pressed choice=${choice:-1} case $choice in 0) read -p $'\033[0;36mEnter custom IP: \033[0m' source_ip source_subnet=$(get_subnet "$source_ip") ;; [1-9]*) if [ "$choice" -le "${#interfaces[@]}" ]; then source_ip=$(echo "${interfaces[$((choice - 1))]}" | cut -d' ' -f1) source_subnet=$(get_subnet "$source_ip") else echo -e "${RED}${BOLD}[ERROR]${NC} Invalid option" exit 1 fi ;; [Cc]) echo -e "${YELLOW}Operation cancelled${NC}" exit 0 ;; *) echo -e "${RED}${BOLD}[ERROR]${NC} Invalid option" exit 1 ;; esac # Step 2: Get target IP print_header "Step 2: Select target Tailscale IP address" echo -e "${CYAN}Available Tailscale devices:${NC}" echo -e "${YELLOW}0)${NC} Enter custom IP" # Display Tailscale devices declare -a tailscale_devices mapfile -t tailscale_devices < <(get_tailscale_devices) for i in "${!tailscale_devices[@]}"; do echo -e "${YELLOW}$((i + 1)))${NC} ${tailscale_devices[$i]}" done echo -e "${YELLOW}C)${NC} Cancel" echo -e "${CYAN}Press Enter to select option 0 (custom IP)${NC}" read -p $'\033[0;36mSelect an option: \033[0m' choice # Default to option 0 if Enter is pressed choice=${choice:-0} case $choice in 0) read -p $'\033[0;36mEnter custom IP: \033[0m' target_ip target_device="custom" ;; [1-9]*) if [ "$choice" -le "${#tailscale_devices[@]}" ]; then selected_device="${tailscale_devices[$((choice-1))]}" target_ip=$(echo "$selected_device" | cut -d' ' -f3) target_device=$(echo "$selected_device" | cut -d' ' -f1) else echo -e "${RED}${BOLD}[ERROR]${NC} Invalid option" exit 1 fi ;; [Cc]) echo -e "${YELLOW}Operation cancelled${NC}" exit 0 ;; *) echo -e "${RED}${BOLD}[ERROR]${NC} Invalid option" exit 1 ;; esac # Step 3: Port Configuration print_header "Step 3: Configure Port Settings" echo -e "${CYAN}Port Configuration Options:${NC}" echo -e "${YELLOW}1)${NC} Use default ports only (SSH TCP 22, Tailscale UDP 41641)" echo -e "${YELLOW}2)${NC} Configure custom ports" echo -e "${CYAN}Press Enter to use default ports only${NC}" read -p $'\033[0;36mSelect an option: \033[0m' port_choice port_choice=${port_choice:-1} declare -a PRESERVED_TCP_PORTS declare -a PRESERVED_UDP_PORTS case $port_choice in 1) PRESERVED_TCP_PORTS=($DEFAULT_TCP_PORT) PRESERVED_UDP_PORTS=($DEFAULT_UDP_PORT) echo -e "${GREEN}Using default TCP port: ${DEFAULT_TCP_PORT}${NC}" echo -e "${GREEN}Using default UDP port: ${DEFAULT_UDP_PORT}${NC}" ;; 2) echo -e "\n${CYAN}Enter TCP ports to preserve (space-separated, e.g., '22 80 443')${NC}" echo -e "${CYAN}Press Enter to use default TCP port ${DEFAULT_TCP_PORT}${NC}" read -p $'\033[0;36mTCP Ports: \033[0m' custom_tcp_ports if [ -z "$custom_tcp_ports" ]; then PRESERVED_TCP_PORTS=($DEFAULT_TCP_PORT) echo -e "${GREEN}Using default TCP port: ${DEFAULT_TCP_PORT}${NC}" else PRESERVED_TCP_PORTS=($custom_tcp_ports) echo -e "${GREEN}Using custom TCP ports: ${custom_tcp_ports}${NC}" fi echo -e "\n${CYAN}Enter UDP ports to preserve (space-separated, e.g., '41641 53')${NC}" echo -e "${CYAN}Press Enter to use default UDP port ${DEFAULT_UDP_PORT}${NC}" read -p $'\033[0;36mUDP Ports: \033[0m' custom_udp_ports if [ -z "$custom_udp_ports" ]; then PRESERVED_UDP_PORTS=($DEFAULT_UDP_PORT) echo -e "${GREEN}Using default UDP port: ${DEFAULT_UDP_PORT}${NC}" else PRESERVED_UDP_PORTS=($custom_udp_ports) echo -e "${GREEN}Using custom UDP ports: ${custom_udp_ports}${NC}" fi ;; *) echo -e "${RED}${BOLD}[ERROR]${NC} Invalid option" exit 1 ;; esac # Step 4: Confirmation print_header "Step 4: Review your selections" source_interface=$(get_interface_name "$source_ip") echo -e "${CYAN}Source Details:${NC}" echo -e " IP Address: ${BOLD}$source_ip${NC}" echo -e " Interface: ${BOLD}$source_interface${NC}" echo -e " Subnet: ${BOLD}$source_subnet${NC}" echo -e "\n${CYAN}Target Details:${NC}" echo -e " IP Address: ${BOLD}$target_ip${NC}" echo -e " Device: ${BOLD}$target_device${NC}" echo -e "\n${CYAN}Port Configuration:${NC}" echo -e " Preserved TCP Ports: ${BOLD}${PRESERVED_TCP_PORTS[*]}${NC}" echo -e " Preserved UDP Ports: ${BOLD}${PRESERVED_UDP_PORTS[*]}${NC}" echo -e "\n${YELLOW}This will forward traffic from $source_interface to tailscale0${NC}" read -p $'\033[1;33mApply these changes? [Y/N, default is Y]: \033[0m' confirm # Default to 'Y' if no input is provided confirm=${confirm:-Y} if [[ $confirm =~ ^[Yy]$ ]]; then # Step 5: Apply iptables configuration print_header "Step 5: Applying configuration..." # Create backup before any changes echo -e "${YELLOW}[1/7]${NC} Creating backup..." mkdir -p /etc/iptables/backup timestamp=$(date +%Y%m%d_%H%M%S) backup_file="/etc/iptables/backup/rules.v4.${timestamp}" if ! iptables-save > "$backup_file"; then echo -e "${RED}${BOLD}[ERROR]${NC} Failed to create backup. Aborting configuration" exit 1 fi if [ ! -s "$backup_file" ]; then echo -e "${RED}${BOLD}[ERROR]${NC} Backup file is empty. Aborting configuration" exit 1 fi # Check if IP forwarding is enabled if [ "$(sysctl -n net.ipv4.ip_forward)" -ne 1 ]; then echo -e "${YELLOW}[2/7]${NC} Enabling IP forwarding..." if ! echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf || ! sysctl -w net.ipv4.ip_forward=1; then echo -e "${RED}${BOLD}[ERROR]${NC} Failed to enable IP forwarding" exit 1 fi # Also add to sysctl.d for persistent configuration if ! echo "net.ipv4.ip_forward=1" > /etc/sysctl.d/99-tailscale.conf; then echo -e "${RED}${BOLD}[ERROR]${NC} Failed to create persistent IP forwarding configuration" exit 1 fi sysctl -p /etc/sysctl.d/99-tailscale.conf else echo -e "${YELLOW}[2/7]${NC} IP forwarding is already enabled" fi # Get interface name source_interface=$(get_interface_name "$source_ip") if [ -z "$source_interface" ]; then echo -e "${RED}${BOLD}[ERROR]${NC} Could not determine interface name for IP $source_ip" exit 1 fi # Apply rules echo -e "${YELLOW}[3/7]${NC} Applying new rules..." # Clear existing NAT rules echo -e "${CYAN}Clearing existing NAT and forwarding rules...${NC}" iptables -t nat -F iptables -F FORWARD # Allow preserved TCP ports to reach the host for port in "${PRESERVED_TCP_PORTS[@]}"; do echo -e "${CYAN}Preserving TCP host access on port ${port}${NC}" iptables -t nat -A PREROUTING -d "${source_ip}" -p tcp --dport ${port} -j ACCEPT iptables -A FORWARD -p tcp --dport ${port} -j ACCEPT done # Allow preserved UDP ports to reach the host for port in "${PRESERVED_UDP_PORTS[@]}"; do echo -e "${CYAN}Preserving UDP host access on port ${port}${NC}" iptables -t nat -A PREROUTING -d "${source_ip}" -p udp --dport ${port} -j ACCEPT iptables -A FORWARD -p udp --dport ${port} -j ACCEPT # Additional rule to ensure UDP responses can return iptables -A FORWARD -p udp --sport ${port} -j ACCEPT done # Set up NAT rules for all other ports echo -e "${CYAN}Setting up NAT rules...${NC}" if ! iptables -t nat -A PREROUTING -d "${source_ip}" -j DNAT --to-destination "${target_ip}"; then echo -e "${RED}${BOLD}[ERROR]${NC} Failed to set up DNAT rules" echo -e "${YELLOW}[INFO]${NC} Attempting to restore previous configuration..." if ! iptables-restore < "$backup_file"; then echo -e "${RED}${BOLD}[ERROR]${NC} Failed to restore from backup: $backup_file" echo -e "${RED}${BOLD}[WARNING]${NC} Your firewall rules may be in an inconsistent state" echo -e "${RED}${BOLD}[WARNING]${NC} Please check your configuration manually" else echo -e "${GREEN}[SUCCESS]${NC} Successfully restored previous configuration" fi exit 1 fi if ! iptables -t nat -A POSTROUTING -s "${target_ip}" -j SNAT --to-source "${source_ip}"; then echo -e "${RED}${BOLD}[ERROR]${NC} Failed to set up SNAT rules" echo -e "${YELLOW}[INFO]${NC} Attempting to restore previous configuration..." if ! iptables-restore < "$backup_file"; then echo -e "${RED}${BOLD}[ERROR]${NC} Failed to restore from backup" exit 1 fi exit 1 fi # Set up FORWARD chain echo -e "${CYAN}Setting up forwarding rules...${NC}" iptables -A FORWARD -i ${source_interface} -o tailscale0 -j ACCEPT iptables -A FORWARD -i tailscale0 -o ${source_interface} -j ACCEPT iptables -A FORWARD -p icmp -j ACCEPT iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT # Add Tailscale subnet handling echo -e "${CYAN}Setting up Tailscale subnet rules...${NC}" iptables -t nat -A POSTROUTING -s 100.64.0.0/10 -j SNAT --to-source "${source_ip}" iptables -t nat -A POSTROUTING -s "${source_subnet}" -j MASQUERADE # Verify configuration echo -e "${YELLOW}[4/7]${NC} Verifying configuration..." if ! iptables -t nat -L PREROUTING -n -v >/dev/null 2>&1; then echo -e "${RED}${BOLD}[ERROR]${NC} Configuration verification failed. Restoring previous rules..." if ! iptables-restore < "$backup_file"; then echo -e "${RED}${BOLD}[ERROR]${NC} Failed to restore from backup" exit 1 fi exit 1 fi # Enable service echo -e "${YELLOW}[5/7]${NC} Enabling iptables-persistent service..." if systemctl is-active --quiet netfilter-persistent; then systemctl restart netfilter-persistent else systemctl enable netfilter-persistent systemctl start netfilter-persistent fi # Save current configuration echo -e "${YELLOW}[6/7]${NC} Saving current configuration..." if ! save_iptables; then echo -e "${RED}${BOLD}[ERROR]${NC} Failed to save configuration. Current rules may be lost on reboot" exit 1 fi # Final status echo -e "${YELLOW}[7/7]${NC} Configuration complete" echo -e "\n${GREEN}${BOLD}[SUCCESS]${NC} Configuration completed successfully!" echo -e "${CYAN}Preserved TCP ports: ${PRESERVED_TCP_PORTS[*]}${NC}" echo -e "${CYAN}Preserved UDP ports: ${PRESERVED_UDP_PORTS[*]}${NC}" echo -e "${CYAN}All other ports are forwarded to the Tailscale device${NC}" echo -e "${CYAN}Backup saved to: $backup_file${NC}\n" else echo -e "\n${YELLOW}Operation cancelled by user${NC}" exit 1 fi