package socket import ( "errors" "net" "os" "runtime" "syscall" ) const listenAttempts = 42 const udpBufferSize = 16 * 1024 * 1024 // NewSocket creates either TCP or UDP socket listener on a given port. // The proto param supports on of these values: // udp, udp4, udp6, tcp, tcp4, tcp6 // The function result will be either *net.UDPConn for UDPs or // *net.TCPListener for TCPs. func NewSocket(proto string, port int) (any, error) { if listener, err := socket(proto, port); err != nil { return nil, err } else { return listener, nil } } // NewSocketPortRoll creates either TCP or UDP socket listener on the next free port. // See: NewSocket. func NewSocketPortRoll(proto string, port int) (listener any, err error) { if listener, err = NewSocket(proto, port); err == nil { return listener, nil } if IsPortBusyError(err) { for i := port + 1; i < port+listenAttempts; i++ { listener, err := socket(proto, i) if err == nil { return listener, nil } } return nil, errors.New("no available ports") } return nil, err } func socket(proto string, port int) (any, error) { switch proto { case "udp", "udp4", "udp6": if l, err := net.ListenUDP(proto, &net.UDPAddr{Port: port}); err == nil { _ = l.SetReadBuffer(udpBufferSize) _ = l.SetWriteBuffer(udpBufferSize) return l, nil } else { return nil, err } case "tcp", "tcp4", "tcp6": if l, err := net.ListenTCP(proto, &net.TCPAddr{Port: port}); err == nil { return l, nil } else { return nil, err } } return nil, errors.New("socket error") } // IsPortBusyError tests if the given error is one of // the port busy errors. func IsPortBusyError(err error) bool { if err == nil { return false } var eOsSyscall *os.SyscallError if !errors.As(err, &eOsSyscall) { return false } var errErrno syscall.Errno if !errors.As(eOsSyscall, &errErrno) { return false } if errErrno == syscall.EADDRINUSE { return true } const WSAEADDRINUSE = 10048 if runtime.GOOS == "windows" && errErrno == WSAEADDRINUSE { return true } return false }