Skip to content

Commit 3dbd8ac

Browse files
committed
tcp,udp,icmp: break loopback loop where approp
1 parent dfc14c4 commit 3dbd8ac

4 files changed

Lines changed: 135 additions & 15 deletions

File tree

intra/common.go

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ var (
5959
anyaddr6 = netip.IPv6Unspecified()
6060
)
6161

62+
var onlyExitPid = []string{ipn.Exit}
63+
6264
// immediate is the wait time before sending a summary to the listener.
6365
var immediate = time.Duration(0)
6466

@@ -87,6 +89,9 @@ type baseHandler struct {
8789
conntracker core.ConnMapper // connid -> [local,remote]
8890
fwtracker *core.ExpMap[string, string] // uid+dst(domainOrIP) -> blockSecs
8991

92+
ltmu sync.RWMutex
93+
looptracker map[string]uint64 // dst -> count
94+
9095
once sync.Once
9196

9297
// fields below are mutable
@@ -330,6 +335,7 @@ func (h *baseHandler) forward(local, remote net.Conn, smm *FlowSummary) {
330335
h.queueSummary(smm.done(derr, uploaded.err))
331336
}
332337

338+
// queueSummary queues a summary to be sent to the listener; thread-safe.
333339
func (h *baseHandler) queueSummary(s *FlowSummary) {
334340
if s == nil {
335341
return
@@ -375,6 +381,7 @@ func (h *baseHandler) processSummaries() {
375381
}
376382
}
377383

384+
// sendSummary sends a summary to the listener; thread-safe.
378385
func (h *baseHandler) sendSummary(s *FlowSummary, after time.Duration) {
379386
defer core.Recover(core.DontExit, "c.sendNotif: "+s.ID)
380387

@@ -443,6 +450,70 @@ func (h *baseHandler) stall(flowid string) (secs uint32) {
443450
return
444451
}
445452

453+
func (h *baseHandler) loopAssoc(smm *FlowSummary) (y bool) {
454+
if smm == nil || smm.UID != SELF_UID || ipn.Exit == smm.PID || len(smm.Target) <= 0 {
455+
return false
456+
}
457+
h.ltmu.Lock()
458+
defer h.ltmu.Unlock()
459+
460+
if settings.Loopingback.Load() {
461+
if len(h.looptracker) > 0 {
462+
clear(h.looptracker)
463+
}
464+
return false
465+
}
466+
467+
k := looptrackerKey(smm)
468+
v, loaded := h.looptracker[k]
469+
if !loaded {
470+
h.looptracker[k] = 1
471+
} else {
472+
h.looptracker[k] = v + 1
473+
}
474+
return true
475+
}
476+
477+
func (h *baseHandler) loopDetected(smm *FlowSummary) (y bool) {
478+
if smm == nil || smm.UID != SELF_UID || ipn.Exit == smm.PID || len(smm.Target) <= 0 {
479+
return false
480+
}
481+
h.ltmu.RLock()
482+
defer h.ltmu.RUnlock()
483+
484+
if v, ok := h.looptracker[looptrackerKey(smm)]; ok {
485+
return v >= 1
486+
}
487+
return false
488+
}
489+
490+
func (h *baseHandler) loopUnassoc(smm *FlowSummary) (y bool) {
491+
if smm == nil || smm.UID != SELF_UID || ipn.Exit == smm.PID || len(smm.Target) <= 0 {
492+
return false
493+
}
494+
495+
h.ltmu.Lock()
496+
defer h.ltmu.Unlock()
497+
if !settings.Loopingback.Load() {
498+
if len(h.looptracker) > 0 {
499+
clear(h.looptracker)
500+
}
501+
return false
502+
}
503+
504+
k := looptrackerKey(smm)
505+
v, loaded := h.looptracker[k]
506+
if !loaded {
507+
return false
508+
}
509+
if v <= 1 {
510+
delete(h.looptracker, k)
511+
} else {
512+
h.looptracker[k] = v - 1
513+
}
514+
return true
515+
}
516+
446517
func (h *baseHandler) isDNS(addr netip.AddrPort) bool {
447518
return addr.IsValid() && h.resolver.IsDnsAddr(addr)
448519
}
@@ -476,6 +547,7 @@ func (h *baseHandler) End() {
476547
})
477548
}
478549

550+
// upload copies data from remote to local, and returns the number of bytes copied and error, if any.
479551
// TODO: Propagate TCP RST using local.Abort(), on appropriate errors.
480552
func upload(id string, local, remote net.Conn, ioch chan<- ioinfo) {
481553
defer core.Recover(core.Exit11, "c.upload."+id)
@@ -492,6 +564,7 @@ func upload(id string, local, remote net.Conn, ioch chan<- ioinfo) {
492564
ioch <- ioinfo{n, err}
493565
}
494566

567+
// download copies data from local to remote, and returns the number of bytes copied and error, if any.
495568
func download(id string, local, remote net.Conn) (n int64, err error) {
496569
defer core.CloseOp(local, core.CopW)
497570
defer core.CloseOp(remote, core.CopR)
@@ -505,6 +578,7 @@ func download(id string, local, remote net.Conn) (n int64, err error) {
505578
return
506579
}
507580

581+
// oneRealIPPort returns the first valid AddrPort from realips, or origipp if none are valid.
508582
func oneRealIPPort(realips []netip.Addr, origipp netip.AddrPort, maybeIncludeOrig bool) netip.AddrPort {
509583
if len(realips) <= 0 {
510584
return origipp
@@ -617,6 +691,8 @@ func (h *baseHandler) undoAlg(algip netip.Addr, uid string) (undidAlg bool, real
617691
return
618692
}
619693

694+
// filterFamilyForDialingWithFailSafe filters out invalid IPs and IPs that are not
695+
// of the family that the dialer is configured to use, and includes one excluded IP as a fail-safe if needed.
620696
func filterFamilyForDialingWithFailSafe(ipcsv string) (included []netip.Addr, excluded []netip.Addr, excludedIsIncluded bool) {
621697
included, excluded, excludedIsIncluded = filterFamilyForDialing(ipcsv)
622698
if !excludedIsIncluded && len(excluded) > 0 && len(included) > 0 {
@@ -673,7 +749,7 @@ func (h *baseHandler) judge(decision *Mark, aux ...string) (cid, uid, fid string
673749
}
674750

675751
if len(decision.UID) > 0 {
676-
uid = decision.UID
752+
uid = decision.UID // may be SELF_UID or UNKNOWN_UID_STR
677753
} else {
678754
uid = UNKNOWN_UID_STR
679755
}
@@ -696,6 +772,7 @@ func (h *baseHandler) judge(decision *Mark, aux ...string) (cid, uid, fid string
696772
return
697773
}
698774

775+
// maybeReplaceDest replaces the target AddrPort with the IP in res, if res.IP is set and valid.
699776
func (h *baseHandler) maybeReplaceDest(res *Mark, target *netip.AddrPort) {
700777
if len(res.IP) <= 0 {
701778
return
@@ -709,6 +786,10 @@ func (h *baseHandler) maybeReplaceDest(res *Mark, target *netip.AddrPort) {
709786
}
710787
}
711788

789+
func (h *baseHandler) flowing(smm *FlowSummary) {
790+
h.listener.Flowing(smm.postMark())
791+
}
792+
712793
func conn2str(a net.Conn, b net.Conn) string {
713794
ar := a.RemoteAddr()
714795
br := b.RemoteAddr()
@@ -724,6 +805,7 @@ func clos(c ...core.MinConn) {
724805
core.CloseConn(c...)
725806
}
726807

808+
// ntoa returns the IP protocol number for the given network string.
727809
func ntoa(n string) int32 {
728810
switch n {
729811
case "udp", "udp6", "udp4":
@@ -750,6 +832,10 @@ func containsPid(pids []string, pid string) bool {
750832
return slices.Contains(pids, pid)
751833
}
752834

835+
func looptrackerKey(smm *FlowSummary) string {
836+
return smm.Proto + ":" + smm.Target
837+
}
838+
753839
func extendc(c net.Conn, r time.Duration, w time.Duration) {
754840
if c != nil {
755841
if r == w {

intra/icmp.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,10 @@ func (h *icmpHandler) Ping(msg []byte, source, target netip.AddrPort) (echoed bo
6868
defer func() {
6969
smm.PID = pidstr(px)
7070
smm.RPID = ipn.ViaID(px)
71+
smm.Target = dst.Addr().String()
7172
smm.Tx = int64(tx)
7273
smm.Rx = int64(rx)
7374
smm.Rtt = rtt.Milliseconds()
74-
smm.Target = dst.Addr().String()
7575
h.queueSummary(smm.done(err)) // err may be nil
7676
}()
7777

@@ -99,6 +99,20 @@ func (h *icmpHandler) Ping(msg []byte, source, target netip.AddrPort) (echoed bo
9999
return false // denied
100100
}
101101

102+
smm.PID = pidstr(px)
103+
smm.RPID = ipn.ViaID(px)
104+
smm.Target = dst.Addr().String()
105+
106+
if h.loopDetected(smm) {
107+
log.I("t.icmp: loop: break %s => %s via %s for %s; exiting...", source, dst, pidstr(px), uid)
108+
px, _ = h.prox.ProxyTo(dst, uid, onlyExitPid)
109+
smm.PID = ipn.Exit
110+
smm.RPID = ""
111+
}
112+
113+
h.loopAssoc(smm)
114+
defer h.loopUnassoc(smm)
115+
102116
rttstart := time.Now()
103117
proto, anyaddr := anyaddrFor(dst)
104118

@@ -118,7 +132,7 @@ func (h *icmpHandler) Ping(msg []byte, source, target netip.AddrPort) (echoed bo
118132
defer h.conntracker.Untrack(cid)
119133

120134
awaited := core.Await(func() {
121-
h.listener.Flowing(smm.postMark())
135+
h.flowing(smm)
122136
}, onFlowTimeout)
123137

124138
tx = len(msg)

intra/tcp.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,14 @@ func (h *tcpHandler) Proxy(gconn *netstack.GTCPConn, src, target netip.AddrPort)
339339
continue
340340
}
341341

342+
if h.loopDetected(smm) {
343+
log.I("tcp: loop: break %s => %s via %s for %s; exiting...", src, dstipp, pidstr(px), uid)
344+
px, _ = h.prox.ProxyTo(dstipp, uid, onlyExitPid)
345+
smm.PID = ipn.Exit
346+
smm.RPID = ""
347+
continue
348+
}
349+
342350
if cont, err = h.handle(px, gconn, src, dstipp, delayForHappyEyeballs, smm); err == nil {
343351
return allow // smm instead queued by handle() => forward()
344352
} else {
@@ -446,17 +454,21 @@ func (h *tcpHandler) handle(px ipn.Proxy, gconn *netstack.GTCPConn, src, target
446454
return stop, synackerr
447455
}
448456

457+
h.loopAssoc(smm)
458+
459+
dstlocal := dst.LocalAddr()
449460
core.Go("tcp.forward."+smm.ID, func() {
450-
h.listener.Flowing(smm.postMark())
461+
defer h.loopUnassoc(smm)
462+
h.flowing(smm)
451463
h.forward(gconn, rwext{dst, tcptimeout}, smm) // src always *gonet.TCPConn
452-
// TODO assoc if forward was successful
464+
// TODO: assoc if forward was successful
453465
if eim {
454-
h.natAssoc(smm.PID, src, dst.LocalAddr())
466+
h.natAssoc(smm.PID, src, dstlocal)
455467
}
456468
})
457469

458-
log.I("tcp: %s dialed %s proxy(%s) %s => %s (bind? %t / bindaddr? %s) for %s; rtt? %s",
459-
smm.ID, smm.PID, src, targetstr, dialbindOK, bindAddr, smm.UID, core.FmtMillis(smm.Rtt))
470+
log.I("tcp: %s dialed %s proxy(%s) %s => %s [%v] (bind? %t / bindaddr? %s) for %s; rtt? %s",
471+
smm.ID, smm.PID, src, targetstr, dstlocal, dialbindOK, bindAddr, smm.UID, core.FmtMillis(smm.Rtt))
460472

461473
return cont, nil // handled; takes ownership of src
462474
}

intra/udp.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,12 @@ func (h *udpHandler) proxy(gconn *netstack.GUDPConn, src, dst netip.AddrPort, dm
184184
return true // ok
185185
}
186186

187+
h.loopAssoc(smm)
188+
187189
cid := smm.ID
188190
core.Go("udp.forward."+cid, func() {
189-
h.listener.Flowing(smm.postMark())
191+
defer h.loopUnassoc(smm)
192+
h.flowing(smm)
190193
h.forward(gconn, rwext{remote, udptimeout}, smm)
191194
})
192195
return true // ok
@@ -323,6 +326,17 @@ func (h *udpHandler) Connect(gconn *netstack.GUDPConn, src, target netip.AddrPor
323326
continue
324327
}
325328

329+
smm.PID = pxid // last chosen proxy may yet emayrror out
330+
smm.RPID = rxid
331+
smm.Target = lastselected // may be invalid
332+
333+
if h.loopDetected(smm) {
334+
log.I("udp: loop: break %s => %s via %s for %s; exiting...", src, lastselected, pxid, uid)
335+
px, _ = h.prox.ProxyTo(dstipp, uid, onlyExitPid)
336+
pxid = ipn.Exit
337+
rxid = ""
338+
}
339+
326340
canportfwd = portfwd && ipn.Remote(pxid)
327341

328342
if mux { // mux is not supported by all proxies (few like Exit, Base, WG support it)
@@ -355,12 +369,6 @@ func (h *udpHandler) Connect(gconn *netstack.GUDPConn, src, target netip.AddrPor
355369
}
356370
}
357371

358-
if len(pxid) > 0 { // last chosen proxy which may have errored
359-
smm.PID = pxid
360-
smm.RPID = rxid
361-
smm.Target = lastselected // may be invalid
362-
}
363-
364372
if !selectedTarget.IsValid() {
365373
log.E("udp: connect: %s [%s] no target addr for %s from %v",
366374
cid, uid, target, actualTargets)

0 commit comments

Comments
 (0)