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.
6365var 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.
333339func (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.
378385func (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+
446517func (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.
480552func 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.
495568func 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.
508582func 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.
620696func 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.
699776func (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+
712793func 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.
727809func 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+
753839func extendc (c net.Conn , r time.Duration , w time.Duration ) {
754840 if c != nil {
755841 if r == w {
0 commit comments