mirror of
https://github.com/go-gost/x.git
synced 2026-10-19 16:16:32 +00:00
fix(handler/tun): run keepalive on p2p links and expire stale routes
Three fixes found while wiring the p2p hub topology: - The client keepalive was gated on network == "udp", but on a p2p link raddr is the peer's base64 key (no port), so SplitHostPort fails and network is "ip": the keepalive never ran, a tun server never learned the client's route, and the read deadline was never armed. Gate on the udp network OR a configured keepalive period; the udp branch keeps the one-shot registration handshake it always sent. - Liveness was refreshed only by the keepalive echo; a transient channel hiccup can drop that single packet and redial a healthy tunnel after 3xperiod. Refresh the read deadline after any successful read instead. - Routes were never removed: a departed client's IP -> addr stayed forever and the device kept blackholing to a dead address. Expire a route once its last keepalive is older than 3x the configured period, checked lazily on the write path (no sweeper). The TTL is opt-in: without a server-side keepalive/ttl, routes keep their current never-expiring behavior.
This commit is contained in:
+14
-7
@@ -44,7 +44,11 @@ func (h *tunHandler) handleClient(ctx context.Context, conn net.Conn, network st
|
||||
}
|
||||
defer cc.Close()
|
||||
|
||||
if network == "udp" {
|
||||
// A datagram link needs the keepalive. On the udp network it is the
|
||||
// registration handshake (and runs even without keepalive:true, so
|
||||
// existing configs keep their one-shot registration); on a p2p link
|
||||
// ("ip") it is what registers the route with the peer's tun server.
|
||||
if network == "udp" || h.md.keepAlivePeriod > 0 {
|
||||
iterCtx, iterCancel := context.WithCancel(ctx)
|
||||
defer iterCancel()
|
||||
|
||||
@@ -178,13 +182,16 @@ func (h *tunHandler) transportClient(ctx context.Context, tun io.ReadWriter, con
|
||||
return err
|
||||
}
|
||||
|
||||
if n == keepAliveHeaderLength && bytes.Equal(b[:4], magicHeader) {
|
||||
ip := net.IP(b[4:20])
|
||||
log.Debugf("keepalive received at %v", ip)
|
||||
// Any inbound byte proves the tunnel is alive: refresh the liveness
|
||||
// deadline before interpreting the packet. Relying on the keepalive
|
||||
// echo alone (which a transient channel hiccup can drop) would redial
|
||||
// a healthy tunnel after 3×period.
|
||||
if h.md.keepAlivePeriod > 0 {
|
||||
conn.SetReadDeadline(time.Now().Add(h.md.keepAlivePeriod * 3))
|
||||
}
|
||||
|
||||
if h.md.keepAlivePeriod > 0 {
|
||||
conn.SetReadDeadline(time.Now().Add(h.md.keepAlivePeriod * 3))
|
||||
}
|
||||
if n == keepAliveHeaderLength && bytes.Equal(b[:4], magicHeader) {
|
||||
log.Debugf("keepalive received at %v", net.IP(b[4:20]))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+40
-12
@@ -97,7 +97,7 @@ func (h *tunHandler) transportServer(ctx context.Context, tun io.ReadWriter, con
|
||||
return nil
|
||||
}
|
||||
|
||||
addr := h.findRouteFor(ctx, dst, config.Router)
|
||||
addr := h.findRouteFor(ctx, dst, config.Router, log)
|
||||
if addr == nil {
|
||||
log.Debugf("no route for %s -> %s, packet discarded", src, dst)
|
||||
return nil
|
||||
@@ -228,7 +228,7 @@ func (h *tunHandler) transportServer(ctx context.Context, tun io.ReadWriter, con
|
||||
}
|
||||
|
||||
if !h.md.p2p {
|
||||
if addr := h.findRouteFor(ctx, dst, config.Router); addr != nil {
|
||||
if addr := h.findRouteFor(ctx, dst, config.Router, log); addr != nil {
|
||||
log.Debugf("find route: %s -> %s", dst, addr)
|
||||
|
||||
_, err := conn.WriteTo(b[:n], addr)
|
||||
@@ -252,30 +252,58 @@ func (h *tunHandler) transportServer(ctx context.Context, tun io.ReadWriter, con
|
||||
return collectFirstError(errc, cancel)
|
||||
}
|
||||
|
||||
// tunRoute is a registered route: the client's UDP address and when a keepalive
|
||||
// last refreshed it, so an absent client's route can expire.
|
||||
type tunRoute struct {
|
||||
addr net.Addr
|
||||
lastSeen time.Time
|
||||
}
|
||||
|
||||
func (h *tunHandler) updateRoute(ip net.IP, addr net.Addr, log logger.Logger) {
|
||||
if h.md.p2p {
|
||||
ip = net.IPv6zero
|
||||
}
|
||||
rkey := ipToTunRouteKey(ip)
|
||||
if actual, loaded := h.routes.LoadOrStore(rkey, addr); loaded {
|
||||
if actual.(net.Addr).String() != addr.String() {
|
||||
h.routes.Store(rkey, addr)
|
||||
log.Debugf("update route: %s -> %s (old %s)",
|
||||
ip, addr, actual.(net.Addr))
|
||||
now := time.Now()
|
||||
if actual, loaded := h.routes.LoadOrStore(rkey, tunRoute{addr: addr, lastSeen: now}); loaded {
|
||||
old := actual.(tunRoute)
|
||||
h.routes.Store(rkey, tunRoute{addr: addr, lastSeen: now}) // refresh lastSeen
|
||||
if old.addr.String() != addr.String() {
|
||||
log.Debugf("update route: %s -> %s (old %s)", ip, addr, old.addr)
|
||||
}
|
||||
} else {
|
||||
log.Debugf("new route: %s -> %s", ip, addr)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *tunHandler) findRouteFor(ctx context.Context, dst net.IP, router router.Router) net.Addr {
|
||||
// liveRoute returns the route for rkey, lazily expiring it once the opt-in TTL
|
||||
// has passed since its last keepalive. The TTL applies only when a keepalive
|
||||
// period is configured (server-side keepalive/ttl), so an unconfigured
|
||||
// deployment keeps today's never-expiring routes. No sweeper: every write path
|
||||
// consults this, which is enough.
|
||||
func (h *tunHandler) liveRoute(rkey tunRouteKey, log logger.Logger) (net.Addr, bool) {
|
||||
v, ok := h.routes.Load(rkey)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
r := v.(tunRoute)
|
||||
if ttl := h.md.keepAlivePeriod * 3; ttl > 0 && time.Since(r.lastSeen) > ttl {
|
||||
if h.routes.CompareAndDelete(rkey, r) {
|
||||
log.Infof("route expired: %s -> %s", net.IP(rkey[:]), r.addr)
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
return r.addr, true
|
||||
}
|
||||
|
||||
func (h *tunHandler) findRouteFor(ctx context.Context, dst net.IP, router router.Router, log logger.Logger) net.Addr {
|
||||
if h.md.p2p {
|
||||
dst = net.IPv6zero
|
||||
router = nil
|
||||
}
|
||||
|
||||
if v, ok := h.routes.Load(ipToTunRouteKey(dst)); ok {
|
||||
return v.(net.Addr)
|
||||
if addr, ok := h.liveRoute(ipToTunRouteKey(dst), log); ok {
|
||||
return addr
|
||||
}
|
||||
|
||||
if router == nil {
|
||||
@@ -284,8 +312,8 @@ func (h *tunHandler) findRouteFor(ctx context.Context, dst net.IP, router router
|
||||
|
||||
if route := router.GetRoute(ctx, dst.String()); route != nil {
|
||||
if gw := net.ParseIP(route.Gateway); gw != nil {
|
||||
if v, ok := h.routes.Load(ipToTunRouteKey(gw)); ok {
|
||||
return v.(net.Addr)
|
||||
if addr, ok := h.liveRoute(ipToTunRouteKey(gw), log); ok {
|
||||
return addr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user