2018-10-08 19:20:28 +00:00
package tunnel
2018-05-01 23:45:06 +00:00
import (
2020-11-02 11:21:34 +00:00
"crypto/tls"
2018-05-01 23:45:06 +00:00
"fmt"
"io/ioutil"
2022-08-24 12:33:10 +00:00
mathRand "math/rand"
2022-09-20 10:39:51 +00:00
"net"
"net/netip"
2018-05-01 23:45:06 +00:00
"os"
"path/filepath"
"strings"
2021-09-21 10:02:59 +00:00
"time"
2018-05-01 23:45:06 +00:00
2021-03-23 14:30:43 +00:00
"github.com/google/uuid"
homedir "github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
2022-03-28 09:53:22 +00:00
"github.com/urfave/cli/v2/altsrc"
2021-03-23 14:30:43 +00:00
"golang.org/x/crypto/ssh/terminal"
2021-12-27 19:05:14 +00:00
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
2021-03-08 16:46:23 +00:00
"github.com/cloudflare/cloudflared/config"
2020-10-08 10:12:26 +00:00
"github.com/cloudflare/cloudflared/connection"
2020-10-14 10:28:07 +00:00
"github.com/cloudflare/cloudflared/edgediscovery"
2023-02-06 19:06:02 +00:00
"github.com/cloudflare/cloudflared/edgediscovery/allregions"
2023-03-29 20:12:32 +00:00
"github.com/cloudflare/cloudflared/features"
2020-10-09 00:12:29 +00:00
"github.com/cloudflare/cloudflared/ingress"
2022-02-11 10:49:06 +00:00
"github.com/cloudflare/cloudflared/orchestration"
2022-02-07 09:42:07 +00:00
"github.com/cloudflare/cloudflared/supervisor"
2018-05-01 23:45:06 +00:00
"github.com/cloudflare/cloudflared/tlsconfig"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
)
2020-12-28 18:10:01 +00:00
const LogFieldOriginCertPath = "originCertPath"
2022-03-28 09:53:22 +00:00
const secretValue = "*****"
2020-12-28 18:10:01 +00:00
2018-05-01 23:45:06 +00:00
var (
2018-10-08 19:20:28 +00:00
developerPortal = "https://developers.cloudflare.com/argo-tunnel"
serviceUrl = developerPortal + "/reference/service/"
argumentsUrl = developerPortal + "/reference/arguments/"
2020-12-28 18:10:01 +00:00
2023-03-29 20:12:32 +00:00
secretFlags = [ 2 ] * altsrc . StringFlag { credentialsContentsFlag , tunnelTokenFlag }
2022-04-27 10:51:06 +00:00
2023-02-28 16:11:42 +00:00
configFlags = [ ] string { "autoupdate-freq" , "no-autoupdate" , "retries" , "protocol" , "loglevel" , "transport-loglevel" , "origincert" , "metrics" , "metrics-update-freq" , "edge-ip-version" , "edge-bind-address" }
2018-05-01 23:45:06 +00:00
)
2020-08-14 20:52:47 +00:00
// returns the first path that contains a cert.pem file. If none of the DefaultConfigSearchDirectories
2018-10-08 19:20:28 +00:00
// contains a cert.pem file, return empty string
2018-05-01 23:45:06 +00:00
func findDefaultOriginCertPath ( ) string {
2020-08-14 20:52:47 +00:00
for _ , defaultConfigDir := range config . DefaultConfigSearchDirectories ( ) {
2018-10-08 19:20:28 +00:00
originCertPath , _ := homedir . Expand ( filepath . Join ( defaultConfigDir , config . DefaultCredentialFile ) )
if ok , _ := config . FileExists ( originCertPath ) ; ok {
2018-05-01 23:45:06 +00:00
return originCertPath
}
}
return ""
}
2020-11-25 06:55:13 +00:00
func generateRandomClientID ( log * zerolog . Logger ) ( string , error ) {
2019-03-28 17:58:23 +00:00
u , err := uuid . NewRandom ( )
if err != nil {
2020-11-25 06:55:13 +00:00
log . Error ( ) . Msgf ( "couldn't create UUID for client ID %s" , err )
2019-03-28 17:58:23 +00:00
return "" , err
}
return u . String ( ) , nil
2018-05-01 23:45:06 +00:00
}
2020-11-25 06:55:13 +00:00
func logClientOptions ( c * cli . Context , log * zerolog . Logger ) {
2018-05-01 23:45:06 +00:00
flags := make ( map [ string ] interface { } )
2021-01-15 20:25:20 +00:00
for _ , flag := range c . FlagNames ( ) {
2022-03-28 09:53:22 +00:00
if isSecretFlag ( flag ) {
flags [ flag ] = secretValue
} else {
flags [ flag ] = c . Generic ( flag )
}
2018-05-01 23:45:06 +00:00
}
2019-03-08 08:36:24 +00:00
2018-05-01 23:45:06 +00:00
if len ( flags ) > 0 {
2021-01-15 20:25:20 +00:00
log . Info ( ) . Msgf ( "Settings: %v" , flags )
2018-05-01 23:45:06 +00:00
}
envs := make ( map [ string ] string )
// Find env variables for Argo Tunnel
for _ , env := range os . Environ ( ) {
// All Argo Tunnel env variables start with TUNNEL_
if strings . Contains ( env , "TUNNEL_" ) {
vars := strings . Split ( env , "=" )
if len ( vars ) == 2 {
2022-03-28 09:53:22 +00:00
if isSecretEnvVar ( vars [ 0 ] ) {
envs [ vars [ 0 ] ] = secretValue
} else {
envs [ vars [ 0 ] ] = vars [ 1 ]
}
2018-05-01 23:45:06 +00:00
}
}
}
if len ( envs ) > 0 {
2020-11-25 06:55:13 +00:00
log . Info ( ) . Msgf ( "Environmental variables %v" , envs )
2018-05-01 23:45:06 +00:00
}
}
2022-03-28 09:53:22 +00:00
func isSecretFlag ( key string ) bool {
for _ , flag := range secretFlags {
if flag . Name == key {
return true
}
}
return false
}
func isSecretEnvVar ( key string ) bool {
for _ , flag := range secretFlags {
for _ , secretEnvVar := range flag . EnvVars {
if secretEnvVar == key {
return true
}
}
}
return false
}
2022-02-07 09:42:07 +00:00
func dnsProxyStandAlone ( c * cli . Context , namedTunnel * connection . NamedTunnelProperties ) bool {
2023-02-06 17:13:05 +00:00
return c . IsSet ( "proxy-dns" ) &&
! ( c . IsSet ( "name" ) || // adhoc-named tunnel
2023-03-06 23:19:10 +00:00
c . IsSet ( ingress . HelloWorldFlag ) || // quick or named tunnel
2023-02-06 17:13:05 +00:00
namedTunnel != nil ) // named tunnel
2018-05-01 23:45:06 +00:00
}
2020-12-28 18:10:01 +00:00
func findOriginCert ( originCertPath string , log * zerolog . Logger ) ( string , error ) {
2020-05-21 20:36:49 +00:00
if originCertPath == "" {
2020-11-25 06:55:13 +00:00
log . Info ( ) . Msgf ( "Cannot determine default origin certificate path. No file %s in %v" , config . DefaultCredentialFile , config . DefaultConfigSearchDirectories ( ) )
2018-05-01 23:45:06 +00:00
if isRunningFromTerminal ( ) {
2020-11-25 06:55:13 +00:00
log . Error ( ) . Msgf ( "You need to specify the origin certificate path with --origincert option, or set TUNNEL_ORIGIN_CERT environment variable. See %s for more information." , argumentsUrl )
2020-12-28 18:10:01 +00:00
return "" , fmt . Errorf ( "client didn't specify origincert path when running from terminal" )
2018-05-01 23:45:06 +00:00
} else {
2020-11-25 06:55:13 +00:00
log . Error ( ) . Msgf ( "You need to specify the origin certificate path by specifying the origincert option in the configuration file, or set TUNNEL_ORIGIN_CERT environment variable. See %s for more information." , serviceUrl )
2020-12-28 18:10:01 +00:00
return "" , fmt . Errorf ( "client didn't specify origincert path" )
2018-05-01 23:45:06 +00:00
}
}
2020-05-21 20:36:49 +00:00
var err error
originCertPath , err = homedir . Expand ( originCertPath )
2018-05-01 23:45:06 +00:00
if err != nil {
2020-12-28 18:10:01 +00:00
log . Err ( err ) . Msgf ( "Cannot resolve origin certificate path" )
return "" , fmt . Errorf ( "cannot resolve path %s" , originCertPath )
2018-05-01 23:45:06 +00:00
}
2020-05-21 20:36:49 +00:00
// Check that the user has acquired a certificate using the login command
2018-10-08 19:20:28 +00:00
ok , err := config . FileExists ( originCertPath )
2018-05-01 23:45:06 +00:00
if err != nil {
2021-03-01 09:23:18 +00:00
log . Error ( ) . Err ( err ) . Msgf ( "Cannot check if origin cert exists at path %s" , originCertPath )
2020-12-28 18:10:01 +00:00
return "" , fmt . Errorf ( "cannot check if origin cert exists at path %s" , originCertPath )
2018-05-01 23:45:06 +00:00
}
if ! ok {
2020-11-25 06:55:13 +00:00
log . Error ( ) . Msgf ( ` Cannot find a valid certificate for your origin at the path :
2018-05-01 23:45:06 +00:00
% s
If the path above is wrong , specify the path with the - origincert option .
If you don ' t have a certificate signed by Cloudflare , run the command :
% s login
` , originCertPath , os . Args [ 0 ] )
2020-12-28 18:10:01 +00:00
return "" , fmt . Errorf ( "cannot find a valid certificate at the path %s" , originCertPath )
2018-05-01 23:45:06 +00:00
}
2020-05-21 20:36:49 +00:00
return originCertPath , nil
}
2020-12-28 18:10:01 +00:00
func readOriginCert ( originCertPath string ) ( [ ] byte , error ) {
2018-05-01 23:45:06 +00:00
// Easier to send the certificate as []byte via RPC than decoding it at this point
originCert , err := ioutil . ReadFile ( originCertPath )
if err != nil {
2020-12-28 18:10:01 +00:00
return nil , fmt . Errorf ( "cannot read %s to load origin certificate" , originCertPath )
2018-05-01 23:45:06 +00:00
}
return originCert , nil
}
2020-12-28 18:10:01 +00:00
func getOriginCert ( originCertPath string , log * zerolog . Logger ) ( [ ] byte , error ) {
if originCertPath , err := findOriginCert ( originCertPath , log ) ; err != nil {
2020-05-21 20:36:49 +00:00
return nil , err
} else {
2020-12-28 18:10:01 +00:00
return readOriginCert ( originCertPath )
2020-05-21 20:36:49 +00:00
}
}
2019-02-28 22:56:01 +00:00
func prepareTunnelConfig (
c * cli . Context ,
2021-12-27 19:05:14 +00:00
info * cliutil . BuildInfo ,
2021-02-03 18:32:54 +00:00
log , logTransport * zerolog . Logger ,
2021-01-14 22:33:36 +00:00
observer * connection . Observer ,
2022-02-07 09:42:07 +00:00
namedTunnel * connection . NamedTunnelProperties ,
2022-02-11 10:49:06 +00:00
) ( * supervisor . TunnelConfig , * orchestration . Config , error ) {
2023-02-06 17:13:05 +00:00
clientID , err := uuid . NewRandom ( )
2018-05-01 23:45:06 +00:00
if err != nil {
2023-02-06 17:13:05 +00:00
return nil , nil , errors . Wrap ( err , "can't generate connector UUID" )
2023-02-01 14:01:59 +00:00
}
2023-02-06 17:13:05 +00:00
log . Info ( ) . Msgf ( "Generated Connector ID: %s" , clientID )
2018-05-01 23:45:06 +00:00
tags , err := NewTagSliceFromCLI ( c . StringSlice ( "tag" ) )
if err != nil {
2020-12-28 18:10:01 +00:00
log . Err ( err ) . Msg ( "Tag parse failure" )
2022-02-07 09:42:07 +00:00
return nil , nil , errors . Wrap ( err , "Tag parse failure" )
2018-05-01 23:45:06 +00:00
}
2023-02-06 17:13:05 +00:00
tags = append ( tags , tunnelpogs . Tag { Name : "ID" , Value : clientID . String ( ) } )
2022-04-05 22:07:10 +00:00
transportProtocol := c . String ( "protocol" )
2022-08-24 12:33:10 +00:00
needPQ := c . Bool ( "post-quantum" )
if needPQ {
if FipsEnabled {
return nil , nil , fmt . Errorf ( "post-quantum not supported in FIPS mode" )
}
// Error if the user tries to force a non-quic transport protocol
if transportProtocol != connection . AutoSelectFlag && transportProtocol != connection . QUIC . String ( ) {
return nil , nil , fmt . Errorf ( "post-quantum is only supported with the quic transport" )
}
transportProtocol = connection . QUIC . String ( )
}
2023-03-29 20:12:32 +00:00
clientFeatures := dedup ( append ( c . StringSlice ( "features" ) , features . DefaultFeatures ... ) )
2023-02-06 17:13:05 +00:00
if needPQ {
2023-03-29 20:12:32 +00:00
clientFeatures = append ( clientFeatures , features . FeaturePostQuantum )
2023-02-06 17:13:05 +00:00
}
namedTunnel . Client = tunnelpogs . ClientInfo {
ClientID : clientID [ : ] ,
2023-03-29 20:12:32 +00:00
Features : clientFeatures ,
2023-02-06 17:13:05 +00:00
Version : info . Version ( ) ,
Arch : info . OSArch ( ) ,
}
cfg := config . GetConfiguration ( )
2023-03-09 23:23:11 +00:00
ingressRules , err := ingress . ParseIngressFromConfigAndCLI ( cfg , c , log )
if err != nil {
2023-02-06 17:13:05 +00:00
return nil , nil , err
}
2018-10-08 19:20:28 +00:00
2023-02-06 19:06:02 +00:00
protocolSelector , err := connection . NewProtocolSelector ( transportProtocol , namedTunnel . Credentials . AccountTag , c . IsSet ( TunnelTokenFlag ) , c . Bool ( "post-quantum" ) , edgediscovery . ProtocolPercentage , connection . ResolveTTL , log )
2020-10-14 10:28:07 +00:00
if err != nil {
2022-02-07 09:42:07 +00:00
return nil , nil , err
2020-10-14 10:28:07 +00:00
}
2020-11-25 06:55:13 +00:00
log . Info ( ) . Msgf ( "Initial protocol %s" , protocolSelector . Current ( ) )
2020-10-14 13:42:00 +00:00
edgeTLSConfigs := make ( map [ connection . Protocol ] * tls . Config , len ( connection . ProtocolList ) )
for _ , p := range connection . ProtocolList {
2021-08-17 14:30:02 +00:00
tlsSettings := p . TLSSettings ( )
if tlsSettings == nil {
2022-02-07 09:42:07 +00:00
return nil , nil , fmt . Errorf ( "%s has unknown TLS settings" , p )
2021-08-17 14:30:02 +00:00
}
edgeTLSConfig , err := tlsconfig . CreateTunnelConfig ( c , tlsSettings . ServerName )
2020-10-14 13:42:00 +00:00
if err != nil {
2022-02-07 09:42:07 +00:00
return nil , nil , errors . Wrap ( err , "unable to create TLS config to connect with edge" )
2020-10-14 13:42:00 +00:00
}
2021-08-21 16:31:28 +00:00
if len ( tlsSettings . NextProtos ) > 0 {
2021-08-17 14:30:02 +00:00
edgeTLSConfig . NextProtos = tlsSettings . NextProtos
}
2020-10-14 13:42:00 +00:00
edgeTLSConfigs [ p ] = edgeTLSConfig
2018-11-15 15:43:50 +00:00
}
2020-10-08 10:12:26 +00:00
2021-09-21 10:02:59 +00:00
gracePeriod , err := gracePeriod ( c )
if err != nil {
2022-02-07 09:42:07 +00:00
return nil , nil , err
2020-10-08 10:12:26 +00:00
}
2022-06-02 17:57:37 +00:00
edgeIPVersion , err := parseConfigIPVersion ( c . String ( "edge-ip-version" ) )
if err != nil {
return nil , nil , err
}
2023-02-28 16:11:42 +00:00
edgeBindAddr , err := parseConfigBindAddress ( c . String ( "edge-bind-address" ) )
if err != nil {
return nil , nil , err
}
if err := testIPBindable ( edgeBindAddr ) ; err != nil {
return nil , nil , fmt . Errorf ( "invalid edge-bind-address %s: %v" , edgeBindAddr , err )
}
edgeIPVersion , err = adjustIPVersionByBindAddress ( edgeIPVersion , edgeBindAddr )
if err != nil {
// This is not a fatal error, we just overrode edgeIPVersion
log . Warn ( ) . Str ( "edgeIPVersion" , edgeIPVersion . String ( ) ) . Err ( err ) . Msg ( "Overriding edge-ip-version" )
}
2020-10-08 10:12:26 +00:00
2022-08-24 12:33:10 +00:00
var pqKexIdx int
if needPQ {
pqKexIdx = mathRand . Intn ( len ( supervisor . PQKexes ) )
log . Info ( ) . Msgf (
"Using experimental hybrid post-quantum key agreement %s" ,
supervisor . PQKexNames [ supervisor . PQKexes [ pqKexIdx ] ] ,
)
}
2022-02-07 09:42:07 +00:00
tunnelConfig := & supervisor . TunnelConfig {
GracePeriod : gracePeriod ,
ReplaceExisting : c . Bool ( "force" ) ,
OSArch : info . OSArch ( ) ,
2023-02-06 17:13:05 +00:00
ClientID : clientID . String ( ) ,
2022-02-07 09:42:07 +00:00
EdgeAddrs : c . StringSlice ( "edge" ) ,
Region : c . String ( "region" ) ,
2022-06-02 17:57:37 +00:00
EdgeIPVersion : edgeIPVersion ,
2023-02-28 16:11:42 +00:00
EdgeBindAddr : edgeBindAddr ,
2023-03-13 10:20:58 +00:00
HAConnections : c . Int ( haConnectionsFlag ) ,
2022-02-07 09:42:07 +00:00
IncidentLookup : supervisor . NewIncidentLookup ( ) ,
IsAutoupdated : c . Bool ( "is-autoupdated" ) ,
LBPool : c . String ( "lb-pool" ) ,
Tags : tags ,
Log : log ,
LogTransport : logTransport ,
Observer : observer ,
ReportedVersion : info . Version ( ) ,
2021-01-14 13:08:55 +00:00
// Note TUN-3758 , we use Int because UInt is not supported with altsrc
2022-12-14 11:43:52 +00:00
Retries : uint ( c . Int ( "retries" ) ) ,
RunFromTerminal : isRunningFromTerminal ( ) ,
NamedTunnel : namedTunnel ,
ProtocolSelector : protocolSelector ,
EdgeTLSConfigs : edgeTLSConfigs ,
NeedPQ : needPQ ,
PQKexIdx : pqKexIdx ,
MaxEdgeAddrRetries : uint8 ( c . Int ( "max-edge-addr-retries" ) ) ,
2022-02-07 09:42:07 +00:00
}
2022-09-20 10:39:51 +00:00
packetConfig , err := newPacketConfig ( c , log )
if err != nil {
log . Warn ( ) . Err ( err ) . Msg ( "ICMP proxy feature is disabled" )
} else {
tunnelConfig . PacketConfig = packetConfig
}
2022-04-27 10:51:06 +00:00
orchestratorConfig := & orchestration . Config {
2022-02-07 09:42:07 +00:00
Ingress : & ingressRules ,
2022-06-13 16:44:27 +00:00
WarpRouting : ingress . NewWarpRoutingConfig ( & cfg . WarpRouting ) ,
2022-04-27 10:51:06 +00:00
ConfigurationFlags : parseConfigFlags ( c ) ,
2022-02-07 09:42:07 +00:00
}
2022-04-27 10:51:06 +00:00
return tunnelConfig , orchestratorConfig , nil
}
func parseConfigFlags ( c * cli . Context ) map [ string ] string {
result := make ( map [ string ] string )
for _ , flag := range configFlags {
if v := c . String ( flag ) ; c . IsSet ( flag ) && v != "" {
result [ flag ] = v
}
}
return result
2018-05-01 23:45:06 +00:00
}
2021-09-21 10:02:59 +00:00
func gracePeriod ( c * cli . Context ) ( time . Duration , error ) {
period := c . Duration ( "grace-period" )
if period > connection . MaxGracePeriod {
return time . Duration ( 0 ) , fmt . Errorf ( "grace-period must be equal or less than %v" , connection . MaxGracePeriod )
}
return period , nil
}
2018-10-08 19:20:28 +00:00
func isRunningFromTerminal ( ) bool {
return terminal . IsTerminal ( int ( os . Stdout . Fd ( ) ) )
}
2021-03-02 22:16:38 +00:00
// Remove any duplicates from the slice
func dedup ( slice [ ] string ) [ ] string {
// Convert the slice into a set
set := make ( map [ string ] bool , 0 )
for _ , str := range slice {
set [ str ] = true
}
// Convert the set back into a slice
keys := make ( [ ] string , len ( set ) )
i := 0
for str := range set {
keys [ i ] = str
i ++
}
return keys
}
2022-06-02 17:57:37 +00:00
// ParseConfigIPVersion returns the IP version from possible expected values from config
func parseConfigIPVersion ( version string ) ( v allregions . ConfigIPVersion , err error ) {
switch version {
case "4" :
v = allregions . IPv4Only
case "6" :
v = allregions . IPv6Only
case "auto" :
v = allregions . Auto
default : // unspecified or invalid
err = fmt . Errorf ( "invalid value for edge-ip-version: %s" , version )
}
return
}
2022-09-20 10:39:51 +00:00
2023-02-28 16:11:42 +00:00
func parseConfigBindAddress ( ipstr string ) ( net . IP , error ) {
// Unspecified - it's fine
if ipstr == "" {
return nil , nil
}
ip := net . ParseIP ( ipstr )
if ip == nil {
return nil , fmt . Errorf ( "invalid value for edge-bind-address: %s" , ipstr )
}
return ip , nil
}
func testIPBindable ( ip net . IP ) error {
// "Unspecified" = let OS choose, so always bindable
if ip == nil {
return nil
}
addr := & net . UDPAddr { IP : ip , Port : 0 }
listener , err := net . ListenUDP ( "udp" , addr )
if err != nil {
return err
}
listener . Close ( )
return nil
}
func adjustIPVersionByBindAddress ( ipVersion allregions . ConfigIPVersion , ip net . IP ) ( allregions . ConfigIPVersion , error ) {
if ip == nil {
return ipVersion , nil
}
// https://pkg.go.dev/net#IP.To4: "If ip is not an IPv4 address, To4 returns nil."
if ip . To4 ( ) != nil {
if ipVersion == allregions . IPv6Only {
return allregions . IPv4Only , fmt . Errorf ( "IPv4 bind address is specified, but edge-ip-version is IPv6" )
}
return allregions . IPv4Only , nil
} else {
if ipVersion == allregions . IPv4Only {
return allregions . IPv6Only , fmt . Errorf ( "IPv6 bind address is specified, but edge-ip-version is IPv4" )
}
return allregions . IPv6Only , nil
}
}
2022-10-13 10:01:25 +00:00
func newPacketConfig ( c * cli . Context , logger * zerolog . Logger ) ( * ingress . GlobalRouterConfig , error ) {
2022-09-20 10:39:51 +00:00
ipv4Src , err := determineICMPv4Src ( c . String ( "icmpv4-src" ) , logger )
if err != nil {
return nil , errors . Wrap ( err , "failed to determine IPv4 source address for ICMP proxy" )
}
logger . Info ( ) . Msgf ( "ICMP proxy will use %s as source for IPv4" , ipv4Src )
ipv6Src , zone , err := determineICMPv6Src ( c . String ( "icmpv6-src" ) , logger , ipv4Src )
if err != nil {
return nil , errors . Wrap ( err , "failed to determine IPv6 source address for ICMP proxy" )
}
if zone != "" {
logger . Info ( ) . Msgf ( "ICMP proxy will use %s in zone %s as source for IPv6" , ipv6Src , zone )
} else {
logger . Info ( ) . Msgf ( "ICMP proxy will use %s as source for IPv6" , ipv6Src )
}
icmpRouter , err := ingress . NewICMPRouter ( ipv4Src , ipv6Src , zone , logger )
if err != nil {
return nil , err
}
2022-10-13 10:01:25 +00:00
return & ingress . GlobalRouterConfig {
2022-09-20 10:39:51 +00:00
ICMPRouter : icmpRouter ,
IPv4Src : ipv4Src ,
IPv6Src : ipv6Src ,
Zone : zone ,
} , nil
}
func determineICMPv4Src ( userDefinedSrc string , logger * zerolog . Logger ) ( netip . Addr , error ) {
if userDefinedSrc != "" {
addr , err := netip . ParseAddr ( userDefinedSrc )
if err != nil {
return netip . Addr { } , err
}
if addr . Is4 ( ) {
return addr , nil
}
return netip . Addr { } , fmt . Errorf ( "expect IPv4, but %s is IPv6" , userDefinedSrc )
}
addr , err := findLocalAddr ( net . ParseIP ( "192.168.0.1" ) , 53 )
if err != nil {
addr = netip . IPv4Unspecified ( )
logger . Debug ( ) . Err ( err ) . Msgf ( "Failed to determine the IPv4 for this machine. It will use %s to send/listen for ICMPv4 echo" , addr )
}
return addr , nil
}
type interfaceIP struct {
name string
ip net . IP
}
func determineICMPv6Src ( userDefinedSrc string , logger * zerolog . Logger , ipv4Src netip . Addr ) ( addr netip . Addr , zone string , err error ) {
if userDefinedSrc != "" {
userDefinedIP , zone , _ := strings . Cut ( userDefinedSrc , "%" )
addr , err := netip . ParseAddr ( userDefinedIP )
if err != nil {
return netip . Addr { } , "" , err
}
if addr . Is6 ( ) {
return addr , zone , nil
}
return netip . Addr { } , "" , fmt . Errorf ( "expect IPv6, but %s is IPv4" , userDefinedSrc )
}
// Loop through all the interfaces, the preference is
// 1. The interface where ipv4Src is in
// 2. Interface with IPv6 address
// 3. Unspecified interface
interfaces , err := net . Interfaces ( )
if err != nil {
return netip . IPv6Unspecified ( ) , "" , nil
}
interfacesWithIPv6 := make ( [ ] interfaceIP , 0 )
for _ , interf := range interfaces {
interfaceAddrs , err := interf . Addrs ( )
if err != nil {
continue
}
foundIPv4SrcInterface := false
for _ , interfaceAddr := range interfaceAddrs {
if ipnet , ok := interfaceAddr . ( * net . IPNet ) ; ok {
ip := ipnet . IP
if ip . Equal ( ipv4Src . AsSlice ( ) ) {
foundIPv4SrcInterface = true
}
if ip . To4 ( ) == nil {
interfacesWithIPv6 = append ( interfacesWithIPv6 , interfaceIP {
name : interf . Name ,
ip : ip ,
} )
}
}
}
// Found the interface of ipv4Src. Loop through the addresses to see if there is an IPv6
if foundIPv4SrcInterface {
for _ , interfaceAddr := range interfaceAddrs {
if ipnet , ok := interfaceAddr . ( * net . IPNet ) ; ok {
ip := ipnet . IP
if ip . To4 ( ) == nil {
addr , err := netip . ParseAddr ( ip . String ( ) )
if err == nil {
return addr , interf . Name , nil
}
}
}
}
}
}
for _ , interf := range interfacesWithIPv6 {
addr , err := netip . ParseAddr ( interf . ip . String ( ) )
if err == nil {
return addr , interf . name , nil
}
}
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 ( ) )
return netip . IPv6Unspecified ( ) , "" , nil
}
// FindLocalAddr tries to dial UDP and returns the local address picked by the OS
func findLocalAddr ( dst net . IP , port int ) ( netip . Addr , error ) {
udpConn , err := net . DialUDP ( "udp" , nil , & net . UDPAddr {
IP : dst ,
Port : port ,
} )
if err != nil {
return netip . Addr { } , err
}
defer udpConn . Close ( )
localAddrPort , err := netip . ParseAddrPort ( udpConn . LocalAddr ( ) . String ( ) )
if err != nil {
return netip . Addr { } , err
}
localAddr := localAddrPort . Addr ( )
return localAddr , nil
}