mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-04-15 07:16:34 +00:00
* Bump Go to version from 1.24.6 to 1.25.4 * fix: use net.JoinHostPort for IPv6 compatibility Fix IPv6 address formatting in namespace-pinger.go by replacing fmt.Sprintf with net.JoinHostPort, which correctly handles both IPv4 and IPv6 addresses. Changes: - PingTCP: Use net.JoinHostPort for client connections - startTCPEchoServer: Use net.JoinHostPort for server listener This fixes go vet errors introduced by Go 1.25's stricter checks: address format "%s:%d" does not work with IPv6 IPv4 example: 192.168.1.1:8080 IPv6 example: [::1]:8080 (brackets added automatically) --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Nicholas Mullen <nwmullen@gmail.com>
219 lines
6.5 KiB
Go
219 lines
6.5 KiB
Go
//go:build linux
|
|
|
|
package namespaces
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
type NamespacePinger struct {
|
|
*ManagedNetworkNamespace
|
|
cfg Configuration
|
|
}
|
|
|
|
// PingUDP communicates with the provided IP address from within the namespace.
|
|
// This functions sends an UDP packet and expects to receive an echo back.
|
|
func (n *NamespacePinger) PingUDP(dst net.IP) error {
|
|
n.cfg.Logf("reaching to %q from %q with udp", dst, n.InternalIP)
|
|
pinger := func() error {
|
|
addr := &net.UDPAddr{IP: dst, Port: n.cfg.Port}
|
|
conn, err := net.DialUDP("udp", nil, addr)
|
|
if err != nil {
|
|
return fmt.Errorf("error dialing udp: %w", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
if _, err = conn.Write([]byte("echo")); err != nil {
|
|
return fmt.Errorf("error writing to udp socket: %w", err)
|
|
}
|
|
|
|
deadline := time.Now().Add(n.cfg.Timeout)
|
|
if err := conn.SetReadDeadline(deadline); err != nil {
|
|
return fmt.Errorf("error setting udp read deadline: %w", err)
|
|
}
|
|
|
|
// XXX: review this buffer size and validation.
|
|
buffer := make([]byte, 6)
|
|
if _, _, err = conn.ReadFromUDP(buffer); err != nil {
|
|
return fmt.Errorf("error reading from udp socket: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
return n.Run(pinger)
|
|
}
|
|
|
|
// PingTCP communicates with the provided IP address from within the namespace.
|
|
// This functions sends an TCP packet and expects to receive an echo back.
|
|
func (n *NamespacePinger) PingTCP(dst net.IP) error {
|
|
n.cfg.Logf("reaching to %q from %q with tcp", dst, n.InternalIP)
|
|
pinger := func() error {
|
|
addr := net.JoinHostPort(dst.String(), strconv.Itoa(n.cfg.Port))
|
|
conn, err := net.DialTimeout("tcp", addr, n.cfg.Timeout)
|
|
if err != nil {
|
|
return fmt.Errorf("error dialing tcp: %w", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
if _, err = conn.Write([]byte("echo")); err != nil {
|
|
return fmt.Errorf("error writing to tcp socket: %w", err)
|
|
}
|
|
|
|
// XXX: review this buffer size and validation.
|
|
buffer := make([]byte, 6)
|
|
if _, err = conn.Read(buffer); err != nil {
|
|
return fmt.Errorf("error reading from tcp socket: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
return n.Run(pinger)
|
|
}
|
|
|
|
// StartTCPEchoServer is a helper to run startTCPEchoServer inside a goroutine.
|
|
// This function blocks until the server is ready to receive packets or failed
|
|
// to start. Errors are sent to the provided channel.
|
|
func (n *NamespacePinger) StartTCPEchoServer(errors chan error) {
|
|
ready := make(chan struct{})
|
|
go func() {
|
|
errors <- n.startTCPEchoServer(ready)
|
|
}()
|
|
<-ready
|
|
}
|
|
|
|
// startTCPEchoServer starts a tcp server inside the namespace. The thread
|
|
// running the goroutine that process this call will be moved to the namespace.
|
|
// This echo servers just returns "echo" as a response. Once one packet is
|
|
// received, the server ends. Callers must wait until the ready channel is
|
|
// closed before they can start sending packets.
|
|
func (n *NamespacePinger) startTCPEchoServer(ready chan struct{}) (err error) {
|
|
addr := net.JoinHostPort(n.InternalIP.String(), strconv.Itoa(n.cfg.Port))
|
|
n.cfg.Logf("starting tcp echo server on namespace %q(%q)", n.name, addr)
|
|
|
|
if err = n.Join(); err != nil {
|
|
close(ready)
|
|
return fmt.Errorf("error joining namespace: %w", err)
|
|
}
|
|
|
|
defer func() {
|
|
err = WrapIfFail("error leaving namespace", err, n.Leave)
|
|
}()
|
|
|
|
listener, err := net.Listen("tcp", addr)
|
|
if err != nil {
|
|
close(ready)
|
|
return fmt.Errorf("error starting tcp server: %w", err)
|
|
}
|
|
defer listener.Close()
|
|
|
|
deadline := time.Now().Add(n.cfg.Timeout)
|
|
tcplistener := listener.(*net.TCPListener)
|
|
if err = tcplistener.SetDeadline(deadline); err != nil {
|
|
close(ready)
|
|
return fmt.Errorf("error setting tcp listener deadline: %w", err)
|
|
}
|
|
|
|
go func() {
|
|
// XXX: here be dragons. we can't signalize we are ready until
|
|
// the call to read is done so we artificially sleep for a bit
|
|
// here.
|
|
time.Sleep(100 * time.Millisecond)
|
|
close(ready)
|
|
}()
|
|
|
|
var conn net.Conn
|
|
if conn, err = listener.Accept(); err != nil {
|
|
return fmt.Errorf("error accepting connection: %w", err)
|
|
}
|
|
|
|
n.cfg.Logf("received tcp packet on %q from %q", n.InternalIP, conn.RemoteAddr())
|
|
|
|
if _, err = conn.Write([]byte("echo\n")); err != nil {
|
|
return fmt.Errorf("error writing to tcp socket: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// StartUDPEchoServer is a helper to run startUDPTCPEchoServer inside a goroutine.
|
|
// This function blocks until the server is ready to receive packets or failed
|
|
// to start. Errors are sent to the provided channel.
|
|
func (n *NamespacePinger) StartUDPEchoServer(errors chan error) {
|
|
ready := make(chan struct{})
|
|
go func() {
|
|
errors <- n.startUDPEchoServer(ready)
|
|
}()
|
|
<-ready
|
|
}
|
|
|
|
// startUDPEchoServers starts an udp server inside the namespace. The thread
|
|
// running the goroutine that process this call will be moved to the namespace.
|
|
// This echo servers just returns "echo" as a response. Once one packet is
|
|
// received, the server ends. Callers must wait until the ready channel is
|
|
// closed before they can start sending packets.
|
|
func (n *NamespacePinger) startUDPEchoServer(ready chan struct{}) (err error) {
|
|
addr := net.UDPAddr{Port: n.cfg.Port, IP: n.InternalIP}
|
|
n.cfg.Logf("starting udp echo server on namespace %q(%q)", n.name, addr.String())
|
|
|
|
if err = n.Join(); err != nil {
|
|
close(ready)
|
|
return fmt.Errorf("error joining namespace: %w", err)
|
|
}
|
|
|
|
defer func() {
|
|
err = WrapIfFail("error leaving namespace", err, n.Leave)
|
|
}()
|
|
|
|
conn, err := net.ListenUDP("udp", &addr)
|
|
if err != nil {
|
|
close(ready)
|
|
return fmt.Errorf("error starting udp server: %w", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
deadline := time.Now().Add(n.cfg.Timeout)
|
|
if err = conn.SetDeadline(deadline); err != nil {
|
|
close(ready)
|
|
return fmt.Errorf("error setting udp listener deadline: %w", err)
|
|
}
|
|
|
|
go func() {
|
|
// XXX: here be dragons. we can't signalize we are ready until
|
|
// the call to read is done so we artificially sleep for a bit
|
|
// here.
|
|
time.Sleep(100 * time.Millisecond)
|
|
close(ready)
|
|
}()
|
|
|
|
var source *net.UDPAddr
|
|
var buffer = make([]byte, 1024)
|
|
if _, source, err = conn.ReadFromUDP(buffer); err != nil {
|
|
return fmt.Errorf("error reading from udp socket: %w", err)
|
|
}
|
|
|
|
n.cfg.Logf("received udp packet on %q from %q", n.InternalIP, source.AddrPort())
|
|
|
|
if _, err = conn.WriteToUDP([]byte("echo"), source); err != nil {
|
|
return fmt.Errorf("error writing to udp socket: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func NewNamespacePinger(name, cidraddr string, options ...Option) (*NamespacePinger, error) {
|
|
config := NewConfiguration(options...)
|
|
|
|
namespace, err := NewManagedNetworkNamespace(name, cidraddr, options...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating network namespace: %w", err)
|
|
}
|
|
return &NamespacePinger{
|
|
ManagedNetworkNamespace: namespace,
|
|
cfg: config,
|
|
}, nil
|
|
}
|