Browse Source

fix: improve iSH compatibility and fix critical runtime bugs

Fixed 'SIGSYS: bad system call (futex_time64)' by using blocking accept and avoiding Go's time.Sleep in spin-loops.
Fixed 'TCP accept error: file file+net ish-conn: file exists' by implementing custom ishConn that bypasses Go's network poller.
Fixed 'route ip+net: netlinkrib: invalid argument' by using directNet bypass in TURN client.
Fixed 'TCP accept error: bad file descriptor' by retaining os.File reference to prevent GC from closing the FD.
pull/181/head
Moroka8 3 months ago
parent
commit
2fc71afb08
  1. 77
      client/ish_listener_linux_386.go
  2. 1
      client/main.go

77
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 }

1
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,

Loading…
Cancel
Save