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

     1  //go:build !android
     2  
     3  /*
     4   * Copyright (C) 2021 The "MysteriumNetwork/node" Authors.
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License as published by
     8   * the Free Software Foundation, either version 3 of the License, or
     9   * (at your option) any later version.
    10   *
    11   * This program is distributed in the hope that it will be useful,
    12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14   * GNU General Public License for more details.
    15   *
    16   * You should have received a copy of the GNU General Public License
    17   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18   */
    19  
    20  package router
    21  
    22  import (
    23  	"fmt"
    24  	"net"
    25  	"sync"
    26  	"time"
    27  
    28  	"github.com/rs/zerolog/log"
    29  
    30  	"github.com/mysteriumnetwork/node/config"
    31  	"github.com/mysteriumnetwork/node/router/network"
    32  )
    33  
    34  type manager struct {
    35  	mu   sync.Mutex
    36  	once sync.Once
    37  
    38  	rules     []rule
    39  	currentGW net.IP
    40  
    41  	routingTable router
    42  
    43  	gwCheckInterval time.Duration
    44  
    45  	onceStop sync.Once
    46  	stop     chan struct{}
    47  }
    48  
    49  type router interface {
    50  	DiscoverGateway() (net.IP, error)
    51  	ExcludeRule(ip, gw net.IP) error
    52  	DeleteRule(ip, gw net.IP) error
    53  }
    54  
    55  type rule struct {
    56  	ip    net.IP
    57  	usage int
    58  }
    59  
    60  // NewManager creates a new instance of service that maintain routing table to match current state.
    61  func NewManager() *manager {
    62  	var r router = &network.RoutingTable{}
    63  
    64  	if config.GetBool(config.FlagUserMode) || config.GetBool(config.FlagUserspace) {
    65  		r = &network.RoutingTableRemote{}
    66  	}
    67  
    68  	return &manager{
    69  		stop: make(chan struct{}),
    70  
    71  		gwCheckInterval: 5 * time.Second,
    72  		routingTable:    r,
    73  	}
    74  }
    75  
    76  func (m *manager) ExcludeIP(ip net.IP) error {
    77  	m.ensureStarted()
    78  	m.mu.Lock()
    79  	defer m.mu.Unlock()
    80  
    81  	new := true
    82  
    83  	for i, rule := range m.rules {
    84  		if !rule.ip.Equal(ip) {
    85  			continue
    86  		}
    87  
    88  		new = false
    89  		m.rules[i].usage++
    90  
    91  		break
    92  	}
    93  
    94  	if !new {
    95  		return nil
    96  	}
    97  
    98  	if err := m.routingTable.ExcludeRule(ip, m.currentGW); err != nil {
    99  		return fmt.Errorf("failed to exclude rule: %w", err)
   100  	}
   101  
   102  	m.rules = append(m.rules, rule{
   103  		ip:    ip,
   104  		usage: 1,
   105  	})
   106  
   107  	return nil
   108  }
   109  
   110  func (m *manager) RemoveExcludedIP(ip net.IP) error {
   111  	m.mu.Lock()
   112  	defer m.mu.Unlock()
   113  
   114  	for i, rule := range m.rules {
   115  		if !rule.ip.Equal(ip) {
   116  			continue
   117  		}
   118  
   119  		m.rules[i].usage--
   120  
   121  		if m.rules[i].usage == 0 {
   122  			m.rules = append(m.rules[:i], m.rules[i+1:]...)
   123  
   124  			if err := m.routingTable.DeleteRule(ip, m.currentGW); err != nil {
   125  				return fmt.Errorf("failed to remove excluded rule: %w", err)
   126  			}
   127  		}
   128  
   129  		break
   130  	}
   131  
   132  	return nil
   133  }
   134  
   135  func (m *manager) ensureStarted() {
   136  	m.once.Do(func() {
   137  		m.forceCheckGW()
   138  
   139  		go m.start()
   140  	})
   141  }
   142  
   143  func (m *manager) start() {
   144  	for {
   145  		select {
   146  		case <-time.After(m.gwCheckInterval):
   147  			m.checkGW()
   148  		case <-m.stop:
   149  			return
   150  		}
   151  	}
   152  }
   153  
   154  func (m *manager) Stop() {
   155  	if err := m.Clean(); err != nil {
   156  		log.Error().Err(err).Msg("Failed to clean routing rules")
   157  	}
   158  
   159  	m.onceStop.Do(func() {
   160  		close(m.stop)
   161  	})
   162  }
   163  
   164  func (m *manager) Clean() (lastErr error) {
   165  	m.mu.Lock()
   166  	defer m.mu.Unlock()
   167  
   168  	if err := m.clean(); err != nil {
   169  		return fmt.Errorf("failed to clean routes: %w", err)
   170  	}
   171  
   172  	m.rules = nil
   173  
   174  	return nil
   175  }
   176  
   177  func (m *manager) clean() (lastErr error) {
   178  	for _, rule := range m.rules {
   179  		err := m.routingTable.DeleteRule(rule.ip, m.currentGW)
   180  		if err != nil {
   181  			lastErr = err
   182  			log.Error().Err(err).Msgf("Failed to delete route: %+v", rule)
   183  		}
   184  	}
   185  
   186  	return lastErr
   187  }
   188  
   189  func (m *manager) apply(gw net.IP) (lastErr error) {
   190  	for _, rule := range m.rules {
   191  		err := m.routingTable.ExcludeRule(rule.ip, gw)
   192  		if err != nil {
   193  			lastErr = err
   194  			log.Error().Err(err).Msgf("Failed to delete route: %+v", rule)
   195  		}
   196  	}
   197  
   198  	m.currentGW = gw
   199  
   200  	return lastErr
   201  }
   202  
   203  func (m *manager) forceCheckGW() {
   204  	var currentGW net.IP
   205  
   206  	for currentGW == nil {
   207  		m.checkGW()
   208  
   209  		m.mu.Lock()
   210  		currentGW = m.currentGW
   211  		m.mu.Unlock()
   212  	}
   213  }
   214  
   215  func (m *manager) checkGW() {
   216  	gw, err := m.routingTable.DiscoverGateway()
   217  	if err != nil {
   218  		log.Error().Err(err).Msg("Failed to detect system default gateway, keeping old value")
   219  		return
   220  	}
   221  
   222  	if !m.currentGW.Equal(gw) && !gw.Equal(net.IPv4zero) {
   223  		m.mu.Lock()
   224  		defer m.mu.Unlock()
   225  
   226  		log.Info().Msgf("Default gateway changed to %s, reconfiguring routes.", gw)
   227  
   228  		if err := m.clean(); err != nil {
   229  			log.Error().Err(err).Msg("Failed to clean routing rules")
   230  		}
   231  
   232  		if err := m.apply(gw); err != nil {
   233  			log.Error().Err(err).Msg("Failed to apply new routing rules")
   234  		}
   235  	}
   236  }