github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/nat/service_iptables.go (about)

     1  /*
     2   * Copyright (C) 2017 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package nat
    19  
    20  import (
    21  	"fmt"
    22  	"strconv"
    23  	"sync"
    24  
    25  	"github.com/pkg/errors"
    26  	"github.com/rs/zerolog/log"
    27  
    28  	"github.com/mysteriumnetwork/node/config"
    29  	"github.com/mysteriumnetwork/node/firewall/iptables"
    30  	"github.com/mysteriumnetwork/node/utils"
    31  	"github.com/mysteriumnetwork/node/utils/cmdutil"
    32  )
    33  
    34  type serviceIPTables struct {
    35  	mu        sync.Mutex
    36  	rules     []iptables.Rule
    37  	ipForward serviceIPForward
    38  }
    39  
    40  const (
    41  	chainMyst        = "MYST"
    42  	chainInput       = "INPUT"
    43  	chainForward     = "FORWARD"
    44  	chainPreRouting  = "PREROUTING"
    45  	chainPostRouting = "POSTROUTING"
    46  )
    47  
    48  // Setup sets NAT/Firewall rules for the given NATOptions.
    49  func (svc *serviceIPTables) Setup(opts Options) (appliedRules []interface{}, err error) {
    50  	log.Info().Msg("Setting up NAT/Firewall rules")
    51  	svc.mu.Lock()
    52  	defer svc.mu.Unlock()
    53  
    54  	// Store applied rules so we can remove if setup exits prematurely (one of the latter rules fails to apply)
    55  	var applied []iptables.Rule
    56  	defer func() {
    57  		if err == nil {
    58  			return
    59  		}
    60  		log.Warn().Msg("Error detected, clearing up rules that were already setup")
    61  		for _, rule := range applied {
    62  			if err := svc.removeRule(rule); err != nil {
    63  				log.Error().Err(err).Msg("Could not remove rule")
    64  			}
    65  		}
    66  	}()
    67  
    68  	for _, rule := range makeIPTablesRules(opts) {
    69  		if err := svc.applyRule(rule); err != nil {
    70  			return nil, err
    71  		}
    72  		applied = append(applied, rule)
    73  	}
    74  	log.Info().Msg("Setting up NAT/Firewall rules... done")
    75  	return untypedIptRules(applied), nil
    76  }
    77  
    78  // Del removes given NAT/Firewall rules that were previously set up.
    79  func (svc *serviceIPTables) Del(rules []interface{}) (err error) {
    80  	log.Info().Msg("Deleting NAT/Firewall rules")
    81  	svc.mu.Lock()
    82  	defer svc.mu.Unlock()
    83  
    84  	errs := utils.ErrorCollection{}
    85  	for _, rule := range typedIptRules(rules) {
    86  		log.Trace().Msgf("Deleting rule: %v", rule)
    87  		if err := svc.removeRule(rule); err != nil {
    88  			errs.Add(err)
    89  		}
    90  	}
    91  	err = errs.Error()
    92  	log.Info().Err(err).Msg("Deleting NAT/Firewall rules... done")
    93  	return err
    94  }
    95  
    96  // Enable enables NAT service.
    97  func (svc *serviceIPTables) Enable() error {
    98  	if config.GetBool(config.FlagUserMode) || config.GetBool(config.FlagUserspace) {
    99  		log.Info().Msg("Usermode active, nothing to do with iptables")
   100  		return nil
   101  	}
   102  
   103  	err := svc.prepare()
   104  	if err != nil {
   105  		log.Warn().Err(err).Msg("Failed to prepare iptables setup")
   106  	}
   107  
   108  	err = svc.ipForward.Enable()
   109  	if err != nil {
   110  		log.Warn().Err(err).Msg("Failed to enable IP forwarding")
   111  	}
   112  	return err
   113  }
   114  
   115  // Disable disables NAT service and deletes all rules.
   116  func (svc *serviceIPTables) Disable() error {
   117  	if config.GetBool(config.FlagUserMode) || config.GetBool(config.FlagUserspace) {
   118  		log.Info().Msg("Usermode active, nothing to do with iptables")
   119  		return nil
   120  	}
   121  
   122  	svc.ipForward.Disable()
   123  	err := svc.Del(untypedIptRules(svc.rules))
   124  	if err != nil {
   125  		return fmt.Errorf("failed to cleanup iptables rules")
   126  	}
   127  
   128  	err = svc.clean()
   129  	if err != nil {
   130  		return fmt.Errorf("failed to cleanup iptables chains")
   131  	}
   132  
   133  	return nil
   134  }
   135  
   136  func (svc *serviceIPTables) applyRule(rule iptables.Rule) error {
   137  	if err := iptablesExec(rule.ApplyArgs()...); err != nil {
   138  		return err
   139  	}
   140  	svc.rules = append(svc.rules, rule)
   141  	return nil
   142  }
   143  
   144  func (svc *serviceIPTables) removeRule(rule iptables.Rule) error {
   145  	if err := iptablesExec(rule.RemoveArgs()...); err != nil {
   146  		return err
   147  	}
   148  	for i := range svc.rules {
   149  		if svc.rules[i].Equals(rule) {
   150  			svc.rules = append(svc.rules[:i], svc.rules[i+1:]...)
   151  			break
   152  		}
   153  	}
   154  	return nil
   155  }
   156  
   157  func (svc *serviceIPTables) prepare() error {
   158  	err := iptablesExec("--new", chainMyst, "--table", "nat")
   159  	if err != nil {
   160  		return fmt.Errorf("failed to create MYST iptables chain: %w", err)
   161  	}
   162  
   163  	for _, ipNet := range protectedNetworks() {
   164  		// Protect private networks rule
   165  		err = svc.applyRule(iptables.AppendTo(chainMyst).RuleSpec(
   166  			"--destination", ipNet.String(), "--jump", "DNAT", "--to-destination", "240.0.0.1", "--table", "nat"))
   167  		if err != nil {
   168  			return fmt.Errorf("failed to create blackhole rule in the MYST iptables chain: %w", err)
   169  		}
   170  	}
   171  
   172  	return nil
   173  }
   174  
   175  func (svc *serviceIPTables) clean() error {
   176  	err := iptablesExec("--flush", chainMyst, "--table", "nat")
   177  	if err != nil {
   178  		return fmt.Errorf("failed to flush MYST iptables chain: %w", err)
   179  	}
   180  
   181  	err = iptablesExec("--delete-chain", chainMyst, "--table", "nat")
   182  	if err != nil {
   183  		return fmt.Errorf("failed to delete MYST iptables chain: %w", err)
   184  	}
   185  
   186  	return nil
   187  }
   188  
   189  func makeIPTablesRules(opts Options) (rules []iptables.Rule) {
   190  	vpnNetwork := opts.VPNNetwork.String()
   191  
   192  	rule := iptables.InsertAt(chainPreRouting, 1).RuleSpec(
   193  		"--source", vpnNetwork, "--jump", chainMyst, "--table", "nat")
   194  	rules = append(rules, rule)
   195  
   196  	// DNS port redirect rule (udp)
   197  	rule = iptables.InsertAt(chainMyst, 1).RuleSpec(
   198  		"--destination", opts.DNSIP.String(), "--protocol", "udp", "--dport", strconv.Itoa(53),
   199  		"--jump", "REDIRECT",
   200  		"--to-ports", strconv.Itoa(config.GetInt(config.FlagDNSListenPort)),
   201  		"--table", "nat",
   202  	)
   203  	rules = append(rules, rule)
   204  
   205  	// DNS port redirect rule (tcp)
   206  	rule = iptables.InsertAt(chainMyst, 1).RuleSpec(
   207  		"--destination", opts.DNSIP.String(), "--protocol", "tcp", "--dport", strconv.Itoa(53),
   208  		"--jump", "REDIRECT",
   209  		"--to-ports", strconv.Itoa(config.GetInt(config.FlagDNSListenPort)),
   210  		"--table", "nat",
   211  	)
   212  	rules = append(rules, rule)
   213  
   214  	// NAT forwarding rule
   215  	rule = iptables.AppendTo(chainPostRouting).RuleSpec("--source", vpnNetwork, "!", "--destination", vpnNetwork,
   216  		"--jump", "SNAT", "--to", opts.ProviderExtIP.String(),
   217  		"--table", "nat")
   218  	rules = append(rules, rule)
   219  
   220  	// ACCEPT forwarding rules
   221  	rules = append(rules, iptables.AppendTo(chainForward).RuleSpec("--source", vpnNetwork, "--jump", "ACCEPT"))
   222  	rules = append(rules, iptables.AppendTo(chainForward).RuleSpec("--destination", vpnNetwork, "--jump", "ACCEPT"))
   223  
   224  	return rules
   225  }
   226  
   227  func iptablesExec(args ...string) error {
   228  	args = append([]string{"/usr/sbin/iptables"}, args...)
   229  	if err := cmdutil.SudoExec(args...); err != nil {
   230  		return errors.Wrap(err, "error calling IPTables")
   231  	}
   232  	return nil
   233  }
   234  
   235  func untypedIptRules(rules []iptables.Rule) []interface{} {
   236  	res := make([]interface{}, len(rules))
   237  	for i := range rules {
   238  		res[i] = rules[i]
   239  	}
   240  	return res
   241  }
   242  
   243  func typedIptRules(rules []interface{}) []iptables.Rule {
   244  	res := make([]iptables.Rule, len(rules))
   245  	for i := range rules {
   246  		res[i] = rules[i].(iptables.Rule)
   247  	}
   248  	return res
   249  }