k8s.io/kubernetes@v1.29.3/test/images/agnhost/net/nat/closewait.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package nat
    18  
    19  /*
    20  client/server for testing CLOSE_WAIT timeout condition in iptables NAT.
    21  
    22  client              server
    23    |                   |
    24    |<--tcp handshake-->|
    25    |<-------fin--------| half-close from server
    26    |                   | client is in CLOSE_WAIT
    27  */
    28  
    29  import (
    30  	"errors"
    31  	"io"
    32  	"log"
    33  	"net"
    34  	"time"
    35  
    36  	"k8s.io/kubernetes/test/images/agnhost/net/common"
    37  )
    38  
    39  // leakedConnection is a global variable that should leak the active
    40  // connection assigned here.
    41  //
    42  //nolint:unused // U1000 intentional unused variable
    43  var leakedConnection *net.TCPConn
    44  
    45  // CloseWaitServerOptions holds server JSON options.
    46  type CloseWaitServerOptions struct {
    47  	// Address to bind for the test
    48  	LocalAddr string
    49  	// Timeout to wait after sending the FIN.
    50  	PostFinTimeoutSeconds int
    51  }
    52  
    53  type closeWaitServer struct {
    54  	options *CloseWaitServerOptions
    55  }
    56  
    57  // NewCloseWaitServer returns a new Runner.
    58  func NewCloseWaitServer() common.Runner {
    59  	return &closeWaitServer{}
    60  }
    61  
    62  // NewOptions allocates new options structure.
    63  func (server *closeWaitServer) NewOptions() interface{} {
    64  	return &CloseWaitServerOptions{}
    65  }
    66  
    67  // Run the server-side of the test.
    68  func (server *closeWaitServer) Run(logger *log.Logger, rawOptions interface{}) error {
    69  	if options, ok := rawOptions.(*CloseWaitServerOptions); ok {
    70  		server.options = options
    71  	} else {
    72  		return errors.New("invalid type")
    73  	}
    74  
    75  	logger.Printf("Run %v", server.options)
    76  
    77  	addr, err := net.ResolveTCPAddr("tcp", server.options.LocalAddr)
    78  	if err != nil {
    79  		return err
    80  	}
    81  
    82  	listener, err := net.ListenTCP("tcp", addr)
    83  	if err != nil {
    84  		return err
    85  	}
    86  	defer listener.Close()
    87  
    88  	logger.Printf("Server listening on %v", addr)
    89  
    90  	conn, err := listener.AcceptTCP()
    91  	if err != nil {
    92  		return err
    93  	}
    94  	defer conn.Close()
    95  
    96  	logger.Printf("Client connected")
    97  
    98  	// Send client half-close FIN so client is now in CLOSE_WAIT. We keep
    99  	// the client -> server pipe open to verify whether or not the NAT
   100  	// dropped our connection.
   101  	if err := conn.CloseWrite(); err != nil {
   102  		return err
   103  	}
   104  
   105  	logger.Printf("Server sent FIN, waiting %v seconds",
   106  		server.options.PostFinTimeoutSeconds)
   107  
   108  	<-time.After(time.Duration(server.options.PostFinTimeoutSeconds) * time.Second)
   109  
   110  	logger.Printf("Done")
   111  
   112  	return nil
   113  }
   114  
   115  // CloseWaitClientOptions holds client JSON options.
   116  type CloseWaitClientOptions struct {
   117  	// RemoteAddr of the server to connect to.
   118  	RemoteAddr string
   119  	// TimeoutSeconds on I/O with the server.
   120  	TimeoutSeconds int
   121  	// Half-close timeout (to give the test time to check the status of the
   122  	// conntrack table entry.
   123  	PostFinTimeoutSeconds int
   124  	// Leak connection (assign to global variable so connection persists
   125  	// as long as the process remains.
   126  	LeakConnection bool
   127  }
   128  
   129  type closeWaitClient struct {
   130  	options *CloseWaitClientOptions
   131  }
   132  
   133  // NewCloseWaitClient creates a new runner
   134  func NewCloseWaitClient() common.Runner {
   135  	return &closeWaitClient{}
   136  }
   137  
   138  // NewOptions allocates new options structure.
   139  func (client *closeWaitClient) NewOptions() interface{} {
   140  	return &CloseWaitClientOptions{}
   141  }
   142  
   143  // Run the client.m
   144  func (client *closeWaitClient) Run(logger *log.Logger, rawOptions interface{}) error {
   145  	if options, ok := rawOptions.(*CloseWaitClientOptions); ok {
   146  		client.options = options
   147  	} else {
   148  		return errors.New("invalid type")
   149  	}
   150  
   151  	logger.Printf("Run %v", client.options)
   152  
   153  	addr, err := net.ResolveTCPAddr("tcp", client.options.RemoteAddr)
   154  	if err != nil {
   155  		return err
   156  	}
   157  
   158  	conn, err := net.DialTCP("tcp", nil, addr)
   159  	if err != nil {
   160  		return err
   161  	}
   162  	if !client.options.LeakConnection {
   163  		defer conn.Close()
   164  	}
   165  
   166  	logger.Printf("Connected to server")
   167  
   168  	if client.options.TimeoutSeconds > 0 {
   169  		delay := time.Duration(client.options.TimeoutSeconds) * time.Second
   170  		conn.SetReadDeadline(time.Now().Add(delay))
   171  	}
   172  
   173  	buf := make([]byte, 1, 1)
   174  	size, err := conn.Read(buf)
   175  
   176  	if err != nil && err != io.EOF {
   177  		return err
   178  	}
   179  
   180  	if size != 0 {
   181  		return errors.New("Got data but expected EOF")
   182  	}
   183  
   184  	logger.Printf("Server has half-closed the connection, waiting %v seconds",
   185  		client.options.PostFinTimeoutSeconds)
   186  
   187  	if client.options.LeakConnection {
   188  		logger.Printf("Leaking client connection (assigning to global variable)")
   189  		leakedConnection = conn
   190  	}
   191  
   192  	<-time.After(
   193  		time.Duration(client.options.PostFinTimeoutSeconds) * time.Second)
   194  
   195  	logger.Printf("Done")
   196  
   197  	return nil
   198  }