mirror of
https://github.com/holdmysocks/ps5-tailscale.git
synced 2026-10-06 12:00:20 +02:00
The status page can now be protected with a password and edits the settings itself, so the config file no longer has to be changed by hand. - Password for everything on the status page that shows or changes something. The console's own browser is exempt. Connections to the page from the tailnet are served directly so they are not taken for local. - Settings form: name, ports, forwards, proxy, priority, update checks. Most take effect at once; the page says which need a restart. - Game streaming: several Sunshine hosts, each with its own port. - The HTTP proxy is off by default. - The page says when a newer release exists. - Uninstall removes the home screen icon. - Priority setting for streams that stutter under a demanding game. - After the console's network is reconfigured, Tailscale is asked to rebind. Seen working across a short stay in rest mode. - Favicon, and the device list can be collapsed.
75 lines
2.3 KiB
Go
75 lines
2.3 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 port == d.webPort {
|
|
// The status page is served on the tailnet connection itself rather
|
|
// than through localhost, so that the page sees who is asking.
|
|
return d.tailnetWeb.deliver, true
|
|
}
|
|
d.mu.Lock()
|
|
blocked := slices.Contains(d.cfg.BlockedPorts, port) || port == d.proxyPort
|
|
d.mu.Unlock()
|
|
if blocked || 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
|
|
}
|
|
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
|
|
}
|