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

     1  /*
     2   * Copyright (C) 2018 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  	"net"
    23  	"strings"
    24  	"sync"
    25  
    26  	"github.com/pkg/errors"
    27  	"github.com/rs/zerolog/log"
    28  
    29  	"github.com/mysteriumnetwork/node/config"
    30  	"github.com/mysteriumnetwork/node/utils/cmdutil"
    31  )
    32  
    33  type servicePFCtl struct {
    34  	mu        sync.Mutex
    35  	rules     []string
    36  	ipForward serviceIPForward
    37  }
    38  
    39  // Setup sets NAT/Firewall rules for the given NATOptions.
    40  func (service *servicePFCtl) Setup(opts Options) (appliedRules []interface{}, err error) {
    41  	log.Info().Msg("Setting up NAT/Firewall rules")
    42  	service.mu.Lock()
    43  	defer service.mu.Unlock()
    44  
    45  	rules, err := makePfctlRules(opts)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	if err := service.pfctlExec(rules); err != nil {
    51  		return nil, err
    52  	}
    53  	service.rules = append(service.rules, rules...)
    54  
    55  	return untypedPfctlRules(rules), nil
    56  }
    57  
    58  func (service *servicePFCtl) Del(rules []interface{}) error {
    59  	log.Info().Msg("Deleting NAT/Firewall rules")
    60  	service.mu.Lock()
    61  	defer service.mu.Unlock()
    62  
    63  	rulesToDel := typedPfctlRules(rules)
    64  	var newRules []string
    65  	for _, rule := range service.rules {
    66  		shouldDelete := false
    67  		for _, ruleToDel := range rulesToDel {
    68  			if rule == ruleToDel {
    69  				shouldDelete = true
    70  				break
    71  			}
    72  		}
    73  
    74  		if !shouldDelete {
    75  			newRules = append(newRules, rule)
    76  		}
    77  	}
    78  
    79  	if err := service.pfctlExec(newRules); err != nil {
    80  		return err
    81  	}
    82  
    83  	service.rules = newRules
    84  	log.Info().Msg("Deleting NAT/Firewall rules... done")
    85  	return nil
    86  }
    87  
    88  func makePfctlRules(opts Options) (rules []string, err error) {
    89  	externalIface, err := ifaceByAddress(opts.ProviderExtIP)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	// DNS port redirect rule
    95  	tunnelIface, err := ifaceByAddress(opts.DNSIP)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	rule := fmt.Sprintf("rdr pass on %s inet proto { udp, tcp } from any to %s port 53 -> %s port %d",
   100  		tunnelIface,
   101  		opts.VPNNetwork.String(),
   102  		opts.DNSIP,
   103  		config.GetInt(config.FlagDNSListenPort),
   104  	)
   105  	rules = append(rules, rule)
   106  
   107  	// Protect private networks rule
   108  	networks := protectedNetworks()
   109  	if len(networks) > 0 {
   110  		var targets []string
   111  		for _, network := range networks {
   112  			targets = append(targets, network.String())
   113  		}
   114  		rule := fmt.Sprintf("no nat on %s inet from %s to { %s }",
   115  			externalIface,
   116  			opts.VPNNetwork.String(),
   117  			strings.Join(targets, ", "),
   118  		)
   119  		rules = append(rules, rule)
   120  	}
   121  
   122  	// NAT forwarding rule
   123  	rule = fmt.Sprintf("nat on %s inet from %s to any -> %s",
   124  		externalIface,
   125  		opts.VPNNetwork.String(),
   126  		opts.ProviderExtIP,
   127  	)
   128  	rules = append(rules, rule)
   129  
   130  	return rules, nil
   131  }
   132  
   133  // Enable enables NAT service.
   134  func (service *servicePFCtl) Enable() error {
   135  	err := service.ipForward.Enable()
   136  	if err != nil {
   137  		log.Warn().Err(err).Msg("Failed to enable IP forwarding")
   138  	}
   139  	return err
   140  }
   141  
   142  // Disable disables NAT service and deletes all rules.
   143  func (service *servicePFCtl) Disable() error {
   144  	service.mu.Lock()
   145  	defer service.mu.Unlock()
   146  
   147  	service.ipForward.Disable()
   148  	service.rules = nil
   149  	service.disableRules()
   150  	return nil
   151  }
   152  
   153  func ifaceByAddress(ip net.IP) (string, error) {
   154  	ifaces, err := net.Interfaces()
   155  	if err != nil {
   156  		return "", err
   157  	}
   158  
   159  	for _, ifi := range ifaces {
   160  		addresses, err := ifi.Addrs()
   161  		if err != nil {
   162  			return "", err
   163  		}
   164  		for _, address := range addresses {
   165  			if address.(*net.IPNet).Contains(ip) {
   166  				return ifi.Name, nil
   167  			}
   168  		}
   169  	}
   170  	return "", errors.New("not able to determine outbound ethernet interface for IP: " + ip.String())
   171  }
   172  
   173  func (service *servicePFCtl) pfctlExec(rules []string) error {
   174  	natRule := strings.Join(rules, "\n")
   175  	arguments := fmt.Sprintf(`echo "%v" | /sbin/pfctl -vEf -`, natRule)
   176  
   177  	if output, err := cmdutil.ExecOutput("sh", "-c", arguments); err != nil {
   178  		if !strings.Contains(output, natRule) {
   179  			log.Warn().Err(err).Msgf("Failed to create pfctl rule")
   180  			return err
   181  		}
   182  	}
   183  	log.Info().Msg("NAT rules applied")
   184  	return nil
   185  }
   186  
   187  func (service *servicePFCtl) disableRules() {
   188  	_, err := cmdutil.ExecOutput("/sbin/pfctl", "-F", "nat")
   189  	if err != nil {
   190  		log.Warn().Err(err).Msgf("Failed cleanup NAT rules (pfctl)")
   191  	} else {
   192  		log.Info().Msg("NAT rules cleared")
   193  	}
   194  }
   195  
   196  func untypedPfctlRules(rules []string) []interface{} {
   197  	res := make([]interface{}, len(rules))
   198  	for i := range rules {
   199  		res[i] = rules[i]
   200  	}
   201  	return res
   202  }
   203  
   204  func typedPfctlRules(rules []interface{}) []string {
   205  	res := make([]string, len(rules))
   206  	for i := range rules {
   207  		res[i] = rules[i].(string)
   208  	}
   209  	return res
   210  }