Files
holdmysocks--ps5-tailscale/tsd/forward.go
T
holdmysocks 4554502591 Tailscale for jailbroken PS5
A payload that runs the Tailscale client (tsnet, userspace networking) on a
jailbroken PS5: a C launcher built with ps5-payload-sdk that loads a Go
program in-process, an installer that registers it with the console's
payload autoloader and adds a home screen icon, and the patch that makes Go
1.27.1 speak the PS5's FreeBSD 9 era syscall interface.
2026-10-02 10:29:31 -04:00

71 lines
2.2 KiB
Go

package main
import (
"io"
"net"
"net/netip"
"slices"
"strconv"
"time"
)
// forwardToLocalhost is tsnet's fallback TCP handler: it is asked about every
// tailnet connection to a port nothing in this process listens on. If a
// service on the console listens on that port, the connection is accepted and
// piped to it. That is what makes FTP, the payload loader and the like
// reachable over the tailnet.
//
// The local connection is made before answering, while the tailnet peer is
// still waiting for its SYN-ACK. If nothing listens on the port the
// connection is declined, so the peer sees an ordinary "connection refused"
// rather than a connection that opens and closes.
func (d *daemon) forwardToLocalhost(src, dst netip.AddrPort) (handler func(net.Conn), intercept bool) {
port := dst.Port()
if slices.Contains(d.cfg.BlockedPorts, port) || port == d.proxyPort || d.fwd.listensOnTCP(port) {
// The outbound proxy and the local forwards are for the console's
// own apps. Exposing them would let any tailnet device use the
// console as a relay.
return nil, false
}
local, err := net.DialTimeout("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(int(port))), 2*time.Second)
if err != nil {
return nil, false
}
// If the tailnet side never completes its handshake the handler is not
// called; do not keep the local connection open for it forever.
abandoned := time.AfterFunc(30*time.Second, func() { local.Close() })
return func(c net.Conn) {
defer c.Close()
defer local.Close()
if !abandoned.Stop() {
return
}
if port != d.webPort {
// The status page polls every few seconds; logging its own
// requests would bury everything else.
d.logf("forward %v -> localhost:%d", src, port)
}
pipe(c, local)
}, true
}
// pipe copies in both directions until both sides are done.
func pipe(a, b net.Conn) {
done := make(chan struct{}, 2)
cp := func(dst, src net.Conn) {
io.Copy(dst, src)
// Pass the end of the stream on, so that protocols relying on a
// half-close (such as sending a payload to the ELF loader) work.
if cw, ok := dst.(interface{ CloseWrite() error }); ok {
cw.CloseWrite()
} else {
dst.Close()
}
done <- struct{}{}
}
go cp(a, b)
go cp(b, a)
<-done
<-done
}