...
1
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package portforwardtester
31
32 import (
33 "fmt"
34 "net"
35 "os"
36 "strconv"
37 "strings"
38 "time"
39
40 "github.com/spf13/cobra"
41 )
42
43 func getEnvInt(name string) int {
44 s := os.Getenv(name)
45 value, err := strconv.Atoi(s)
46 if err != nil {
47 fmt.Printf("Error parsing %s %q: %v\n", name, s, err)
48 os.Exit(1)
49 }
50 return value
51 }
52
53
54
55
56
57
58
59
60
61
62 const rstAvoidanceDelay = 500 * time.Millisecond
63
64
65 var CmdPortForwardTester = &cobra.Command{
66 Use: "port-forward-tester",
67 Short: "Creates a TCP server that sends chunks of data",
68 Long: `Listens for TCP connections on a given address and port, optionally checks the data received,
69 and sends a configurable number of data chunks, with a configurable interval between chunks.
70
71 The subcommand is using the following environment variables:
72
73 - BIND_ADDRESS (optional): The address on which it will start listening for TCP connections (default value: localhost)
74 - BIND_PORT: The port on which it will start listening for TCP connections.
75 - EXPECTED_CLIENT_DATA (optional): If set, it will check that the request sends the same exact data.
76 - CHUNKS: How many chunks of data to write in the response.
77 - CHUNK_SIZE: The expected size of each written chunk of data. If it does not match the actual size of the written data, it will exit with the exit code 4.
78 - CHUNK_INTERVAL: The amount of time to wait in between chunks.`,
79 Args: cobra.MaximumNArgs(0),
80 Run: main,
81 }
82
83 func main(cmd *cobra.Command, args []string) {
84 bindAddress := os.Getenv("BIND_ADDRESS")
85 if bindAddress == "" {
86 bindAddress = "localhost"
87 }
88 bindPort := os.Getenv("BIND_PORT")
89 addr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(bindAddress, bindPort))
90 if err != nil {
91 fmt.Printf("Error resolving: %v\n", err)
92 os.Exit(1)
93 }
94 listener, err := net.ListenTCP("tcp", addr)
95 if err != nil {
96 fmt.Printf("Error listening: %v\n", err)
97 os.Exit(1)
98 }
99
100 conn, err := listener.AcceptTCP()
101 if err != nil {
102 fmt.Printf("Error accepting connection: %v\n", err)
103 os.Exit(1)
104 }
105
106 fmt.Println("Accepted client connection")
107
108 expectedClientData := os.Getenv("EXPECTED_CLIENT_DATA")
109 if len(expectedClientData) > 0 {
110 buf := make([]byte, len(expectedClientData))
111 read, err := conn.Read(buf)
112 if read != len(expectedClientData) {
113 fmt.Printf("Expected to read %d bytes from client, but got %d instead. err=%v\n", len(expectedClientData), read, err)
114 os.Exit(2)
115 }
116 if expectedClientData != string(buf) {
117 fmt.Printf("Expect to read %q, but got %q. err=%v\n", expectedClientData, string(buf), err)
118 os.Exit(3)
119 }
120 if err != nil {
121 fmt.Printf("Read err: %v\n", err)
122 }
123 fmt.Println("Received expected client data")
124 }
125
126 chunks := getEnvInt("CHUNKS")
127 chunkSize := getEnvInt("CHUNK_SIZE")
128 chunkInterval := getEnvInt("CHUNK_INTERVAL")
129
130 stringData := strings.Repeat("x", chunkSize)
131 data := []byte(stringData)
132
133 for i := 0; i < chunks; i++ {
134 written, err := conn.Write(data)
135 if written != chunkSize {
136 fmt.Printf("Expected to write %d bytes from client, but wrote %d instead. err=%v\n", chunkSize, written, err)
137 os.Exit(4)
138 }
139 if err != nil {
140 fmt.Printf("Write err: %v\n", err)
141 }
142 if i+1 < chunks {
143 time.Sleep(time.Duration(chunkInterval) * time.Millisecond)
144 }
145 }
146
147 fmt.Println("Shutting down connection")
148
149
150
151
152 conn.SetLinger(-1)
153
154
155
156
157
158 conn.CloseWrite()
159 time.Sleep(rstAvoidanceDelay)
160 conn.Close()
161
162 fmt.Println("Done")
163 }
164
View as plain text