commit 9383fa19f437201f0b1d900bcef3ed8ca2ad0761 Author: hhf Date: Sat Dec 7 12:50:00 2024 +0530 Add tailscale-forward.sh diff --git a/tailscale-forward.sh b/tailscale-forward.sh new file mode 100644 index 0000000..b7acb92 --- /dev/null +++ b/tailscale-forward.sh @@ -0,0 +1,489 @@ +#!/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 \ No newline at end of file