55 "fmt"
66 "io/ioutil"
77 mathRand "math/rand"
8+ "net"
9+ "net/netip"
810 "os"
911 "path/filepath"
1012 "strings"
@@ -20,6 +22,7 @@ import (
2022
2123 "github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
2224 "github.com/cloudflare/cloudflared/edgediscovery/allregions"
25+ "github.com/cloudflare/cloudflared/packet"
2326
2427 "github.com/cloudflare/cloudflared/config"
2528 "github.com/cloudflare/cloudflared/connection"
@@ -384,6 +387,12 @@ func prepareTunnelConfig(
384387 NeedPQ : needPQ ,
385388 PQKexIdx : pqKexIdx ,
386389 }
390+ packetConfig , err := newPacketConfig (c , log )
391+ if err != nil {
392+ log .Warn ().Err (err ).Msg ("ICMP proxy feature is disabled" )
393+ } else {
394+ tunnelConfig .PacketConfig = packetConfig
395+ }
387396 orchestratorConfig := & orchestration.Config {
388397 Ingress : & ingressRules ,
389398 WarpRouting : ingress .NewWarpRoutingConfig (& cfg .WarpRouting ),
@@ -453,3 +462,147 @@ func parseConfigIPVersion(version string) (v allregions.ConfigIPVersion, err err
453462 }
454463 return
455464}
465+
466+ func newPacketConfig (c * cli.Context , logger * zerolog.Logger ) (* packet.GlobalRouterConfig , error ) {
467+ ipv4Src , err := determineICMPv4Src (c .String ("icmpv4-src" ), logger )
468+ if err != nil {
469+ return nil , errors .Wrap (err , "failed to determine IPv4 source address for ICMP proxy" )
470+ }
471+ logger .Info ().Msgf ("ICMP proxy will use %s as source for IPv4" , ipv4Src )
472+
473+ ipv6Src , zone , err := determineICMPv6Src (c .String ("icmpv6-src" ), logger , ipv4Src )
474+ if err != nil {
475+ return nil , errors .Wrap (err , "failed to determine IPv6 source address for ICMP proxy" )
476+ }
477+ if zone != "" {
478+ logger .Info ().Msgf ("ICMP proxy will use %s in zone %s as source for IPv6" , ipv6Src , zone )
479+ } else {
480+ logger .Info ().Msgf ("ICMP proxy will use %s as source for IPv6" , ipv6Src )
481+ }
482+
483+ icmpRouter , err := ingress .NewICMPRouter (ipv4Src , ipv6Src , zone , logger )
484+ if err != nil {
485+ return nil , err
486+ }
487+ return & packet.GlobalRouterConfig {
488+ ICMPRouter : icmpRouter ,
489+ IPv4Src : ipv4Src ,
490+ IPv6Src : ipv6Src ,
491+ Zone : zone ,
492+ }, nil
493+ }
494+
495+ func determineICMPv4Src (userDefinedSrc string , logger * zerolog.Logger ) (netip.Addr , error ) {
496+ if userDefinedSrc != "" {
497+ addr , err := netip .ParseAddr (userDefinedSrc )
498+ if err != nil {
499+ return netip.Addr {}, err
500+ }
501+ if addr .Is4 () {
502+ return addr , nil
503+ }
504+ return netip.Addr {}, fmt .Errorf ("expect IPv4, but %s is IPv6" , userDefinedSrc )
505+ }
506+
507+ addr , err := findLocalAddr (net .ParseIP ("192.168.0.1" ), 53 )
508+ if err != nil {
509+ addr = netip .IPv4Unspecified ()
510+ logger .Debug ().Err (err ).Msgf ("Failed to determine the IPv4 for this machine. It will use %s to send/listen for ICMPv4 echo" , addr )
511+ }
512+ return addr , nil
513+ }
514+
515+ type interfaceIP struct {
516+ name string
517+ ip net.IP
518+ }
519+
520+ func determineICMPv6Src (userDefinedSrc string , logger * zerolog.Logger , ipv4Src netip.Addr ) (addr netip.Addr , zone string , err error ) {
521+ if userDefinedSrc != "" {
522+ userDefinedIP , zone , _ := strings .Cut (userDefinedSrc , "%" )
523+ addr , err := netip .ParseAddr (userDefinedIP )
524+ if err != nil {
525+ return netip.Addr {}, "" , err
526+ }
527+ if addr .Is6 () {
528+ return addr , zone , nil
529+ }
530+ return netip.Addr {}, "" , fmt .Errorf ("expect IPv6, but %s is IPv4" , userDefinedSrc )
531+ }
532+
533+ // Loop through all the interfaces, the preference is
534+ // 1. The interface where ipv4Src is in
535+ // 2. Interface with IPv6 address
536+ // 3. Unspecified interface
537+
538+ interfaces , err := net .Interfaces ()
539+ if err != nil {
540+ return netip .IPv6Unspecified (), "" , nil
541+ }
542+
543+ interfacesWithIPv6 := make ([]interfaceIP , 0 )
544+ for _ , interf := range interfaces {
545+ interfaceAddrs , err := interf .Addrs ()
546+ if err != nil {
547+ continue
548+ }
549+
550+ foundIPv4SrcInterface := false
551+ for _ , interfaceAddr := range interfaceAddrs {
552+ if ipnet , ok := interfaceAddr .(* net.IPNet ); ok {
553+ ip := ipnet .IP
554+ if ip .Equal (ipv4Src .AsSlice ()) {
555+ foundIPv4SrcInterface = true
556+ }
557+ if ip .To4 () == nil {
558+ interfacesWithIPv6 = append (interfacesWithIPv6 , interfaceIP {
559+ name : interf .Name ,
560+ ip : ip ,
561+ })
562+ }
563+ }
564+ }
565+ // Found the interface of ipv4Src. Loop through the addresses to see if there is an IPv6
566+ if foundIPv4SrcInterface {
567+ for _ , interfaceAddr := range interfaceAddrs {
568+ if ipnet , ok := interfaceAddr .(* net.IPNet ); ok {
569+ ip := ipnet .IP
570+ if ip .To4 () == nil {
571+ addr , err := netip .ParseAddr (ip .String ())
572+ if err == nil {
573+ return addr , interf .Name , nil
574+ }
575+ }
576+ }
577+ }
578+ }
579+ }
580+
581+ for _ , interf := range interfacesWithIPv6 {
582+ addr , err := netip .ParseAddr (interf .ip .String ())
583+ if err == nil {
584+ return addr , interf .name , nil
585+ }
586+ }
587+ logger .Debug ().Err (err ).Msgf ("Failed to determine the IPv6 for this machine. It will use %s to send/listen for ICMPv6 echo" , netip .IPv6Unspecified ())
588+
589+ return netip .IPv6Unspecified (), "" , nil
590+ }
591+
592+ // FindLocalAddr tries to dial UDP and returns the local address picked by the OS
593+ func findLocalAddr (dst net.IP , port int ) (netip.Addr , error ) {
594+ udpConn , err := net .DialUDP ("udp" , nil , & net.UDPAddr {
595+ IP : dst ,
596+ Port : port ,
597+ })
598+ if err != nil {
599+ return netip.Addr {}, err
600+ }
601+ defer udpConn .Close ()
602+ localAddrPort , err := netip .ParseAddrPort (udpConn .LocalAddr ().String ())
603+ if err != nil {
604+ return netip.Addr {}, err
605+ }
606+ localAddr := localAddrPort .Addr ()
607+ return localAddr , nil
608+ }
0 commit comments