diff --git a/client/ish_listener_linux_386.go b/client/ish_listener_linux_386.go index b2fe729..c50e4c3 100644 --- a/client/ish_listener_linux_386.go +++ b/client/ish_listener_linux_386.go @@ -11,6 +11,7 @@ import ( type ishListener struct { net.Listener + f *os.File fd int } @@ -26,10 +27,17 @@ func wrapISHListener(ln net.Listener) (net.Listener, error) { return nil, err } - return &ishListener{Listener: ln, fd: int(f.Fd())}, nil + // Keep a reference to *os.File so the garbage collector doesn't close the FD. + return &ishListener{Listener: ln, f: f, fd: int(f.Fd())}, nil } func (l *ishListener) Accept() (net.Conn, error) { + // Set the listener socket to blocking mode. Go makes it non-blocking by default. + // This avoids using time.Sleep in a spin-loop, which triggers futex_time64 SIGSYS in modern Go on iSH. + if err := syscall.SetNonblock(l.fd, false); err != nil { + return nil, err + } + for { addr := make([]byte, 128) addrlen := uintptr(128) @@ -38,10 +46,10 @@ func (l *ishListener) Accept() (net.Conn, error) { // SYS_ACCEPT is subcall 5. args := [3]uintptr{uintptr(l.fd), uintptr(unsafe.Pointer(&addr[0])), uintptr(unsafe.Pointer(&addrlen))} - r1, _, errno := syscall.Syscall(102, 5, uintptr(unsafe.Pointer(&args)), 0) + // Use Syscall6 to ensure we have enough arguments registers for the platform. + r1, _, errno := syscall.Syscall6(102, 5, uintptr(unsafe.Pointer(&args)), 0, 0, 0, 0) if errno != 0 { - if errno == syscall.EAGAIN || errno == syscall.EINTR || errno == syscall.EWOULDBLOCK { - time.Sleep(50 * time.Millisecond) // Just in case it's non-blocking somehow + if errno == syscall.EINTR { continue } return nil, errno @@ -49,26 +57,63 @@ func (l *ishListener) Accept() (net.Conn, error) { nfd := int(r1) - // Wrap raw FD into os.File, then into a net.Conn. - // fileConn duplicates the fd again. - f := os.NewFile(uintptr(nfd), "ish-conn") - conn, err := net.FileConn(f) - f.Close() - - if err != nil { - syscall.Close(nfd) - return nil, err - } - + // We avoid Go's net.FileConn because it tries to register the fd with Go's epoll poller, + // which in iSH emulator consistency fails with EEXIST (file exists). + // Instead, we return a custom blocking net.Conn wrapper. + conn := &ishConn{fd: nfd} return conn, nil } } func (l *ishListener) Close() error { - err1 := syscall.Close(l.fd) + // Close both the duplicated FD and the original listener. + err1 := l.f.Close() err2 := l.Listener.Close() if err1 != nil { return err1 } return err2 } + +// ishConn bypasses Go's network poller to prevent EEXIST bugs in iSH +type ishConn struct { + fd int +} + +func (c *ishConn) Read(b []byte) (n int, err error) { + n, err = syscall.Read(c.fd, b) + if err != nil { + if err == syscall.EAGAIN || err == syscall.EINTR { + return 0, nil + } + return n, err + } + if n == 0 { + return 0, os.ErrClosed + } + return n, nil +} + +func (c *ishConn) Write(b []byte) (n int, err error) { + n, err = syscall.Write(c.fd, b) + if err != nil { + return n, err + } + return n, nil +} + +func (c *ishConn) Close() error { + return syscall.Close(c.fd) +} + +func (c *ishConn) LocalAddr() net.Addr { + return &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 9000} +} + +func (c *ishConn) RemoteAddr() net.Addr { + return &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0} +} + +func (c *ishConn) SetDeadline(t time.Time) error { return nil } +func (c *ishConn) SetReadDeadline(t time.Time) error { return nil } +func (c *ishConn) SetWriteDeadline(t time.Time) error { return nil } diff --git a/client/main.go b/client/main.go index 9766723..e2c7fcb 100644 --- a/client/main.go +++ b/client/main.go @@ -2192,6 +2192,7 @@ func createSmuxSession(ctx context.Context, tp *turnParams, peer *net.UDPAddr, i STUNServerAddr: turnServerAddr, TURNServerAddr: turnServerAddr, Conn: turnConn, + Net: directNet{}, Username: user, Password: pass, RequestedAddressFamily: addrFamily,