44 "context"
55 "errors"
66 "fmt"
7+ "net/netip"
8+ "strconv"
79 "sync"
810 "time"
911
@@ -433,8 +435,9 @@ func (s *Swarm) nonProxyAddr(addr ma.Multiaddr) bool {
433435// filterKnownUndialables takes a list of multiaddrs, and removes those
434436// that we definitely don't want to dial: addresses configured to be blocked,
435437// IPv6 link-local addresses, addresses without a dial-capable transport,
436- // and addresses that we know to be our own.
437- // This is an optimization to avoid wasting time on dials that we know are going to fail.
438+ // addresses that we know to be our own, and addresses with a better tranport
439+ // available. This is an optimization to avoid wasting time on dials that we
440+ // know are going to fail or for which we have a better alternative.
438441func (s * Swarm ) filterKnownUndialables (p peer.ID , addrs []ma.Multiaddr ) []ma.Multiaddr {
439442 lisAddrs , _ := s .InterfaceListenAddresses ()
440443 var ourAddrs []ma.Multiaddr
@@ -448,21 +451,17 @@ func (s *Swarm) filterKnownUndialables(p peer.ID, addrs []ma.Multiaddr) []ma.Mul
448451 })
449452 }
450453
451- // Make a map of udp ports we are listening on to filter peers web transport addresses
452- ourLocalHostUDPPorts := make (map [string ]bool , 2 )
453- for _ , a := range ourAddrs {
454- if ! manet .IsIPLoopback (a ) {
455- continue
456- }
457- if p , err := a .ValueForProtocol (ma .P_UDP ); err == nil {
458- ourLocalHostUDPPorts [p ] = true
459- }
460- }
454+ // The order of these two filters is important. If we can only dial /webtransport,
455+ // we don't want to filter /webtransport addresses out because the peer had a /quic-v1
456+ // address
457+
458+ // filter addresses we cannot dial
459+ addrs = ma .FilterAddrs (addrs , s .canDial )
460+ // filter low priority addresses among the addresses we can dial
461+ addrs = filterLowPriorityAddresses (addrs )
461462
462463 return ma .FilterAddrs (addrs ,
463464 func (addr ma.Multiaddr ) bool { return ! ma .Contains (ourAddrs , addr ) },
464- func (addr ma.Multiaddr ) bool { return checkLocalHostUDPAddrs (addr , ourLocalHostUDPPorts ) },
465- s .canDial ,
466465 // TODO: Consider allowing link-local addresses
467466 func (addr ma.Multiaddr ) bool { return ! manet .IsIP6LinkLocal (addr ) },
468467 func (addr ma.Multiaddr ) bool {
@@ -559,15 +558,79 @@ func isRelayAddr(addr ma.Multiaddr) bool {
559558 return err == nil
560559}
561560
562- // checkLocalHostUDPAddrs returns false for addresses that have the same localhost port
563- // as the one we are listening on
564- // This is useful for filtering out peer's localhost webtransport addresses.
565- func checkLocalHostUDPAddrs (addr ma.Multiaddr , ourUDPPorts map [string ]bool ) bool {
566- if ! manet .IsIPLoopback (addr ) {
567- return true
561+ // filterLowPriorityAddresses removes addresses inplace for which we have a better alternative
562+ // 1. If a /quic-v1 address is present, filter out /quic and /webtransport address on the same 2-tuple:
563+ // QUIC v1 is preferred over the deprecated QUIC draft-29, and given the choice, we prefer using
564+ // raw QUIC over using WebTransport.
565+ // 2. If a /tcp address is present, filter out /ws or /wss addresses on the same 2-tuple:
566+ // We prefer using raw TCP over using WebSocket.
567+ func filterLowPriorityAddresses (addrs []ma.Multiaddr ) []ma.Multiaddr {
568+ // make a map of QUIC v1 and TCP AddrPorts.
569+ quicV1Addr := make (map [netip.AddrPort ]struct {})
570+ tcpAddr := make (map [netip.AddrPort ]struct {})
571+ for _ , a := range addrs {
572+ switch {
573+ case isProtocolAddr (a , ma .P_WEBTRANSPORT ):
574+ case isProtocolAddr (a , ma .P_QUIC_V1 ):
575+ ap , err := addrPort (a , ma .P_UDP )
576+ if err != nil {
577+ continue
578+ }
579+ quicV1Addr [ap ] = struct {}{}
580+ case isProtocolAddr (a , ma .P_WS ) || isProtocolAddr (a , ma .P_WSS ):
581+ case isProtocolAddr (a , ma .P_TCP ):
582+ ap , err := addrPort (a , ma .P_TCP )
583+ if err != nil {
584+ continue
585+ }
586+ tcpAddr [ap ] = struct {}{}
587+ }
568588 }
569- if p , err := addr .ValueForProtocol (ma .P_UDP ); err == nil {
570- return ! ourUDPPorts [p ]
589+
590+ i := 0
591+ for _ , a := range addrs {
592+ switch {
593+ case isProtocolAddr (a , ma .P_WEBTRANSPORT ) || isProtocolAddr (a , ma .P_QUIC ):
594+ ap , err := addrPort (a , ma .P_UDP )
595+ if err != nil {
596+ break
597+ }
598+ if _ , ok := quicV1Addr [ap ]; ok {
599+ continue
600+ }
601+ case isProtocolAddr (a , ma .P_WS ) || isProtocolAddr (a , ma .P_WSS ):
602+ ap , err := addrPort (a , ma .P_TCP )
603+ if err != nil {
604+ break
605+ }
606+ if _ , ok := tcpAddr [ap ]; ok {
607+ continue
608+ }
609+ }
610+ addrs [i ] = a
611+ i ++
612+ }
613+ return addrs [:i ]
614+ }
615+
616+ // addrPort returns the ip and port for a. p should be either ma.P_TCP or ma.P_UDP.
617+ // a must be an (ip, TCP) or (ip, udp) address.
618+ func addrPort (a ma.Multiaddr , p int ) (netip.AddrPort , error ) {
619+ ip , err := manet .ToIP (a )
620+ if err != nil {
621+ return netip.AddrPort {}, err
622+ }
623+ port , err := a .ValueForProtocol (p )
624+ if err != nil {
625+ return netip.AddrPort {}, err
626+ }
627+ pi , err := strconv .Atoi (port )
628+ if err != nil {
629+ return netip.AddrPort {}, err
630+ }
631+ addr , ok := netip .AddrFromSlice (ip )
632+ if ! ok {
633+ return netip.AddrPort {}, fmt .Errorf ("failed to parse IP %s" , ip )
571634 }
572- return true
635+ return netip . AddrPortFrom ( addr , uint16 ( pi )), nil
573636}
0 commit comments