mirror of
https://github.com/go-gost/x.git
synced 2026-10-20 08:18:33 +00:00
A p2p dial's stream survives the peer session under it being rebuilt, so the link can go silently dead: the far side re-creates its session while this side's stream becomes a zombie — writes succeed into the void and nothing reports an error. firstInboundTimeout was a one-shot, cleared by the first inbound byte and never re-armed, so such a dial stayed "running" forever: on 2026-10-17 an Android spoke sat in a two-way blackhole for 17 minutes after the hub killed and rebuilt its relay session alone (derp peer-gone kill), while both ends reported the path up; only a manual entrypoint restart healed it. The registration refresh is echoed by the hub while the route to this device is real, so silence is measurable. A p2p dial now re-arms the read deadline to p2pInboundTimeout on every inbound byte; when it expires the dial ends and the caller redials, which re-registers and heals a hub-side rebuild — the same recovery a manual restart performs (observed as "peer relay session up … client=true"). Socket dials keep the one-shot semantics: their transport reports the far side gone by itself.
121 lines
3.2 KiB
Go
121 lines
3.2 KiB
Go
package tun
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"testing"
|
|
|
|
"github.com/go-gost/core/chain"
|
|
"github.com/go-gost/core/handler"
|
|
"github.com/go-gost/core/hop"
|
|
xlogger "github.com/go-gost/x/logger"
|
|
)
|
|
|
|
// fakeRouter only needs Options: the mode decision reads Options().Chain and
|
|
// never dials.
|
|
type fakeRouter struct {
|
|
options *chain.RouterOptions
|
|
// dialConn, when set, is what Dial hands back — the test holds the far
|
|
// end of it, which is how a dial is driven without a network.
|
|
dialConn net.Conn
|
|
// dialFunc, when set, takes precedence over dialConn: it lets a test
|
|
// hand out a fresh link per dial, which one static conn cannot — the
|
|
// shape a redial loop needs.
|
|
dialFunc func(ctx context.Context, network, address string) (net.Conn, error)
|
|
}
|
|
|
|
func (r *fakeRouter) Options() *chain.RouterOptions { return r.options }
|
|
|
|
func (r *fakeRouter) Dial(ctx context.Context, network, address string, opts ...chain.DialOption) (net.Conn, error) {
|
|
if r.dialFunc != nil {
|
|
return r.dialFunc(ctx, network, address)
|
|
}
|
|
if r.dialConn != nil {
|
|
return r.dialConn, nil
|
|
}
|
|
return nil, errors.New("unexpected dial")
|
|
}
|
|
|
|
func (r *fakeRouter) Bind(ctx context.Context, network, address string, opts ...chain.BindOption) (net.Listener, error) {
|
|
return nil, errors.New("unexpected bind")
|
|
}
|
|
|
|
type fakeChainer struct{}
|
|
|
|
func (fakeChainer) Route(ctx context.Context, network, address string, opts ...chain.RouteOption) chain.Route {
|
|
return nil
|
|
}
|
|
|
|
type fakeHop struct{ node *chain.Node }
|
|
|
|
func (h fakeHop) Select(ctx context.Context, opts ...hop.SelectOption) *chain.Node { return h.node }
|
|
|
|
func newTestHandler(hp hop.Hop, router chain.Router) *tunHandler {
|
|
opts := []handler.Option{handler.LoggerOption(xlogger.Nop())}
|
|
if router != nil {
|
|
opts = append(opts, handler.RouterOption(router))
|
|
}
|
|
h := NewHandler(opts...).(*tunHandler)
|
|
if hp != nil {
|
|
h.Forward(hp)
|
|
}
|
|
return h
|
|
}
|
|
|
|
func TestSelectTarget(t *testing.T) {
|
|
chained := &fakeRouter{options: &chain.RouterOptions{Chain: fakeChainer{}}}
|
|
plain := &fakeRouter{options: &chain.RouterOptions{}}
|
|
|
|
tests := []struct {
|
|
name string
|
|
hop hop.Hop
|
|
router chain.Router
|
|
wantClient bool
|
|
wantAddr string
|
|
}{
|
|
{
|
|
name: "forwarder node",
|
|
hop: fakeHop{node: chain.NewNode("n0", "1.2.3.4:8421")},
|
|
router: chained,
|
|
wantClient: true,
|
|
wantAddr: "1.2.3.4:8421",
|
|
},
|
|
{
|
|
name: "forwarder with no node falls back to server",
|
|
hop: fakeHop{},
|
|
router: chained,
|
|
wantClient: false,
|
|
},
|
|
{
|
|
name: "chain without forwarder is client mode with no address",
|
|
router: chained,
|
|
wantClient: true,
|
|
wantAddr: "",
|
|
},
|
|
{
|
|
name: "no forwarder, no chain is server mode",
|
|
router: plain,
|
|
wantClient: false,
|
|
},
|
|
{
|
|
name: "nil router is server mode",
|
|
router: nil,
|
|
wantClient: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
h := newTestHandler(tt.hop, tt.router)
|
|
addr, client := h.selectTarget(context.Background())
|
|
if client != tt.wantClient {
|
|
t.Fatalf("client mode = %v, want %v", client, tt.wantClient)
|
|
}
|
|
if addr != tt.wantAddr {
|
|
t.Fatalf("dial addr = %q, want %q", addr, tt.wantAddr)
|
|
}
|
|
})
|
|
}
|
|
}
|