istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tools/istio-iptables/pkg/validation/validator.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package validation
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io"
    21  	"net"
    22  	"net/netip"
    23  	"strconv"
    24  	"strings"
    25  	"time"
    26  
    27  	"istio.io/istio/pkg/log"
    28  	"istio.io/istio/tools/istio-iptables/pkg/config"
    29  )
    30  
    31  type ReturnCode int
    32  
    33  const (
    34  	DONE ReturnCode = iota
    35  )
    36  
    37  type Validator struct {
    38  	Config *Config
    39  }
    40  
    41  type Config struct {
    42  	ServerListenAddress []string
    43  	ServerOriginalPort  uint16
    44  	ServerOriginalIP    netip.Addr
    45  	ServerReadyBarrier  chan ReturnCode
    46  	ProbeTimeout        time.Duration
    47  }
    48  
    49  type Service struct {
    50  	Config *Config
    51  }
    52  
    53  type Client struct {
    54  	Config *Config
    55  }
    56  
    57  func (validator *Validator) Run() error {
    58  	log.Infof("Starting iptables validation. This check verifies that iptables rules are properly established for the network.")
    59  	s := Service{
    60  		validator.Config,
    61  	}
    62  	sError := make(chan error, 1)
    63  	sTimer := time.NewTimer(s.Config.ProbeTimeout)
    64  	defer sTimer.Stop()
    65  	go func() {
    66  		sError <- s.Run()
    67  	}()
    68  
    69  	// infinite loop
    70  	go func() {
    71  		c := Client{Config: validator.Config}
    72  		<-c.Config.ServerReadyBarrier
    73  		for {
    74  			_ = c.Run()
    75  			// Avoid spamming the request to the validation server.
    76  			// Since the TIMEWAIT socket is cleaned up in 60 second,
    77  			// it's maintaining 60 TIMEWAIT sockets. Not big deal.
    78  			time.Sleep(time.Second)
    79  		}
    80  	}()
    81  	select {
    82  	case <-sTimer.C:
    83  		return fmt.Errorf("validation timeout")
    84  	case err := <-sError:
    85  		if err == nil {
    86  			log.Info("Validation passed, iptables rules established")
    87  		} else {
    88  			log.Errorf("Validation failed: %v", err)
    89  		}
    90  		return err
    91  	}
    92  }
    93  
    94  // TODO(lambdai): remove this if iptables only need to redirect to outbound proxy port on A call A
    95  func genListenerAddress(ip netip.Addr, ports []string) []string {
    96  	addresses := make([]string, 0, len(ports))
    97  	for _, port := range ports {
    98  		addresses = append(addresses, net.JoinHostPort(ip.String(), port))
    99  	}
   100  	return addresses
   101  }
   102  
   103  func NewValidator(config *config.Config) *Validator {
   104  	// It's tricky here:
   105  	// Connect to 127.0.0.6 will redirect to 127.0.0.1
   106  	// Connect to ::6       will redirect to ::1
   107  	isIPv6 := config.HostIP.Is6()
   108  	listenIP, _ := netip.AddrFromSlice([]byte{127, 0, 0, 1})
   109  	serverIP, _ := netip.AddrFromSlice([]byte{127, 0, 0, 6})
   110  	if isIPv6 {
   111  		listenIP, _ = netip.AddrFromSlice([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
   112  		serverIP, _ = netip.AddrFromSlice([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6})
   113  	}
   114  	return &Validator{
   115  		Config: &Config{
   116  			ServerListenAddress: genListenerAddress(listenIP, []string{config.ProxyPort, config.InboundCapturePort}),
   117  			ServerOriginalPort:  config.IptablesProbePort,
   118  			ServerOriginalIP:    serverIP,
   119  			ServerReadyBarrier:  make(chan ReturnCode, 1),
   120  			ProbeTimeout:        config.ProbeTimeout,
   121  		},
   122  	}
   123  }
   124  
   125  // Write human readable response
   126  func echo(conn io.WriteCloser, echo []byte) {
   127  	_, _ = conn.Write(echo)
   128  	_ = conn.Close()
   129  }
   130  
   131  func restoreOriginalAddress(l net.Listener, config *Config, c chan<- ReturnCode) {
   132  	defer l.Close()
   133  	for {
   134  		conn, err := l.Accept()
   135  		if err != nil {
   136  			log.Errorf("Listener failed to accept connection: %v", err)
   137  			continue
   138  		}
   139  		_, port, err := GetOriginalDestination(conn)
   140  		if err != nil {
   141  			log.Errorf("Error getting original dst: %v", err)
   142  			conn.Close()
   143  			continue
   144  		}
   145  
   146  		// echo original port for debugging.
   147  		// Since the write amount is small it should fit in sock buffer and never blocks.
   148  		echo(conn, []byte(strconv.Itoa(int(port))))
   149  		// Handle connections
   150  		// Since the write amount is small it should fit in sock buffer and never blocks.
   151  		if port != config.ServerOriginalPort {
   152  			// This could be probe request from no where
   153  			continue
   154  		}
   155  		// Server recovers the magical original port
   156  		c <- DONE
   157  		return
   158  	}
   159  }
   160  
   161  func (s *Service) Run() error {
   162  	// at most 2 message: ipv4 and ipv6
   163  	c := make(chan ReturnCode, 2)
   164  	hasAtLeastOneListener := false
   165  	for _, addr := range s.Config.ServerListenAddress {
   166  		log.Infof("Listening on %v", addr)
   167  		config := &net.ListenConfig{Control: reuseAddr}
   168  
   169  		l, err := config.Listen(context.Background(), "tcp", addr) // bind to the address:port
   170  		if err != nil {
   171  			log.Errorf("Error on listening: %v", err)
   172  			continue
   173  		}
   174  
   175  		hasAtLeastOneListener = true
   176  		go restoreOriginalAddress(l, s.Config, c)
   177  	}
   178  	if hasAtLeastOneListener {
   179  		s.Config.ServerReadyBarrier <- DONE
   180  		// bump at least one since we currently support either v4 or v6
   181  		<-c
   182  		return nil
   183  	}
   184  	return fmt.Errorf("no listener available: %s", strings.Join(s.Config.ServerListenAddress, ","))
   185  }
   186  
   187  func (c *Client) Run() error {
   188  	laddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0")
   189  	if err != nil {
   190  		return err
   191  	}
   192  	if c.Config.ServerOriginalIP.Is6() {
   193  		laddr, err = net.ResolveTCPAddr("tcp", "[::1]:0")
   194  		if err != nil {
   195  			return err
   196  		}
   197  	}
   198  	sOriginalPort := fmt.Sprintf("%d", c.Config.ServerOriginalPort)
   199  	serverOriginalAddress := net.JoinHostPort(c.Config.ServerOriginalIP.String(), sOriginalPort)
   200  	raddr, err := net.ResolveTCPAddr("tcp", serverOriginalAddress)
   201  	if err != nil {
   202  		return err
   203  	}
   204  	conn, err := net.DialTCP("tcp", laddr, raddr)
   205  	if err != nil {
   206  		log.Errorf("Error connecting to %s: %v", serverOriginalAddress, err)
   207  		return err
   208  	}
   209  	conn.Close()
   210  	return nil
   211  }