istio
211 строк · 5.5 Кб
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
15package validation16
17import (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
31type ReturnCode int32
33const (34DONE ReturnCode = iota35)
36
37type Validator struct {38Config *Config39}
40
41type Config struct {42ServerListenAddress []string43ServerOriginalPort uint1644ServerOriginalIP netip.Addr45ServerReadyBarrier chan ReturnCode46ProbeTimeout time.Duration47}
48
49type Service struct {50Config *Config51}
52
53type Client struct {54Config *Config55}
56
57func (validator *Validator) Run() error {58log.Infof("Starting iptables validation. This check verifies that iptables rules are properly established for the network.")59s := Service{60validator.Config,61}62sError := make(chan error, 1)63sTimer := time.NewTimer(s.Config.ProbeTimeout)64defer sTimer.Stop()65go func() {66sError <- s.Run()67}()68
69// infinite loop70go func() {71c := Client{Config: validator.Config}72<-c.Config.ServerReadyBarrier73for {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.78time.Sleep(time.Second)79}80}()81select {82case <-sTimer.C:83return fmt.Errorf("validation timeout")84case err := <-sError:85if err == nil {86log.Info("Validation passed, iptables rules established")87} else {88log.Errorf("Validation failed: %v", err)89}90return err91}92}
93
94// TODO(lambdai): remove this if iptables only need to redirect to outbound proxy port on A call A
95func genListenerAddress(ip netip.Addr, ports []string) []string {96addresses := make([]string, 0, len(ports))97for _, port := range ports {98addresses = append(addresses, net.JoinHostPort(ip.String(), port))99}100return addresses101}
102
103func NewValidator(config *config.Config) *Validator {104// It's tricky here:105// Connect to 127.0.0.6 will redirect to 127.0.0.1106// Connect to ::6 will redirect to ::1107isIPv6 := config.HostIP.Is6()108listenIP, _ := netip.AddrFromSlice([]byte{127, 0, 0, 1})109serverIP, _ := netip.AddrFromSlice([]byte{127, 0, 0, 6})110if isIPv6 {111listenIP, _ = netip.AddrFromSlice([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})112serverIP, _ = netip.AddrFromSlice([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6})113}114return &Validator{115Config: &Config{116ServerListenAddress: genListenerAddress(listenIP, []string{config.ProxyPort, config.InboundCapturePort}),117ServerOriginalPort: config.IptablesProbePort,118ServerOriginalIP: serverIP,119ServerReadyBarrier: make(chan ReturnCode, 1),120ProbeTimeout: config.ProbeTimeout,121},122}123}
124
125// Write human readable response
126func echo(conn io.WriteCloser, echo []byte) {127_, _ = conn.Write(echo)128_ = conn.Close()129}
130
131func restoreOriginalAddress(l net.Listener, config *Config, c chan<- ReturnCode) {132defer l.Close()133for {134conn, err := l.Accept()135if err != nil {136log.Errorf("Listener failed to accept connection: %v", err)137continue138}139_, port, err := GetOriginalDestination(conn)140if err != nil {141log.Errorf("Error getting original dst: %v", err)142conn.Close()143continue144}145
146// echo original port for debugging.147// Since the write amount is small it should fit in sock buffer and never blocks.148echo(conn, []byte(strconv.Itoa(int(port))))149// Handle connections150// Since the write amount is small it should fit in sock buffer and never blocks.151if port != config.ServerOriginalPort {152// This could be probe request from no where153continue154}155// Server recovers the magical original port156c <- DONE157return158}159}
160
161func (s *Service) Run() error {162// at most 2 message: ipv4 and ipv6163c := make(chan ReturnCode, 2)164hasAtLeastOneListener := false165for _, addr := range s.Config.ServerListenAddress {166log.Infof("Listening on %v", addr)167config := &net.ListenConfig{Control: reuseAddr}168
169l, err := config.Listen(context.Background(), "tcp", addr) // bind to the address:port170if err != nil {171log.Errorf("Error on listening: %v", err)172continue173}174
175hasAtLeastOneListener = true176go restoreOriginalAddress(l, s.Config, c)177}178if hasAtLeastOneListener {179s.Config.ServerReadyBarrier <- DONE180// bump at least one since we currently support either v4 or v6181<-c182return nil183}184return fmt.Errorf("no listener available: %s", strings.Join(s.Config.ServerListenAddress, ","))185}
186
187func (c *Client) Run() error {188laddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0")189if err != nil {190return err191}192if c.Config.ServerOriginalIP.Is6() {193laddr, err = net.ResolveTCPAddr("tcp", "[::1]:0")194if err != nil {195return err196}197}198sOriginalPort := fmt.Sprintf("%d", c.Config.ServerOriginalPort)199serverOriginalAddress := net.JoinHostPort(c.Config.ServerOriginalIP.String(), sOriginalPort)200raddr, err := net.ResolveTCPAddr("tcp", serverOriginalAddress)201if err != nil {202return err203}204conn, err := net.DialTCP("tcp", laddr, raddr)205if err != nil {206log.Errorf("Error connecting to %s: %v", serverOriginalAddress, err)207return err208}209conn.Close()210return nil211}
212