package scan import ( "bufio" "errors" "fmt" "io" "net" "github.com/cloudflare/cfssl/scan/crypto/tls" ) // Connectivity contains scanners testing basic connectivity to the host var Connectivity = &Family{ Description: "Scans for basic connectivity with the host through DNS and TCP/TLS dials", Scanners: map[string]*Scanner{ "DNSLookup": { "Host can be resolved through DNS", dnsLookupScan, }, "CloudFlareStatus": { "Host is on CloudFlare", onCloudFlareScan, }, "TCPDial": { "Host accepts TCP connection", tcpDialScan, }, "TLSDial": { "Host can perform TLS handshake", tlsDialScan, }, }, } // dnsLookupScan tests that DNS resolution of the host returns at least one address func dnsLookupScan(addr, hostname string) (grade Grade, output Output, err error) { addrs, err := net.LookupHost(hostname) if err != nil { return } if len(addrs) == 0 { err = errors.New("no addresses found for host") } grade, output = Good, addrs return } var ( cfNets []*net.IPNet cfNetsErr error ) func initOnCloudFlareScan() ([]*net.IPNet, error) { // Propagate previous errors and don't attempt to re-download. if cfNetsErr != nil { return nil, cfNetsErr } // Don't re-download ranges if we already have them. if len(cfNets) > 0 { return cfNets, nil } // Download CloudFlare CIDR ranges and parse them. v4resp, err := Client.Get("https://www.cloudflare.com/ips-v4") if err != nil { cfNetsErr = fmt.Errorf("Couldn't download CloudFlare IPs: %v", err) return nil, cfNetsErr } defer v4resp.Body.Close() v6resp, err := Client.Get("https://www.cloudflare.com/ips-v6") if err != nil { cfNetsErr = fmt.Errorf("Couldn't download CloudFlare IPs: %v", err) return nil, cfNetsErr } defer v6resp.Body.Close() scanner := bufio.NewScanner(io.MultiReader(v4resp.Body, v6resp.Body)) for scanner.Scan() { _, ipnet, err := net.ParseCIDR(scanner.Text()) if err != nil { cfNetsErr = fmt.Errorf("Couldn't parse CIDR range: %v", err) return nil, cfNetsErr } cfNets = append(cfNets, ipnet) } if err := scanner.Err(); err != nil { cfNetsErr = fmt.Errorf("Couldn't read IP bodies: %v", err) return nil, cfNetsErr } return cfNets, nil } func onCloudFlareScan(addr, hostname string) (grade Grade, output Output, err error) { var cloudflareNets []*net.IPNet if cloudflareNets, err = initOnCloudFlareScan(); err != nil { grade = Skipped return } _, addrs, err := dnsLookupScan(addr, hostname) if err != nil { return } cfStatus := make(map[string]bool) grade = Good for _, addr := range addrs.([]string) { ip := net.ParseIP(addr) for _, cfNet := range cloudflareNets { if cfNet.Contains(ip) { cfStatus[addr] = true break } } if !cfStatus[addr] { cfStatus[addr] = false grade = Bad } } output = cfStatus return } // tcpDialScan tests that the host can be connected to through TCP. func tcpDialScan(addr, hostname string) (grade Grade, output Output, err error) { conn, err := Dialer.Dial(Network, addr) if err != nil { return } conn.Close() grade = Good return } // tlsDialScan tests that the host can perform a TLS Handshake // and warns if the server's certificate can't be verified. func tlsDialScan(addr, hostname string) (grade Grade, output Output, err error) { var conn *tls.Conn config := defaultTLSConfig(hostname) if conn, err = tls.DialWithDialer(Dialer, Network, addr, config); err != nil { return } conn.Close() config.InsecureSkipVerify = false if conn, err = tls.DialWithDialer(Dialer, Network, addr, config); err != nil { grade = Warning return } conn.Close() grade = Good return }