1
16
17 package netexec
18
19 import (
20 "context"
21 "encoding/json"
22 "fmt"
23 "io"
24 "log"
25 "net"
26 "net/http"
27 "net/url"
28 "os"
29 "os/exec"
30 "os/signal"
31 "strconv"
32 "strings"
33 "sync/atomic"
34 "syscall"
35 "time"
36
37 "github.com/ishidawataru/sctp"
38 "github.com/spf13/cobra"
39
40 utilnet "k8s.io/apimachinery/pkg/util/net"
41 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
42 "k8s.io/apimachinery/pkg/util/sets"
43 netutils "k8s.io/utils/net"
44 )
45
46 var (
47 httpPort = 8080
48 udpPort = 8081
49 sctpPort = -1
50 shellPath = "/bin/sh"
51 serverReady = &atomicBool{0}
52 certFile = ""
53 privKeyFile = ""
54 httpOverride = ""
55 udpListenAddresses = ""
56 delayShutdown = 0
57 )
58
59 const bindToAny = ""
60
61
62 var CmdNetexec = &cobra.Command{
63 Use: "netexec",
64 Short: "Creates HTTP(S), UDP, and (optionally) SCTP servers with various endpoints",
65 Long: `Starts a HTTP(S) server on given port with the following endpoints:
66
67 - /: Returns the request's timestamp.
68 - /clientip: Returns the request's IP address.
69 - /header: Returns the request's header value corresponding to the key provided or the entire
70 header marshalled as json, if no form value (key) is provided.
71 ("/header?key=X-Forwarded-For" or /header)
72 - /dial: Creates a given number of requests to the given host and port using the given protocol,
73 and returns a JSON with the fields "responses" (successful request responses) and "errors" (
74 failed request responses). Returns "200 OK" status code if the last request succeeded,
75 "417 Expectation Failed" if it did not, or "400 Bad Request" if any of the endpoint's parameters
76 is invalid. The endpoint's parameters are:
77 - "host": The host that will be dialed.
78 - "port": The port that will be dialed.
79 - "request": The HTTP endpoint or data to be sent through UDP. If not specified, it will result
80 in a "400 Bad Request" status code being returned.
81 - "protocol": The protocol which will be used when making the request. Default value: "http".
82 Acceptable values: "http", "udp", "sctp".
83 - "tries": The number of times the request will be performed. Default value: "1".
84 - "/echo": Returns the given "msg" ("/echo?msg=echoed_msg"), with the optional status "code".
85 - "/exit": Closes the server with the given code and graceful shutdown. The endpoint's parameters
86 are:
87 - "code": The exit code for the process. Default value: 0. Allows an integer [0-127].
88 - "timeout": The amount of time to wait for connections to close before shutting down.
89 Acceptable values are golang durations. If 0 the process will exit immediately without
90 shutdown.
91 - "wait": The amount of time to wait before starting shutdown. Acceptable values are
92 golang durations. If 0 the process will start shutdown immediately.
93 - "/healthz": Returns "200 OK" if the server is healthy, "412 Status Precondition Failed"
94 otherwise. The server is considered not ready if the UDP server did not start yet or
95 it exited.
96 - "/readyz": Returns "200 OK" if the server is ready to receive traffic, "412 Status Precondition Failed", if the
97 server is not yet ready to receive traffic, but may be ready later, and "503" if the server is shutting down.
98 When a sig-term is observed, the /readyz will report 503, but healthz will report 200 to indicate that the
99 server is healthy (don't kill it), but the it should not be sent traffic (remove from endpoints).
100 - "/hostname": Returns the server's hostname.
101 - "/hostName": Returns the server's hostname.
102 - "/redirect": Returns a redirect response to the given "location", with the optional status "code"
103 ("/redirect?location=/echo%3Fmsg=foobar&code=307").
104 - "/shell": Executes the given "shellCommand" or "cmd" ("/shell?cmd=some-command") and
105 returns a JSON containing the fields "output" (command's output) and "error" (command's
106 error message). Returns "200 OK" if the command succeeded, "417 Expectation Failed" if not.
107 - "/shutdown": Closes the server with the exit code 0.
108 - "/upload": Accepts a file to be uploaded, writing it in the "/uploads" folder on the host.
109 Returns a JSON with the fields "output" (containing the file's name on the server) and
110 "error" containing any potential server side errors.
111
112 If "--tls-cert-file" is added (ideally in conjunction with "--tls-private-key-file", the HTTP server
113 will be upgraded to HTTPS. The image has default, "localhost"-based cert/privkey files at
114 "/localhost.crt" and "/localhost.key" (see: "porter" subcommand)
115
116 If "--http-override" is set, the HTTP(S) server will always serve the override path & options,
117 ignoring the request URL.
118
119 It will also start a UDP server on the indicated UDP port and addresses that responds to the following commands:
120
121 - "hostname": Returns the server's hostname
122 - "echo <msg>": Returns the given <msg>
123 - "clientip": Returns the request's IP address
124
125 The UDP server can be disabled by setting --udp-port to -1.
126
127 Additionally, if (and only if) --sctp-port is passed, it will start an SCTP server on that port,
128 responding to the same commands as the UDP server.
129 `,
130 Args: cobra.MaximumNArgs(0),
131 Run: main,
132 }
133
134 func init() {
135 CmdNetexec.Flags().IntVar(&httpPort, "http-port", 8080, "HTTP Listen Port")
136 CmdNetexec.Flags().StringVar(&certFile, "tls-cert-file", "",
137 "File containing an x509 certificate for HTTPS. (CA cert, if any, concatenated after server cert)")
138 CmdNetexec.Flags().StringVar(&privKeyFile, "tls-private-key-file", "",
139 "File containing an x509 private key matching --tls-cert-file")
140 CmdNetexec.Flags().IntVar(&udpPort, "udp-port", 8081, "UDP Listen Port")
141 CmdNetexec.Flags().IntVar(&sctpPort, "sctp-port", -1, "SCTP Listen Port")
142 CmdNetexec.Flags().StringVar(&httpOverride, "http-override", "", "Override the HTTP handler to always respond as if it were a GET with this path & params")
143 CmdNetexec.Flags().StringVar(&udpListenAddresses, "udp-listen-addresses", "", "A comma separated list of ip addresses the udp servers listen from")
144 CmdNetexec.Flags().IntVar(&delayShutdown, "delay-shutdown", 0, "Number of seconds to delay shutdown when receiving SIGTERM.")
145 }
146
147
148 type atomicBool struct {
149 v int32
150 }
151
152
153 func (a *atomicBool) set(value bool) {
154 if value {
155 atomic.StoreInt32(&a.v, 1)
156 return
157 }
158 atomic.StoreInt32(&a.v, 0)
159 }
160
161
162 func (a *atomicBool) get() bool {
163 return atomic.LoadInt32(&a.v) == 1
164 }
165
166 func main(cmd *cobra.Command, args []string) {
167 exitCh := make(chan shutdownRequest)
168
169 sigTermReceived := make(chan struct{})
170 go func() {
171 termCh := make(chan os.Signal, 1)
172 signal.Notify(termCh, syscall.SIGTERM)
173
174 <-termCh
175 close(sigTermReceived)
176 }()
177
178 go func() {
179 <-sigTermReceived
180 if delayShutdown > 0 {
181 log.Printf("Sleeping %d seconds before terminating...", delayShutdown)
182 time.Sleep(time.Duration(delayShutdown) * time.Second)
183 }
184 os.Exit(0)
185 }()
186
187 if httpOverride != "" {
188 mux := http.NewServeMux()
189 addRoutes(mux, sigTermReceived, exitCh)
190
191 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
192 overrideReq, err := http.NewRequestWithContext(r.Context(), "GET", httpOverride, nil)
193 if err != nil {
194 http.Error(w, fmt.Sprintf("override request failed: %v", err), http.StatusInternalServerError)
195 return
196 }
197 mux.ServeHTTP(w, overrideReq)
198 })
199 } else {
200 addRoutes(http.DefaultServeMux, sigTermReceived, exitCh)
201 }
202
203
204 if udpPort != -1 {
205 udpBindTo, err := parseAddresses(udpListenAddresses)
206 if err != nil {
207 log.Fatal(err)
208 }
209
210 for _, address := range udpBindTo {
211 go startUDPServer(address, udpPort)
212 }
213 }
214
215
216 if sctpPort != -1 {
217 go startSCTPServer(sctpPort)
218 }
219
220 server := &http.Server{Addr: fmt.Sprintf(":%d", httpPort)}
221 if len(certFile) > 0 {
222 startServer(server, exitCh, func() error { return server.ListenAndServeTLS(certFile, privKeyFile) })
223 } else {
224 startServer(server, exitCh, server.ListenAndServe)
225 }
226 }
227
228 func addRoutes(mux *http.ServeMux, sigTermReceived chan struct{}, exitCh chan shutdownRequest) {
229 mux.HandleFunc("/", rootHandler)
230 mux.HandleFunc("/clientip", clientIPHandler)
231 mux.HandleFunc("/header", headerHandler)
232 mux.HandleFunc("/dial", dialHandler)
233 mux.HandleFunc("/echo", echoHandler)
234 mux.HandleFunc("/exit", func(w http.ResponseWriter, req *http.Request) { exitHandler(w, req, exitCh) })
235 mux.HandleFunc("/healthz", healthzHandler)
236 mux.HandleFunc("/readyz", readyzHandler(sigTermReceived))
237 mux.HandleFunc("/hostname", hostnameHandler)
238 mux.HandleFunc("/redirect", redirectHandler)
239 mux.HandleFunc("/shell", shellHandler)
240 mux.HandleFunc("/upload", uploadHandler)
241
242 mux.HandleFunc("/hostName", hostNameHandler)
243 mux.HandleFunc("/shutdown", shutdownHandler)
244 }
245
246 func startServer(server *http.Server, exitCh chan shutdownRequest, fn func() error) {
247 log.Printf("Started HTTP server on port %d", httpPort)
248 go func() {
249 re := <-exitCh
250 ctx, cancelFn := context.WithTimeout(context.Background(), re.timeout)
251 defer cancelFn()
252 err := server.Shutdown(ctx)
253 log.Printf("Graceful shutdown completed with: %v", err)
254 os.Exit(re.code)
255 }()
256
257 if err := fn(); err != nil {
258 if err == http.ErrServerClosed {
259
260 select {}
261 }
262 log.Fatal(err)
263 }
264 }
265
266 func rootHandler(w http.ResponseWriter, r *http.Request) {
267 log.Printf("GET /")
268 fmt.Fprintf(w, "NOW: %v", time.Now())
269 }
270
271 func echoHandler(w http.ResponseWriter, r *http.Request) {
272 msg := r.FormValue("msg")
273 codeString := r.FormValue("code")
274 log.Printf("GET /echo?msg=%s&code=%s", msg, codeString)
275 if codeString != "" {
276 code, err := strconv.Atoi(codeString)
277 if err != nil && codeString != "" {
278 fmt.Fprintf(w, "argument 'code' must be an integer or empty, got %q\n", codeString)
279 return
280 }
281 w.WriteHeader(code)
282 }
283 fmt.Fprintf(w, "%s", msg)
284 }
285
286 func clientIPHandler(w http.ResponseWriter, r *http.Request) {
287 log.Printf("GET /clientip")
288 fmt.Fprintf(w, r.RemoteAddr)
289 }
290 func headerHandler(w http.ResponseWriter, r *http.Request) {
291 key := r.FormValue("key")
292 if key != "" {
293 log.Printf("GET /header?key=%s", key)
294 fmt.Fprintf(w, "%s", r.Header.Get(key))
295 } else {
296 log.Printf("GET /header")
297 data, err := json.Marshal(r.Header)
298 if err != nil {
299 fmt.Fprintf(w, "error marshalling header, err: %v", err)
300 return
301 }
302 fmt.Fprintf(w, "%s", string(data))
303 }
304 }
305
306 type shutdownRequest struct {
307 code int
308 timeout time.Duration
309 }
310
311 func exitHandler(w http.ResponseWriter, r *http.Request, exitCh chan<- shutdownRequest) {
312 waitString := r.FormValue("wait")
313 timeoutString := r.FormValue("timeout")
314 codeString := r.FormValue("code")
315 log.Printf("GET /exit?code=%s&timeout=%s&wait=%s", codeString, timeoutString, waitString)
316 timeout, err := time.ParseDuration(timeoutString)
317 if err != nil && timeoutString != "" {
318 fmt.Fprintf(w, "argument 'timeout' must be a valid golang duration or empty, got %q\n", timeoutString)
319 return
320 }
321 wait, err := time.ParseDuration(waitString)
322 if err != nil && waitString != "" {
323 fmt.Fprintf(w, "argument 'wait' must be a valid golang duration or empty, got %q\n", waitString)
324 return
325 }
326 code, err := strconv.Atoi(codeString)
327 if err != nil && codeString != "" {
328 fmt.Fprintf(w, "argument 'code' must be an integer [0-127] or empty, got %q\n", codeString)
329 return
330 }
331 log.Printf("Will begin shutdown in %s, allowing %s for connections to close, then will exit with %d", wait, timeout, code)
332 time.Sleep(wait)
333 if timeout == 0 {
334 os.Exit(code)
335 }
336 exitCh <- shutdownRequest{code: code, timeout: timeout}
337 }
338
339 func hostnameHandler(w http.ResponseWriter, r *http.Request) {
340 log.Printf("GET /hostname")
341 fmt.Fprint(w, getHostName())
342 }
343
344
345
346 func healthzHandler(w http.ResponseWriter, r *http.Request) {
347 log.Printf("GET /healthz")
348 if serverReady.get() {
349 w.WriteHeader(200)
350 return
351 }
352 w.WriteHeader(http.StatusPreconditionFailed)
353 }
354
355
356
357 func readyzHandler(sigTermReceived chan struct{}) func(w http.ResponseWriter, r *http.Request) {
358 return func(w http.ResponseWriter, r *http.Request) {
359 log.Printf("GET /readyz")
360
361 select {
362 case <-sigTermReceived:
363 w.WriteHeader(http.StatusServiceUnavailable)
364 if _, err := w.Write([]byte("shutting down")); err != nil {
365 utilruntime.HandleError(err)
366 }
367 return
368
369 default:
370 if serverReady.get() {
371 if _, err := w.Write([]byte("ok")); err != nil {
372 utilruntime.HandleError(err)
373 }
374 return
375 }
376 w.WriteHeader(http.StatusPreconditionFailed)
377 }
378 }
379 }
380
381 func shutdownHandler(w http.ResponseWriter, r *http.Request) {
382 log.Printf("GET /shutdown")
383 os.Exit(0)
384 }
385
386 func dialHandler(w http.ResponseWriter, r *http.Request) {
387 values, err := url.Parse(r.URL.RequestURI())
388 if err != nil {
389 http.Error(w, fmt.Sprintf("%v", err), http.StatusBadRequest)
390 return
391 }
392
393 host := values.Query().Get("host")
394 port := values.Query().Get("port")
395 request := values.Query().Get("request")
396 protocol := values.Query().Get("protocol")
397 tryParam := values.Query().Get("tries")
398 log.Printf("GET /dial?host=%s&protocol=%s&port=%s&request=%s&tries=%s", host, protocol, port, request, tryParam)
399 tries := 1
400 if len(tryParam) > 0 {
401 tries, err = strconv.Atoi(tryParam)
402 }
403 if err != nil {
404 http.Error(w, fmt.Sprintf("tries parameter is invalid. %v", err), http.StatusBadRequest)
405 return
406 }
407 if len(request) == 0 {
408 http.Error(w, fmt.Sprintf("request parameter not specified. %v", err), http.StatusBadRequest)
409 return
410 }
411
412 hostPort := net.JoinHostPort(host, port)
413 var addr net.Addr
414 var dialer func(string, net.Addr) (string, error)
415 switch strings.ToLower(protocol) {
416 case "", "http":
417 dialer = dialHTTP
418 addr, err = net.ResolveTCPAddr("tcp", hostPort)
419 case "udp":
420 dialer = dialUDP
421 addr, err = net.ResolveUDPAddr("udp", hostPort)
422 case "sctp":
423 dialer = dialSCTP
424 addr, err = sctp.ResolveSCTPAddr("sctp", hostPort)
425 default:
426 http.Error(w, fmt.Sprintf("unsupported protocol. %s", protocol), http.StatusBadRequest)
427 return
428 }
429 if err != nil {
430 http.Error(w, fmt.Sprintf("host and/or port param are invalid. %v", err), http.StatusBadRequest)
431 return
432 }
433
434 errors := make([]string, 0)
435 responses := make([]string, 0)
436 var response string
437 for i := 0; i < tries; i++ {
438 response, err = dialer(request, addr)
439 if err != nil {
440 errors = append(errors, fmt.Sprintf("%v", err))
441 } else {
442 responses = append(responses, response)
443 }
444 }
445 output := map[string][]string{}
446 if len(response) > 0 {
447 output["responses"] = responses
448 }
449 if len(errors) > 0 {
450 output["errors"] = errors
451 }
452 bytes, err := json.Marshal(output)
453 if err == nil {
454 fmt.Fprint(w, string(bytes))
455 } else {
456 http.Error(w, fmt.Sprintf("response could not be serialized. %v", err), http.StatusExpectationFailed)
457 }
458 }
459
460 func dialHTTP(request string, addr net.Addr) (string, error) {
461 transport := utilnet.SetTransportDefaults(&http.Transport{})
462 httpClient := createHTTPClient(transport)
463 resp, err := httpClient.Get(fmt.Sprintf("http://%s/%s", addr.String(), request))
464 defer transport.CloseIdleConnections()
465 if err == nil {
466 defer resp.Body.Close()
467 body, err := io.ReadAll(resp.Body)
468 if err == nil {
469 return string(body), nil
470 }
471 }
472 return "", err
473 }
474
475 func createHTTPClient(transport *http.Transport) *http.Client {
476 client := &http.Client{
477 Transport: transport,
478 Timeout: 5 * time.Second,
479 }
480 return client
481 }
482
483 func dialUDP(request string, addr net.Addr) (string, error) {
484 Conn, err := net.DialUDP("udp", nil, addr.(*net.UDPAddr))
485 if err != nil {
486 return "", fmt.Errorf("udp dial failed. err:%v", err)
487 }
488
489 defer Conn.Close()
490 buf := []byte(request)
491 _, err = Conn.Write(buf)
492 if err != nil {
493 return "", fmt.Errorf("udp connection write failed. err:%v", err)
494 }
495 udpResponse := make([]byte, 2048)
496 Conn.SetReadDeadline(time.Now().Add(5 * time.Second))
497 count, err := Conn.Read(udpResponse)
498 if err != nil || count == 0 {
499 return "", fmt.Errorf("reading from udp connection failed. err:'%v'", err)
500 }
501 return string(udpResponse[0:count]), nil
502 }
503
504 func dialSCTP(request string, addr net.Addr) (string, error) {
505 Conn, err := sctp.DialSCTP("sctp", nil, addr.(*sctp.SCTPAddr))
506 if err != nil {
507 return "", fmt.Errorf("sctp dial failed. err:%v", err)
508 }
509
510 defer Conn.Close()
511 buf := []byte(request)
512 _, err = Conn.Write(buf)
513 if err != nil {
514 return "", fmt.Errorf("sctp connection write failed. err:%v", err)
515 }
516 sctpResponse := make([]byte, 1024)
517 Conn.SetReadDeadline(time.Now().Add(5 * time.Second))
518 count, err := Conn.Read(sctpResponse)
519 if err != nil || count == 0 {
520 return "", fmt.Errorf("reading from sctp connection failed. err:'%v'", err)
521 }
522 return string(sctpResponse[0:count]), nil
523 }
524
525 func shellHandler(w http.ResponseWriter, r *http.Request) {
526 cmd := r.FormValue("shellCommand")
527 if cmd == "" {
528 cmd = r.FormValue("cmd")
529 }
530 log.Printf("GET /shell?cmd=%s", cmd)
531 cmdOut, err := exec.Command(shellPath, "-c", cmd).CombinedOutput()
532 output := map[string]string{}
533 if len(cmdOut) > 0 {
534 output["output"] = string(cmdOut)
535 }
536 if err != nil {
537 output["error"] = fmt.Sprintf("%v", err)
538 }
539 log.Printf("Output: %s", output)
540 bytes, err := json.Marshal(output)
541 if err == nil {
542 fmt.Fprint(w, string(bytes))
543 } else {
544 http.Error(w, fmt.Sprintf("response could not be serialized. %v", err), http.StatusExpectationFailed)
545 }
546 }
547
548 func uploadHandler(w http.ResponseWriter, r *http.Request) {
549 log.Printf("GET /upload")
550 result := map[string]string{}
551 file, _, err := r.FormFile("file")
552 if err != nil {
553 result["error"] = "Unable to upload file."
554 bytes, err := json.Marshal(result)
555 if err == nil {
556 fmt.Fprint(w, string(bytes))
557 } else {
558 http.Error(w, fmt.Sprintf("%s. Also unable to serialize output. %v", result["error"], err), http.StatusInternalServerError)
559 }
560 log.Printf("Unable to upload file: %s", err)
561 return
562 }
563 defer file.Close()
564
565 f, err := os.CreateTemp("/uploads", "upload")
566 if err != nil {
567 result["error"] = "Unable to open file for write"
568 bytes, err := json.Marshal(result)
569 if err == nil {
570 fmt.Fprint(w, string(bytes))
571 } else {
572 http.Error(w, fmt.Sprintf("%s. Also unable to serialize output. %v", result["error"], err), http.StatusInternalServerError)
573 }
574 log.Printf("Unable to open file for write: %s", err)
575 return
576 }
577 defer f.Close()
578 if _, err = io.Copy(f, file); err != nil {
579 result["error"] = "Unable to write file."
580 bytes, err := json.Marshal(result)
581 if err == nil {
582 fmt.Fprint(w, string(bytes))
583 } else {
584 http.Error(w, fmt.Sprintf("%s. Also unable to serialize output. %v", result["error"], err), http.StatusInternalServerError)
585 }
586 log.Printf("Unable to write file: %s", err)
587 return
588 }
589
590 UploadFile := f.Name()
591 if err := os.Chmod(UploadFile, 0700); err != nil {
592 result["error"] = "Unable to chmod file."
593 bytes, err := json.Marshal(result)
594 if err == nil {
595 fmt.Fprint(w, string(bytes))
596 } else {
597 http.Error(w, fmt.Sprintf("%s. Also unable to serialize output. %v", result["error"], err), http.StatusInternalServerError)
598 }
599 log.Printf("Unable to chmod file: %s", err)
600 return
601 }
602 log.Printf("Wrote upload to %s", UploadFile)
603 result["output"] = UploadFile
604 w.WriteHeader(http.StatusCreated)
605 bytes, err := json.Marshal(result)
606 if err != nil {
607 http.Error(w, fmt.Sprintf("%s. Also unable to serialize output. %v", result["error"], err), http.StatusInternalServerError)
608 return
609 }
610 fmt.Fprint(w, string(bytes))
611 }
612
613 func hostNameHandler(w http.ResponseWriter, r *http.Request) {
614 log.Printf("GET /hostName")
615 fmt.Fprint(w, getHostName())
616 }
617
618 func redirectHandler(w http.ResponseWriter, r *http.Request) {
619 location := r.FormValue("location")
620 codeString := r.FormValue("code")
621 log.Printf("%s /redirect?msg=%s&code=%s", r.Method, location, codeString)
622 code := http.StatusFound
623 if codeString != "" {
624 var err error
625 code, err = strconv.Atoi(codeString)
626 if err != nil && codeString != "" {
627 fmt.Fprintf(w, "argument 'code' must be an integer or empty, got %q\n", codeString)
628 return
629 }
630 }
631 http.Redirect(w, r, location, code)
632 }
633
634
635 func startUDPServer(address string, udpPort int) {
636 serverAddress, err := net.ResolveUDPAddr("udp", net.JoinHostPort(address, strconv.Itoa(udpPort)))
637 assertNoError(err, fmt.Sprintf("failed to resolve UDP address for port %d", udpPort))
638 serverConn, err := net.ListenUDP("udp", serverAddress)
639 assertNoError(err, fmt.Sprintf("failed to create listener for UDP address %v", serverAddress))
640 defer serverConn.Close()
641 buf := make([]byte, 2048)
642
643 log.Printf("Started UDP server on port %s %d", address, udpPort)
644
645 serverReady.set(true)
646 defer func() {
647 log.Printf("UDP server exited")
648 serverReady.set(false)
649 }()
650 for {
651 n, clientAddress, err := serverConn.ReadFromUDP(buf)
652 assertNoError(err, fmt.Sprintf("failed accepting UDP connections"))
653 receivedText := strings.ToLower(strings.TrimSpace(string(buf[0:n])))
654 if receivedText == "hostname" {
655 log.Println("Sending udp hostName response")
656 _, err = serverConn.WriteToUDP([]byte(getHostName()), clientAddress)
657 assertNoError(err, fmt.Sprintf("failed to write hostname to UDP client %s", clientAddress))
658 } else if strings.HasPrefix(receivedText, "echo ") {
659 parts := strings.SplitN(receivedText, " ", 2)
660 resp := ""
661 if len(parts) == 2 {
662 resp = parts[1]
663 }
664 log.Printf("Echoing %v to UDP client %s\n", resp, clientAddress)
665 _, err = serverConn.WriteToUDP([]byte(resp), clientAddress)
666 assertNoError(err, fmt.Sprintf("failed to echo to UDP client %s", clientAddress))
667 } else if receivedText == "clientip" {
668 log.Printf("Sending clientip back to UDP client %s\n", clientAddress)
669 _, err = serverConn.WriteToUDP([]byte(clientAddress.String()), clientAddress)
670 assertNoError(err, fmt.Sprintf("failed to write clientip to UDP client %s", clientAddress))
671 } else if len(receivedText) > 0 {
672 log.Printf("Unknown UDP command received from %s: %v\n", clientAddress, receivedText)
673 }
674 }
675 }
676
677
678 func startSCTPServer(sctpPort int) {
679 serverAddress, err := sctp.ResolveSCTPAddr("sctp", fmt.Sprintf(":%d", sctpPort))
680 assertNoError(err, fmt.Sprintf("failed to resolve SCTP address for port %d", sctpPort))
681 listener, err := sctp.ListenSCTP("sctp", serverAddress)
682 assertNoError(err, fmt.Sprintf("failed to create listener for SCTP address %v", serverAddress))
683 defer listener.Close()
684 buf := make([]byte, 1024)
685
686 log.Printf("Started SCTP server")
687
688 serverReady.set(true)
689 defer func() {
690 log.Printf("SCTP server exited")
691 serverReady.set(false)
692 }()
693 for {
694 conn, err := listener.AcceptSCTP()
695 assertNoError(err, fmt.Sprintf("failed accepting SCTP connections"))
696 clientAddress := conn.RemoteAddr().String()
697 n, err := conn.Read(buf)
698 assertNoError(err, fmt.Sprintf("failed to read from SCTP client %s", clientAddress))
699 receivedText := strings.ToLower(strings.TrimSpace(string(buf[0:n])))
700 if receivedText == "hostname" {
701 log.Println("Sending SCTP hostName response")
702 _, err = conn.Write([]byte(getHostName()))
703 assertNoError(err, fmt.Sprintf("failed to write hostname to SCTP client %s", clientAddress))
704 } else if strings.HasPrefix(receivedText, "echo ") {
705 parts := strings.SplitN(receivedText, " ", 2)
706 resp := ""
707 if len(parts) == 2 {
708 resp = parts[1]
709 }
710 log.Printf("Echoing %v to SCTP client %s\n", resp, clientAddress)
711 _, err = conn.Write([]byte(resp))
712 assertNoError(err, fmt.Sprintf("failed to echo to SCTP client %s", clientAddress))
713 } else if receivedText == "clientip" {
714 log.Printf("Sending clientip back to SCTP client %s\n", clientAddress)
715 _, err = conn.Write([]byte(clientAddress))
716 assertNoError(err, fmt.Sprintf("failed to write clientip to SCTP client %s", clientAddress))
717 } else if len(receivedText) > 0 {
718 log.Printf("Unknown SCTP command received from %s: %v\n", clientAddress, receivedText)
719 }
720 conn.Close()
721 }
722 }
723
724 func getHostName() string {
725 hostName, err := os.Hostname()
726 assertNoError(err, "failed to get hostname")
727 return hostName
728 }
729
730 func assertNoError(err error, detail string) {
731 if err != nil {
732 log.Fatalf("Error occurred: %s:%v", detail, err)
733 }
734 }
735
736 func parseAddresses(addresses string) ([]string, error) {
737 if addresses == "" {
738 return []string{bindToAny}, nil
739 }
740
741 res := make([]string, 0)
742 split := strings.Split(addresses, ",")
743 for _, address := range split {
744 netAddr := netutils.ParseIPSloppy(address)
745 if netAddr == nil {
746 return nil, fmt.Errorf("parseAddress: invalid address %s", address)
747 }
748 res = append(res, address)
749 }
750 set := sets.NewString(res...)
751 return set.List(), nil
752 }
753
View as plain text