Skip to content

Instantly share code, notes, and snippets.

@jbenet
Last active July 26, 2022 06:09
Show Gist options
  • Save jbenet/5c191d698fe9ec58c49d to your computer and use it in GitHub Desktop.
Save jbenet/5c191d698fe9ec58c49d to your computer and use it in GitHub Desktop.
// Package main demonstrates a problem with sockets.
// run it with:
//
// go build
// ./test 127.0.0.1:1234
//
// Nevermind, found the problem (using select wrong)
//
package main
import (
"fmt"
"net"
"os"
"strconv"
"syscall"
"time"
sockaddrnet "github.com/jbenet/go-sockaddr/net"
)
func main() {
localAddr := os.Args[1]
l, err := net.Listen("tcp", localAddr)
if err != nil {
fmt.Printf("Listen failed: %s\n", err)
return
}
defer l.Close()
fd, err := newSocket()
if err != nil {
fmt.Printf("newSocket failed: %s\n", err)
return
}
defer syscall.Close(fd)
netAddr, err := net.ResolveTCPAddr("tcp", localAddr)
if err != nil {
fmt.Printf("ResolveTCPAddr failed: %s\n", err)
return
}
rsa := sockaddrnet.NetAddrToSockaddr(netAddr)
if err = connect(fd, rsa, time.Time{}); err != nil {
fmt.Printf("connect failed: %s\n", err)
return
}
// if we wait here, the addr will resolve in fd,
// and the FileConn will correctly inherit it
// <-time.After(time.Second)
f := os.NewFile(uintptr(fd), "port."+strconv.Itoa(os.Getpid()))
c, err := net.FileConn(f)
if err != nil {
fmt.Printf("FileConn failed: %v\n", err)
return
}
defer c.Close()
// waiting here has no effect. the FileConn will not inherit the addr
// <-time.After(time.Second)
fmt.Printf("LocalAddr: %v, RemoteAddr: %v\n", c.LocalAddr(), c.RemoteAddr())
}
func newSocket() (fd int, err error) {
syscall.ForkLock.RLock()
fd, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
if err == nil {
syscall.CloseOnExec(fd)
}
syscall.ForkLock.RUnlock()
if err != nil {
return -1, err
}
if err = syscall.SetNonblock(fd, true); err != nil {
syscall.Close(fd)
return -1, err
}
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
syscall.Close(fd)
return -1, err
}
return fd, err
}
// this is close to the connect() function inside stdlib/net
func connect(fd int, ra syscall.Sockaddr, deadline time.Time) error {
switch err := syscall.Connect(fd, ra); err {
case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR:
case nil, syscall.EISCONN:
if !deadline.IsZero() && deadline.Before(time.Now()) {
return errTimeout
}
return nil
default:
return err
}
var err error
var to syscall.Timeval
var toptr *syscall.Timeval
var pw syscall.FdSet
FD_SET(uintptr(fd), &pw)
for {
// wait until the fd is ready to read or write.
if !deadline.IsZero() {
to = syscall.NsecToTimeval(deadline.Sub(time.Now()).Nanoseconds())
toptr = &to
}
// wait until the fd is ready to write. we can't use:
// if err := fd.pd.WaitWrite(); err != nil {
// return err
// }
// so we use select instead.
if _, err = Select(fd+1, nil, &pw, nil, toptr); err != nil {
fmt.Println(err)
return err
}
var nerr int
nerr, err = syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_ERROR)
if err != nil {
return err
}
switch err = syscall.Errno(nerr); err {
case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR:
continue
case syscall.Errno(0), syscall.EISCONN:
if !deadline.IsZero() && deadline.Before(time.Now()) {
return errTimeout
}
return nil
default:
return err
}
}
}
var errTimeout = &timeoutError{}
type timeoutError struct{}
func (e *timeoutError) Error() string { return "i/o timeout" }
func (e *timeoutError) Timeout() bool { return true }
func (e *timeoutError) Temporary() bool { return true }
func FD_SET(fd uintptr, p *syscall.FdSet) {
n, k := fd/32, fd%32
p.Bits[n] |= (1 << uint32(k))
}
// +build darwin freebsd dragonfly netbsd openbsd
package main
import (
"syscall"
)
func Select(nfd int, r *syscall.FdSet, w *syscall.FdSet, e *syscall.FdSet, timeout *syscall.Timeval) (n int, err error) {
err = syscall.Select(nfd, r, w, e, timeout)
return 0, err
}
// +build linux
package main
import (
"syscall"
)
func Select(nfd int, r *syscall.FdSet, w *syscall.FdSet, e *syscall.FdSet, timeout *syscall.Timeval) (n int, err error) {
return syscall.Select(nfd, r, w, e, timeout)
}
@jbenet
Copy link
Author

jbenet commented Jan 22, 2015

Run with:

git clone https://gist.github.com/jbenet/5c191d698fe9ec58c49d test
cd test
go build
./test 127.0.0.1:1234

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment