mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-30 18:22:49 -04:00 
			
		
		
		
	Merge pull request #2079 from mholt/telemetry
Caddy telemetry: a global, server-side perspective of the health of the Internet
This commit is contained in:
		
						commit
						148a6f4430
					
				
							
								
								
									
										6
									
								
								caddy.go
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								caddy.go
									
									
									
									
									
								
							| @ -44,6 +44,7 @@ import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/mholt/caddy/caddyfile" | ||||
| 	"github.com/mholt/caddy/telemetry" | ||||
| ) | ||||
| 
 | ||||
| // Configurable application parameters | ||||
| @ -122,6 +123,7 @@ type Instance struct { | ||||
| 	StorageMu sync.RWMutex | ||||
| } | ||||
| 
 | ||||
| // Instances returns the list of instances. | ||||
| func Instances() []*Instance { | ||||
| 	return instances | ||||
| } | ||||
| @ -615,6 +617,8 @@ func ValidateAndExecuteDirectives(cdyfile Input, inst *Instance, justValidate bo | ||||
| 		return fmt.Errorf("error inspecting server blocks: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	telemetry.Set("num_server_blocks", len(sblocks)) | ||||
| 
 | ||||
| 	return executeDirectives(inst, cdyfile.Path(), stype.Directives(), sblocks, justValidate) | ||||
| } | ||||
| 
 | ||||
| @ -869,7 +873,7 @@ func Stop() error { | ||||
| // explicitly like a common local hostname. addr must only | ||||
| // be a host or a host:port combination. | ||||
| func IsLoopback(addr string) bool { | ||||
| 	host, _, err := net.SplitHostPort(addr) | ||||
| 	host, _, err := net.SplitHostPort(strings.ToLower(addr)) | ||||
| 	if err != nil { | ||||
| 		host = addr // happens if the addr is just a hostname | ||||
| 	} | ||||
|  | ||||
| @ -21,19 +21,20 @@ import ( | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"runtime" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/google/uuid" | ||||
| 	"github.com/klauspost/cpuid" | ||||
| 	"github.com/mholt/caddy" | ||||
| 	"github.com/mholt/caddy/caddytls" | ||||
| 	"github.com/mholt/caddy/telemetry" | ||||
| 	"github.com/xenolf/lego/acmev2" | ||||
| 	"gopkg.in/natefinch/lumberjack.v2" | ||||
| 
 | ||||
| 	"github.com/xenolf/lego/acmev2" | ||||
| 
 | ||||
| 	"github.com/mholt/caddy" | ||||
| 	// plug in the HTTP server type | ||||
| 	_ "github.com/mholt/caddy/caddyhttp" | ||||
| 
 | ||||
| 	"github.com/mholt/caddy/caddytls" | ||||
| 	_ "github.com/mholt/caddy/caddyhttp" // plug in the HTTP server type | ||||
| 	// This is where other plugins get plugged in (imported) | ||||
| ) | ||||
| 
 | ||||
| @ -45,6 +46,7 @@ func init() { | ||||
| 	flag.StringVar(&caddytls.DefaultCAUrl, "ca", "https://acme-v02.api.letsencrypt.org/directory", "URL to certificate authority's ACME server directory") | ||||
| 	flag.BoolVar(&caddytls.DisableHTTPChallenge, "disable-http-challenge", caddytls.DisableHTTPChallenge, "Disable the ACME HTTP challenge") | ||||
| 	flag.BoolVar(&caddytls.DisableTLSSNIChallenge, "disable-tls-sni-challenge", caddytls.DisableTLSSNIChallenge, "Disable the ACME TLS-SNI challenge") | ||||
| 	flag.StringVar(&disabledMetrics, "disabled-metrics", "", "Comma-separated list of telemetry metrics to disable") | ||||
| 	flag.StringVar(&conf, "conf", "", "Caddyfile to load (default \""+caddy.DefaultConfigFile+"\")") | ||||
| 	flag.StringVar(&cpu, "cpu", "100%", "CPU cap") | ||||
| 	flag.BoolVar(&plugins, "plugins", false, "List installed plugins") | ||||
| @ -87,6 +89,16 @@ func Run() { | ||||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	// initialize telemetry client | ||||
| 	if enableTelemetry { | ||||
| 		err := initTelemetry() | ||||
| 		if err != nil { | ||||
| 			mustLogFatalf("[ERROR] Initializing telemetry: %v", err) | ||||
| 		} | ||||
| 	} else if disabledMetrics != "" { | ||||
| 		mustLogFatalf("[ERROR] Cannot disable specific metrics because telemetry is disabled") | ||||
| 	} | ||||
| 
 | ||||
| 	// Check for one-time actions | ||||
| 	if revoke != "" { | ||||
| 		err := caddytls.Revoke(revoke) | ||||
| @ -143,6 +155,23 @@ func Run() { | ||||
| 	// Execute instantiation events | ||||
| 	caddy.EmitEvent(caddy.InstanceStartupEvent, instance) | ||||
| 
 | ||||
| 	// Begin telemetry (these are no-ops if telemetry disabled) | ||||
| 	telemetry.Set("caddy_version", appVersion) | ||||
| 	telemetry.Set("num_listeners", len(instance.Servers())) | ||||
| 	telemetry.Set("server_type", serverType) | ||||
| 	telemetry.Set("os", runtime.GOOS) | ||||
| 	telemetry.Set("arch", runtime.GOARCH) | ||||
| 	telemetry.Set("cpu", struct { | ||||
| 		BrandName  string `json:"brand_name,omitempty"` | ||||
| 		NumLogical int    `json:"num_logical,omitempty"` | ||||
| 		AESNI      bool   `json:"aes_ni,omitempty"` | ||||
| 	}{ | ||||
| 		BrandName:  cpuid.CPU.BrandName, | ||||
| 		NumLogical: runtime.NumCPU(), | ||||
| 		AESNI:      cpuid.CPU.AesNi(), | ||||
| 	}) | ||||
| 	telemetry.StartEmitting() | ||||
| 
 | ||||
| 	// Twiddle your thumbs | ||||
| 	instance.Wait() | ||||
| } | ||||
| @ -266,18 +295,86 @@ func setCPU(cpu string) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // initTelemetry initializes the telemetry engine. | ||||
| func initTelemetry() error { | ||||
| 	uuidFilename := filepath.Join(caddy.AssetsPath(), "uuid") | ||||
| 
 | ||||
| 	newUUID := func() uuid.UUID { | ||||
| 		id := uuid.New() | ||||
| 		err := ioutil.WriteFile(uuidFilename, []byte(id.String()), 0600) // human-readable as a string | ||||
| 		if err != nil { | ||||
| 			log.Printf("[ERROR] Persisting instance UUID: %v", err) | ||||
| 		} | ||||
| 		return id | ||||
| 	} | ||||
| 
 | ||||
| 	var id uuid.UUID | ||||
| 
 | ||||
| 	// load UUID from storage, or create one if we don't have one | ||||
| 	if uuidFile, err := os.Open(uuidFilename); os.IsNotExist(err) { | ||||
| 		// no UUID exists yet; create a new one and persist it | ||||
| 		id = newUUID() | ||||
| 	} else if err != nil { | ||||
| 		log.Printf("[ERROR] Loading persistent UUID: %v", err) | ||||
| 		id = newUUID() | ||||
| 	} else { | ||||
| 		defer uuidFile.Close() | ||||
| 		uuidBytes, err := ioutil.ReadAll(uuidFile) | ||||
| 		if err != nil { | ||||
| 			log.Printf("[ERROR] Reading persistent UUID: %v", err) | ||||
| 			id = newUUID() | ||||
| 		} else { | ||||
| 			id, err = uuid.ParseBytes(uuidBytes) | ||||
| 			if err != nil { | ||||
| 				log.Printf("[ERROR] Parsing UUID: %v", err) | ||||
| 				id = newUUID() | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// parse and check the list of disabled metrics | ||||
| 	var disabledMetricsSlice []string | ||||
| 	if len(disabledMetrics) > 0 { | ||||
| 		if len(disabledMetrics) > 1024 { | ||||
| 			// mitigate disk space exhaustion at the collection endpoint | ||||
| 			return fmt.Errorf("too many metrics to disable") | ||||
| 		} | ||||
| 		disabledMetricsSlice = strings.Split(disabledMetrics, ",") | ||||
| 		for i, metric := range disabledMetricsSlice { | ||||
| 			if metric == "instance_id" || metric == "timestamp" || metric == "disabled_metrics" { | ||||
| 				return fmt.Errorf("instance_id, timestamp, and disabled_metrics cannot be disabled") | ||||
| 			} | ||||
| 			if metric == "" { | ||||
| 				disabledMetricsSlice = append(disabledMetricsSlice[:i], disabledMetricsSlice[i+1:]...) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// initialize telemetry | ||||
| 	telemetry.Init(id, disabledMetricsSlice) | ||||
| 
 | ||||
| 	// if any metrics were disabled, report it | ||||
| 	if len(disabledMetricsSlice) > 0 { | ||||
| 		telemetry.Set("disabled_metrics", disabledMetricsSlice) | ||||
| 		log.Printf("[NOTICE] The following telemetry metrics are disabled: %s", disabledMetrics) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| const appName = "Caddy" | ||||
| 
 | ||||
| // Flags that control program flow or startup | ||||
| var ( | ||||
| 	serverType string | ||||
| 	conf       string | ||||
| 	cpu        string | ||||
| 	logfile    string | ||||
| 	revoke     string | ||||
| 	version    bool | ||||
| 	plugins    bool | ||||
| 	validate   bool | ||||
| 	serverType      string | ||||
| 	conf            string | ||||
| 	cpu             string | ||||
| 	logfile         string | ||||
| 	revoke          string | ||||
| 	version         bool | ||||
| 	plugins         bool | ||||
| 	validate        bool | ||||
| 	disabledMetrics string | ||||
| ) | ||||
| 
 | ||||
| // Build information obtained with the help of -ldflags | ||||
| @ -292,3 +389,5 @@ var ( | ||||
| 	gitShortStat     string // git diff-index --shortstat | ||||
| 	gitFilesModified string // git diff-index --name-only HEAD | ||||
| ) | ||||
| 
 | ||||
| const enableTelemetry = true | ||||
|  | ||||
| @ -20,6 +20,8 @@ import ( | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/mholt/caddy/telemetry" | ||||
| ) | ||||
| 
 | ||||
| // Parse parses the input just enough to group tokens, in | ||||
| @ -374,6 +376,7 @@ func (p *parser) directive() error { | ||||
| 
 | ||||
| 	// The directive itself is appended as a relevant token | ||||
| 	p.block.Tokens[dir] = append(p.block.Tokens[dir], p.tokens[p.cursor]) | ||||
| 	telemetry.AppendUnique("directives", dir) | ||||
| 
 | ||||
| 	for p.Next() { | ||||
| 		if p.Val() == "{" { | ||||
|  | ||||
| @ -24,6 +24,9 @@ import ( | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"github.com/mholt/caddy/caddytls" | ||||
| 	"github.com/mholt/caddy/telemetry" | ||||
| ) | ||||
| 
 | ||||
| // tlsHandler is a http.Handler that will inject a value | ||||
| @ -49,6 +52,9 @@ type tlsHandler struct { | ||||
| // Halderman, et. al. in "The Security Impact of HTTPS Interception" (NDSS '17): | ||||
| // https://jhalderm.com/pub/papers/interception-ndss17.pdf | ||||
| func (h *tlsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 	// TODO: one request per connection, we should report UA in connection with | ||||
| 	// handshake (reported in caddytls package) and our MITM assessment | ||||
| 
 | ||||
| 	if h.listener == nil { | ||||
| 		h.next.ServeHTTP(w, r) | ||||
| 		return | ||||
| @ -60,6 +66,9 @@ func (h *tlsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 
 | ||||
| 	ua := r.Header.Get("User-Agent") | ||||
| 
 | ||||
| 	// report this request's UA in connection with this ClientHello | ||||
| 	go telemetry.AppendUnique("tls_client_hello_ua:"+caddytls.ClientHelloInfo(info).Key(), ua) | ||||
| 
 | ||||
| 	var checked, mitm bool | ||||
| 	if r.Header.Get("X-BlueCoat-Via") != "" || // Blue Coat (masks User-Agent header to generic values) | ||||
| 		r.Header.Get("X-FCCKV2") != "" || // Fortinet | ||||
| @ -97,6 +106,13 @@ func (h *tlsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 
 | ||||
| 	if checked { | ||||
| 		r = r.WithContext(context.WithValue(r.Context(), MitmCtxKey, mitm)) | ||||
| 		if mitm { | ||||
| 			go telemetry.AppendUnique("http_mitm", "likely") | ||||
| 		} else { | ||||
| 			go telemetry.AppendUnique("http_mitm", "unlikely") | ||||
| 		} | ||||
| 	} else { | ||||
| 		go telemetry.AppendUnique("http_mitm", "unknown") | ||||
| 	} | ||||
| 
 | ||||
| 	if mitm && h.closeOnMITM { | ||||
| @ -195,6 +211,11 @@ func (c *clientHelloConn) Read(b []byte) (n int, err error) { | ||||
| 	c.listener.helloInfos[c.Conn.RemoteAddr().String()] = rawParsed | ||||
| 	c.listener.helloInfosMu.Unlock() | ||||
| 
 | ||||
| 	// report this ClientHello to telemetry | ||||
| 	chKey := caddytls.ClientHelloInfo(rawParsed).Key() | ||||
| 	go telemetry.SetNested("tls_client_hello", chKey, rawParsed) | ||||
| 	go telemetry.AppendUnique("tls_client_hello_count", chKey) | ||||
| 
 | ||||
| 	c.readHello = true | ||||
| 	return | ||||
| } | ||||
| @ -215,6 +236,7 @@ func parseRawClientHello(data []byte) (info rawHelloInfo) { | ||||
| 	if len(data) < 42 { | ||||
| 		return | ||||
| 	} | ||||
| 	info.Version = uint16(data[4])<<8 | uint16(data[5]) | ||||
| 	sessionIDLen := int(data[38]) | ||||
| 	if sessionIDLen > 32 || len(data) < 39+sessionIDLen { | ||||
| 		return | ||||
| @ -231,9 +253,9 @@ func parseRawClientHello(data []byte) (info rawHelloInfo) { | ||||
| 	} | ||||
| 	numCipherSuites := cipherSuiteLen / 2 | ||||
| 	// read in the cipher suites | ||||
| 	info.cipherSuites = make([]uint16, numCipherSuites) | ||||
| 	info.CipherSuites = make([]uint16, numCipherSuites) | ||||
| 	for i := 0; i < numCipherSuites; i++ { | ||||
| 		info.cipherSuites[i] = uint16(data[2+2*i])<<8 | uint16(data[3+2*i]) | ||||
| 		info.CipherSuites[i] = uint16(data[2+2*i])<<8 | uint16(data[3+2*i]) | ||||
| 	} | ||||
| 	data = data[2+cipherSuiteLen:] | ||||
| 	if len(data) < 1 { | ||||
| @ -244,7 +266,7 @@ func parseRawClientHello(data []byte) (info rawHelloInfo) { | ||||
| 	if len(data) < 1+compressionMethodsLen { | ||||
| 		return | ||||
| 	} | ||||
| 	info.compressionMethods = data[1 : 1+compressionMethodsLen] | ||||
| 	info.CompressionMethods = data[1 : 1+compressionMethodsLen] | ||||
| 
 | ||||
| 	data = data[1+compressionMethodsLen:] | ||||
| 
 | ||||
| @ -272,7 +294,7 @@ func parseRawClientHello(data []byte) (info rawHelloInfo) { | ||||
| 		} | ||||
| 
 | ||||
| 		// record that the client advertised support for this extension | ||||
| 		info.extensions = append(info.extensions, extension) | ||||
| 		info.Extensions = append(info.Extensions, extension) | ||||
| 
 | ||||
| 		switch extension { | ||||
| 		case extensionSupportedCurves: | ||||
| @ -285,10 +307,10 @@ func parseRawClientHello(data []byte) (info rawHelloInfo) { | ||||
| 				return | ||||
| 			} | ||||
| 			numCurves := l / 2 | ||||
| 			info.curves = make([]tls.CurveID, numCurves) | ||||
| 			info.Curves = make([]tls.CurveID, numCurves) | ||||
| 			d := data[2:] | ||||
| 			for i := 0; i < numCurves; i++ { | ||||
| 				info.curves[i] = tls.CurveID(d[0])<<8 | tls.CurveID(d[1]) | ||||
| 				info.Curves[i] = tls.CurveID(d[0])<<8 | tls.CurveID(d[1]) | ||||
| 				d = d[2:] | ||||
| 			} | ||||
| 		case extensionSupportedPoints: | ||||
| @ -300,8 +322,8 @@ func parseRawClientHello(data []byte) (info rawHelloInfo) { | ||||
| 			if length != l+1 { | ||||
| 				return | ||||
| 			} | ||||
| 			info.points = make([]uint8, l) | ||||
| 			copy(info.points, data[1:]) | ||||
| 			info.Points = make([]uint8, l) | ||||
| 			copy(info.Points, data[1:]) | ||||
| 		} | ||||
| 
 | ||||
| 		data = data[length:] | ||||
| @ -352,18 +374,12 @@ func (l *tlsHelloListener) Accept() (net.Conn, error) { | ||||
| // by Durumeric, Halderman, et. al. in | ||||
| // "The Security Impact of HTTPS Interception": | ||||
| // https://jhalderm.com/pub/papers/interception-ndss17.pdf | ||||
| type rawHelloInfo struct { | ||||
| 	cipherSuites       []uint16 | ||||
| 	extensions         []uint16 | ||||
| 	compressionMethods []byte | ||||
| 	curves             []tls.CurveID | ||||
| 	points             []uint8 | ||||
| } | ||||
| type rawHelloInfo caddytls.ClientHelloInfo | ||||
| 
 | ||||
| // advertisesHeartbeatSupport returns true if info indicates | ||||
| // that the client supports the Heartbeat extension. | ||||
| func (info rawHelloInfo) advertisesHeartbeatSupport() bool { | ||||
| 	for _, ext := range info.extensions { | ||||
| 	for _, ext := range info.Extensions { | ||||
| 		if ext == extensionHeartbeat { | ||||
| 			return true | ||||
| 		} | ||||
| @ -386,31 +402,31 @@ func (info rawHelloInfo) looksLikeFirefox() bool { | ||||
| 	// Note: Firefox 55+ doesn't appear to advertise 0xFF03 (65283, short headers). It used to be between 5 and 13. | ||||
| 	// Note: Firefox on Fedora (or RedHat) doesn't include ECC suites because of patent liability. | ||||
| 	requiredExtensionsOrder := []uint16{23, 65281, 10, 11, 35, 16, 5, 13} | ||||
| 	if !assertPresenceAndOrdering(requiredExtensionsOrder, info.extensions, true) { | ||||
| 	if !assertPresenceAndOrdering(requiredExtensionsOrder, info.Extensions, true) { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	// We check for both presence of curves and their ordering. | ||||
| 	requiredCurves := []tls.CurveID{29, 23, 24, 25} | ||||
| 	if len(info.curves) < len(requiredCurves) { | ||||
| 	if len(info.Curves) < len(requiredCurves) { | ||||
| 		return false | ||||
| 	} | ||||
| 	for i := range requiredCurves { | ||||
| 		if info.curves[i] != requiredCurves[i] { | ||||
| 		if info.Curves[i] != requiredCurves[i] { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	if len(info.curves) > len(requiredCurves) { | ||||
| 	if len(info.Curves) > len(requiredCurves) { | ||||
| 		// newer Firefox (55 Nightly?) may have additional curves at end of list | ||||
| 		allowedCurves := []tls.CurveID{256, 257} | ||||
| 		for i := range allowedCurves { | ||||
| 			if info.curves[len(requiredCurves)+i] != allowedCurves[i] { | ||||
| 			if info.Curves[len(requiredCurves)+i] != allowedCurves[i] { | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if hasGreaseCiphers(info.cipherSuites) { | ||||
| 	if hasGreaseCiphers(info.CipherSuites) { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| @ -437,7 +453,7 @@ func (info rawHelloInfo) looksLikeFirefox() bool { | ||||
| 		tls.TLS_RSA_WITH_AES_256_CBC_SHA,            // 0x35 | ||||
| 		tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,           // 0xa | ||||
| 	} | ||||
| 	return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.cipherSuites, false) | ||||
| 	return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.CipherSuites, false) | ||||
| } | ||||
| 
 | ||||
| // looksLikeChrome returns true if info looks like a handshake | ||||
| @ -478,20 +494,20 @@ func (info rawHelloInfo) looksLikeChrome() bool { | ||||
| 		TLS_DHE_RSA_WITH_AES_128_CBC_SHA:            {}, // 0x33 | ||||
| 		TLS_DHE_RSA_WITH_AES_256_CBC_SHA:            {}, // 0x39 | ||||
| 	} | ||||
| 	for _, ext := range info.cipherSuites { | ||||
| 	for _, ext := range info.CipherSuites { | ||||
| 		if _, ok := chromeCipherExclusions[ext]; ok { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Chrome does not include curve 25 (CurveP521) (as of Chrome 56, Feb. 2017). | ||||
| 	for _, curve := range info.curves { | ||||
| 	for _, curve := range info.Curves { | ||||
| 		if curve == 25 { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if !hasGreaseCiphers(info.cipherSuites) { | ||||
| 	if !hasGreaseCiphers(info.CipherSuites) { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| @ -509,19 +525,19 @@ func (info rawHelloInfo) looksLikeEdge() bool { | ||||
| 	// More specifically, the OCSP status request extension appears | ||||
| 	// *directly* before the other two extensions, which occur in that | ||||
| 	// order. (I contacted the authors for clarification and verified it.) | ||||
| 	for i, ext := range info.extensions { | ||||
| 	for i, ext := range info.Extensions { | ||||
| 		if ext == extensionOCSPStatusRequest { | ||||
| 			if len(info.extensions) <= i+2 { | ||||
| 			if len(info.Extensions) <= i+2 { | ||||
| 				return false | ||||
| 			} | ||||
| 			if info.extensions[i+1] != extensionSupportedCurves || | ||||
| 				info.extensions[i+2] != extensionSupportedPoints { | ||||
| 			if info.Extensions[i+1] != extensionSupportedCurves || | ||||
| 				info.Extensions[i+2] != extensionSupportedPoints { | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for _, cs := range info.cipherSuites { | ||||
| 	for _, cs := range info.CipherSuites { | ||||
| 		// As of Feb. 2017, Edge does not have 0xff, but Avast adds it | ||||
| 		if cs == scsvRenegotiation { | ||||
| 			return false | ||||
| @ -532,7 +548,7 @@ func (info rawHelloInfo) looksLikeEdge() bool { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if hasGreaseCiphers(info.cipherSuites) { | ||||
| 	if hasGreaseCiphers(info.CipherSuites) { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| @ -558,23 +574,23 @@ func (info rawHelloInfo) looksLikeSafari() bool { | ||||
| 
 | ||||
| 	// We check for the presence and order of the extensions. | ||||
| 	requiredExtensionsOrder := []uint16{10, 11, 13, 13172, 16, 5, 18, 23} | ||||
| 	if !assertPresenceAndOrdering(requiredExtensionsOrder, info.extensions, true) { | ||||
| 	if !assertPresenceAndOrdering(requiredExtensionsOrder, info.Extensions, true) { | ||||
| 		// Safari on iOS 11 (beta) uses different set/ordering of extensions | ||||
| 		requiredExtensionsOrderiOS11 := []uint16{65281, 0, 23, 13, 5, 13172, 18, 16, 11, 10} | ||||
| 		if !assertPresenceAndOrdering(requiredExtensionsOrderiOS11, info.extensions, true) { | ||||
| 		if !assertPresenceAndOrdering(requiredExtensionsOrderiOS11, info.Extensions, true) { | ||||
| 			return false | ||||
| 		} | ||||
| 	} else { | ||||
| 		// For these versions of Safari, expect TLS_EMPTY_RENEGOTIATION_INFO_SCSV first. | ||||
| 		if len(info.cipherSuites) < 1 { | ||||
| 		if len(info.CipherSuites) < 1 { | ||||
| 			return false | ||||
| 		} | ||||
| 		if info.cipherSuites[0] != scsvRenegotiation { | ||||
| 		if info.CipherSuites[0] != scsvRenegotiation { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if hasGreaseCiphers(info.cipherSuites) { | ||||
| 	if hasGreaseCiphers(info.CipherSuites) { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| @ -599,19 +615,19 @@ func (info rawHelloInfo) looksLikeSafari() bool { | ||||
| 		tls.TLS_RSA_WITH_AES_256_CBC_SHA,            // 0x35 | ||||
| 		tls.TLS_RSA_WITH_AES_128_CBC_SHA,            // 0x2f | ||||
| 	} | ||||
| 	return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.cipherSuites, true) | ||||
| 	return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.CipherSuites, true) | ||||
| } | ||||
| 
 | ||||
| // looksLikeTor returns true if the info looks like a ClientHello from Tor browser | ||||
| // (based on Firefox). | ||||
| func (info rawHelloInfo) looksLikeTor() bool { | ||||
| 	requiredExtensionsOrder := []uint16{10, 11, 16, 5, 13} | ||||
| 	if !assertPresenceAndOrdering(requiredExtensionsOrder, info.extensions, true) { | ||||
| 	if !assertPresenceAndOrdering(requiredExtensionsOrder, info.Extensions, true) { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	// check for session tickets support; Tor doesn't support them to prevent tracking | ||||
| 	for _, ext := range info.extensions { | ||||
| 	for _, ext := range info.Extensions { | ||||
| 		if ext == 35 { | ||||
| 			return false | ||||
| 		} | ||||
| @ -619,12 +635,12 @@ func (info rawHelloInfo) looksLikeTor() bool { | ||||
| 
 | ||||
| 	// We check for both presence of curves and their ordering, including | ||||
| 	// an optional curve at the beginning (for Tor based on Firefox 52) | ||||
| 	infoCurves := info.curves | ||||
| 	if len(info.curves) == 4 { | ||||
| 		if info.curves[0] != 29 { | ||||
| 	infoCurves := info.Curves | ||||
| 	if len(info.Curves) == 4 { | ||||
| 		if info.Curves[0] != 29 { | ||||
| 			return false | ||||
| 		} | ||||
| 		infoCurves = info.curves[1:] | ||||
| 		infoCurves = info.Curves[1:] | ||||
| 	} | ||||
| 	requiredCurves := []tls.CurveID{23, 24, 25} | ||||
| 	if len(infoCurves) < len(requiredCurves) { | ||||
| @ -636,7 +652,7 @@ func (info rawHelloInfo) looksLikeTor() bool { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if hasGreaseCiphers(info.cipherSuites) { | ||||
| 	if hasGreaseCiphers(info.CipherSuites) { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| @ -663,7 +679,7 @@ func (info rawHelloInfo) looksLikeTor() bool { | ||||
| 		tls.TLS_RSA_WITH_AES_256_CBC_SHA,            // 0x35 | ||||
| 		tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,           // 0xa | ||||
| 	} | ||||
| 	return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.cipherSuites, false) | ||||
| 	return assertPresenceAndOrdering(expectedCipherSuiteOrder, info.CipherSuites, false) | ||||
| } | ||||
| 
 | ||||
| // assertPresenceAndOrdering will return true if candidateList contains | ||||
|  | ||||
| @ -32,44 +32,48 @@ func TestParseClientHello(t *testing.T) { | ||||
| 			// curl 7.51.0 (x86_64-apple-darwin16.0) libcurl/7.51.0 SecureTransport zlib/1.2.8 | ||||
| 			inputHex: `010000a6030358a28c73a71bdfc1f09dee13fecdc58805dcce42ac44254df548f14645f7dc2c00004400ffc02cc02bc024c023c00ac009c008c030c02fc028c027c014c013c012009f009e006b0067003900330016009d009c003d003c0035002f000a00af00ae008d008c008b01000039000a00080006001700180019000b00020100000d00120010040102010501060104030203050306030005000501000000000012000000170000`, | ||||
| 			expected: rawHelloInfo{ | ||||
| 				cipherSuites:       []uint16{255, 49196, 49195, 49188, 49187, 49162, 49161, 49160, 49200, 49199, 49192, 49191, 49172, 49171, 49170, 159, 158, 107, 103, 57, 51, 22, 157, 156, 61, 60, 53, 47, 10, 175, 174, 141, 140, 139}, | ||||
| 				extensions:         []uint16{10, 11, 13, 5, 18, 23}, | ||||
| 				compressionMethods: []byte{0}, | ||||
| 				curves:             []tls.CurveID{23, 24, 25}, | ||||
| 				points:             []uint8{0}, | ||||
| 				Version:            0x303, | ||||
| 				CipherSuites:       []uint16{255, 49196, 49195, 49188, 49187, 49162, 49161, 49160, 49200, 49199, 49192, 49191, 49172, 49171, 49170, 159, 158, 107, 103, 57, 51, 22, 157, 156, 61, 60, 53, 47, 10, 175, 174, 141, 140, 139}, | ||||
| 				Extensions:         []uint16{10, 11, 13, 5, 18, 23}, | ||||
| 				CompressionMethods: []byte{0}, | ||||
| 				Curves:             []tls.CurveID{23, 24, 25}, | ||||
| 				Points:             []uint8{0}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			// Chrome 56 | ||||
| 			inputHex: `010000c003031dae75222dae1433a5a283ddcde8ddabaefbf16d84f250eee6fdff48cdfff8a00000201a1ac02bc02fc02cc030cca9cca8cc14cc13c013c014009c009d002f0035000a010000777a7a0000ff010001000000000e000c0000096c6f63616c686f73740017000000230000000d00140012040308040401050308050501080606010201000500050100000000001200000010000e000c02683208687474702f312e3175500000000b00020100000a000a0008aaaa001d001700182a2a000100`, | ||||
| 			expected: rawHelloInfo{ | ||||
| 				cipherSuites:       []uint16{6682, 49195, 49199, 49196, 49200, 52393, 52392, 52244, 52243, 49171, 49172, 156, 157, 47, 53, 10}, | ||||
| 				extensions:         []uint16{31354, 65281, 0, 23, 35, 13, 5, 18, 16, 30032, 11, 10, 10794}, | ||||
| 				compressionMethods: []byte{0}, | ||||
| 				curves:             []tls.CurveID{43690, 29, 23, 24}, | ||||
| 				points:             []uint8{0}, | ||||
| 				Version:            0x303, | ||||
| 				CipherSuites:       []uint16{6682, 49195, 49199, 49196, 49200, 52393, 52392, 52244, 52243, 49171, 49172, 156, 157, 47, 53, 10}, | ||||
| 				Extensions:         []uint16{31354, 65281, 0, 23, 35, 13, 5, 18, 16, 30032, 11, 10, 10794}, | ||||
| 				CompressionMethods: []byte{0}, | ||||
| 				Curves:             []tls.CurveID{43690, 29, 23, 24}, | ||||
| 				Points:             []uint8{0}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			// Firefox 51 | ||||
| 			inputHex: `010000bd030375f9022fc3a6562467f3540d68013b2d0b961979de6129e944efe0b35531323500001ec02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a010000760000000e000c0000096c6f63616c686f737400170000ff01000100000a000a0008001d001700180019000b00020100002300000010000e000c02683208687474702f312e31000500050100000000ff030000000d0020001e040305030603020308040805080604010501060102010402050206020202`, | ||||
| 			expected: rawHelloInfo{ | ||||
| 				cipherSuites:       []uint16{49195, 49199, 52393, 52392, 49196, 49200, 49162, 49161, 49171, 49172, 51, 57, 47, 53, 10}, | ||||
| 				extensions:         []uint16{0, 23, 65281, 10, 11, 35, 16, 5, 65283, 13}, | ||||
| 				compressionMethods: []byte{0}, | ||||
| 				curves:             []tls.CurveID{29, 23, 24, 25}, | ||||
| 				points:             []uint8{0}, | ||||
| 				Version:            0x303, | ||||
| 				CipherSuites:       []uint16{49195, 49199, 52393, 52392, 49196, 49200, 49162, 49161, 49171, 49172, 51, 57, 47, 53, 10}, | ||||
| 				Extensions:         []uint16{0, 23, 65281, 10, 11, 35, 16, 5, 65283, 13}, | ||||
| 				CompressionMethods: []byte{0}, | ||||
| 				Curves:             []tls.CurveID{29, 23, 24, 25}, | ||||
| 				Points:             []uint8{0}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			// openssl s_client (OpenSSL 0.9.8zh 14 Jan 2016) | ||||
| 			inputHex: `0100012b03035d385236b8ca7b7946fa0336f164e76bf821ed90e8de26d97cc677671b6f36380000acc030c02cc028c024c014c00a00a500a300a1009f006b006a0069006800390038003700360088008700860085c032c02ec02ac026c00fc005009d003d00350084c02fc02bc027c023c013c00900a400a200a0009e00670040003f003e0033003200310030009a0099009800970045004400430042c031c02dc029c025c00ec004009c003c002f009600410007c011c007c00cc00200050004c012c008001600130010000dc00dc003000a00ff0201000055000b000403000102000a001c001a00170019001c001b0018001a0016000e000d000b000c0009000a00230000000d0020001e060106020603050105020503040104020403030103020303020102020203000f000101`, | ||||
| 			expected: rawHelloInfo{ | ||||
| 				cipherSuites:       []uint16{49200, 49196, 49192, 49188, 49172, 49162, 165, 163, 161, 159, 107, 106, 105, 104, 57, 56, 55, 54, 136, 135, 134, 133, 49202, 49198, 49194, 49190, 49167, 49157, 157, 61, 53, 132, 49199, 49195, 49191, 49187, 49171, 49161, 164, 162, 160, 158, 103, 64, 63, 62, 51, 50, 49, 48, 154, 153, 152, 151, 69, 68, 67, 66, 49201, 49197, 49193, 49189, 49166, 49156, 156, 60, 47, 150, 65, 7, 49169, 49159, 49164, 49154, 5, 4, 49170, 49160, 22, 19, 16, 13, 49165, 49155, 10, 255}, | ||||
| 				extensions:         []uint16{11, 10, 35, 13, 15}, | ||||
| 				compressionMethods: []byte{1, 0}, | ||||
| 				curves:             []tls.CurveID{23, 25, 28, 27, 24, 26, 22, 14, 13, 11, 12, 9, 10}, | ||||
| 				points:             []uint8{0, 1, 2}, | ||||
| 				Version:            0x303, | ||||
| 				CipherSuites:       []uint16{49200, 49196, 49192, 49188, 49172, 49162, 165, 163, 161, 159, 107, 106, 105, 104, 57, 56, 55, 54, 136, 135, 134, 133, 49202, 49198, 49194, 49190, 49167, 49157, 157, 61, 53, 132, 49199, 49195, 49191, 49187, 49171, 49161, 164, 162, 160, 158, 103, 64, 63, 62, 51, 50, 49, 48, 154, 153, 152, 151, 69, 68, 67, 66, 49201, 49197, 49193, 49189, 49166, 49156, 156, 60, 47, 150, 65, 7, 49169, 49159, 49164, 49154, 5, 4, 49170, 49160, 22, 19, 16, 13, 49165, 49155, 10, 255}, | ||||
| 				Extensions:         []uint16{11, 10, 35, 13, 15}, | ||||
| 				CompressionMethods: []byte{1, 0}, | ||||
| 				Curves:             []tls.CurveID{23, 25, 28, 27, 24, 26, 22, 14, 13, 11, 12, 9, 10}, | ||||
| 				Points:             []uint8{0, 1, 2}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} { | ||||
| @ -338,8 +342,8 @@ func TestHeuristicFunctionsAndHandler(t *testing.T) { | ||||
| 				(isEdge && (isChrome || isFirefox || isSafari || isTor)) || | ||||
| 				(isTor && (isChrome || isFirefox || isSafari || isEdge)) { | ||||
| 				t.Errorf("[%s] Test %d: Multiple fingerprinting functions matched: "+ | ||||
| 					"Chrome=%v Firefox=%v Safari=%v Edge=%v Tor=%v\n\tparsed hello dec: %+v\n\tparsed hello hex: %#x\n", | ||||
| 					client, i, isChrome, isFirefox, isSafari, isEdge, isTor, parsed, parsed) | ||||
| 					"Chrome=%v Firefox=%v Safari=%v Edge=%v Tor=%v\n\tparsed hello dec: %+v\n", | ||||
| 					client, i, isChrome, isFirefox, isSafari, isEdge, isTor, parsed) | ||||
| 			} | ||||
| 
 | ||||
| 			// test the handler and detection results | ||||
| @ -367,8 +371,8 @@ func TestHeuristicFunctionsAndHandler(t *testing.T) { | ||||
| 			if got != want { | ||||
| 				t.Errorf("[%s] Test %d: Expected MITM=%v but got %v (type assertion OK (checked)=%v)", | ||||
| 					client, i, want, got, checked) | ||||
| 				t.Errorf("[%s] Test %d: Looks like Chrome=%v Firefox=%v Safari=%v Edge=%v Tor=%v\n\tparsed hello dec: %+v\n\tparsed hello hex: %#x\n", | ||||
| 					client, i, isChrome, isFirefox, isSafari, isEdge, isTor, parsed, parsed) | ||||
| 				t.Errorf("[%s] Test %d: Looks like Chrome=%v Firefox=%v Safari=%v Edge=%v Tor=%v\n\tparsed hello dec: %+v\n", | ||||
| 					client, i, isChrome, isFirefox, isSafari, isEdge, isTor, parsed) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @ -30,6 +30,7 @@ import ( | ||||
| 	"github.com/mholt/caddy/caddyfile" | ||||
| 	"github.com/mholt/caddy/caddyhttp/staticfiles" | ||||
| 	"github.com/mholt/caddy/caddytls" | ||||
| 	"github.com/mholt/caddy/telemetry" | ||||
| ) | ||||
| 
 | ||||
| const serverType = "http" | ||||
| @ -66,6 +67,12 @@ func init() { | ||||
| 	caddy.RegisterParsingCallback(serverType, "root", hideCaddyfile) | ||||
| 	caddy.RegisterParsingCallback(serverType, "tls", activateHTTPS) | ||||
| 	caddytls.RegisterConfigGetter(serverType, func(c *caddy.Controller) *caddytls.Config { return GetConfig(c).TLS }) | ||||
| 
 | ||||
| 	// disable the caddytls package reporting ClientHellos | ||||
| 	// to telemetry, since our MITM detector does this but | ||||
| 	// with more information than the standard lib provides | ||||
| 	// (as of May 2018) | ||||
| 	caddytls.ClientHelloTelemetry = false | ||||
| } | ||||
| 
 | ||||
| // hideCaddyfile hides the source/origin Caddyfile if it is within the | ||||
| @ -208,6 +215,18 @@ func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []cadd | ||||
| // MakeServers uses the newly-created siteConfigs to | ||||
| // create and return a list of server instances. | ||||
| func (h *httpContext) MakeServers() ([]caddy.Server, error) { | ||||
| 	// make a rough estimate as to whether we're in a "production | ||||
| 	// environment/system" - start by assuming that most production | ||||
| 	// servers will set their default CA endpoint to a public, | ||||
| 	// trusted CA (obviously not a perfect hueristic) | ||||
| 	var looksLikeProductionCA bool | ||||
| 	for _, publicCAEndpoint := range caddytls.KnownACMECAs { | ||||
| 		if strings.Contains(caddytls.DefaultCAUrl, publicCAEndpoint) { | ||||
| 			looksLikeProductionCA = true | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Iterate each site configuration and make sure that: | ||||
| 	// 1) TLS is disabled for explicitly-HTTP sites (necessary | ||||
| 	//    when an HTTP address shares a block containing tls) | ||||
| @ -215,7 +234,22 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) { | ||||
| 	//    currently, QUIC does not support ClientAuth (TODO: | ||||
| 	//    revisit this when our QUIC implementation supports it) | ||||
| 	// 3) if TLS ClientAuth is used, StrictHostMatching is on | ||||
| 	var atLeastOneSiteLooksLikeProduction bool | ||||
| 	for _, cfg := range h.siteConfigs { | ||||
| 		// see if all the addresses (both sites and | ||||
| 		// listeners) are loopback to help us determine | ||||
| 		// if this is a "production" instance or not | ||||
| 		if !atLeastOneSiteLooksLikeProduction { | ||||
| 			if !caddy.IsLoopback(cfg.Addr.Host) && | ||||
| 				!caddy.IsLoopback(cfg.ListenHost) && | ||||
| 				(caddytls.QualifiesForManagedTLS(cfg) || | ||||
| 					caddytls.HostQualifies(cfg.Addr.Host)) { | ||||
| 				atLeastOneSiteLooksLikeProduction = true | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// make sure TLS is disabled for explicitly-HTTP sites | ||||
| 		// (necessary when HTTP address shares a block containing tls) | ||||
| 		if !cfg.TLS.Enabled { | ||||
| 			continue | ||||
| 		} | ||||
| @ -265,6 +299,18 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) { | ||||
| 		servers = append(servers, s) | ||||
| 	} | ||||
| 
 | ||||
| 	// NOTE: This value is only a "good guess". Quite often, development | ||||
| 	// environments will use internal DNS or a local hosts file to serve | ||||
| 	// real-looking domains in local development. We can't easily tell | ||||
| 	// which without doing a DNS lookup, so this guess is definitely naive, | ||||
| 	// and if we ever want a better guess, we will have to do DNS lookups. | ||||
| 	deploymentGuess := "dev" | ||||
| 	if looksLikeProductionCA && atLeastOneSiteLooksLikeProduction { | ||||
| 		deploymentGuess = "prod" | ||||
| 	} | ||||
| 	telemetry.Set("http_deployment_guess", deploymentGuess) | ||||
| 	telemetry.Set("http_num_sites", len(h.siteConfigs)) | ||||
| 
 | ||||
| 	return servers, nil | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -36,6 +36,7 @@ import ( | ||||
| 	"github.com/mholt/caddy" | ||||
| 	"github.com/mholt/caddy/caddyhttp/staticfiles" | ||||
| 	"github.com/mholt/caddy/caddytls" | ||||
| 	"github.com/mholt/caddy/telemetry" | ||||
| ) | ||||
| 
 | ||||
| // Server is the HTTP server implementation. | ||||
| @ -348,6 +349,14 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	// record the User-Agent string (with a cap on its length to mitigate attacks) | ||||
| 	ua := r.Header.Get("User-Agent") | ||||
| 	if len(ua) > 512 { | ||||
| 		ua = ua[:512] | ||||
| 	} | ||||
| 	go telemetry.AppendUnique("http_user_agent", ua) | ||||
| 	go telemetry.Increment("http_request_count") | ||||
| 
 | ||||
| 	// copy the original, unchanged URL into the context | ||||
| 	// so it can be referenced by middlewares | ||||
| 	urlCopy := *r.URL | ||||
|  | ||||
| @ -26,6 +26,7 @@ import ( | ||||
| 	"sync" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/mholt/caddy/telemetry" | ||||
| 	"golang.org/x/crypto/ocsp" | ||||
| ) | ||||
| 
 | ||||
| @ -165,6 +166,7 @@ func (cfg *Config) CacheManagedCertificate(domain string) (Certificate, error) { | ||||
| 	if err != nil { | ||||
| 		return cert, err | ||||
| 	} | ||||
| 	telemetry.Increment("tls_managed_cert_count") | ||||
| 	return cfg.cacheCertificate(cert), nil | ||||
| } | ||||
| 
 | ||||
| @ -179,6 +181,7 @@ func (cfg *Config) cacheUnmanagedCertificatePEMFile(certFile, keyFile string) er | ||||
| 		return err | ||||
| 	} | ||||
| 	cfg.cacheCertificate(cert) | ||||
| 	telemetry.Increment("tls_manual_cert_count") | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| @ -192,6 +195,7 @@ func (cfg *Config) cacheUnmanagedCertificatePEMBytes(certBytes, keyBytes []byte) | ||||
| 		return err | ||||
| 	} | ||||
| 	cfg.cacheCertificate(cert) | ||||
| 	telemetry.Increment("tls_manual_cert_count") | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -26,6 +26,7 @@ import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/mholt/caddy" | ||||
| 	"github.com/mholt/caddy/telemetry" | ||||
| 	"github.com/xenolf/lego/acmev2" | ||||
| ) | ||||
| 
 | ||||
| @ -273,6 +274,8 @@ func (c *ACMEClient) Obtain(name string) error { | ||||
| 		break | ||||
| 	} | ||||
| 
 | ||||
| 	go telemetry.Increment("tls_acme_certs_obtained") | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| @ -340,6 +343,7 @@ func (c *ACMEClient) Renew(name string) error { | ||||
| 	} | ||||
| 
 | ||||
| 	caddy.EmitEvent(caddy.CertRenewEvent, name) | ||||
| 	go telemetry.Increment("tls_acme_certs_renewed") | ||||
| 
 | ||||
| 	return saveCertResource(c.storage, newCertMeta) | ||||
| } | ||||
| @ -366,6 +370,8 @@ func (c *ACMEClient) Revoke(name string) error { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	go telemetry.Increment("tls_acme_certs_revoked") | ||||
| 
 | ||||
| 	err = c.storage.DeleteSite(name) | ||||
| 	if err != nil { | ||||
| 		return errors.New("certificate revoked, but unable to delete certificate file: " + err.Error()) | ||||
| @ -417,3 +423,10 @@ func (c *nameCoordinator) Has(name string) bool { | ||||
| 	c.mu.RUnlock() | ||||
| 	return ok | ||||
| } | ||||
| 
 | ||||
| // KnownACMECAs is a list of ACME directory endpoints of | ||||
| // known, public, and trusted ACME-compatible certificate | ||||
| // authorities. | ||||
| var KnownACMECAs = []string{ | ||||
| 	"https://acme-v02.api.letsencrypt.org/directory", | ||||
| } | ||||
|  | ||||
| @ -23,7 +23,7 @@ import ( | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/codahale/aesnicheck" | ||||
| 	"github.com/klauspost/cpuid" | ||||
| 	"github.com/mholt/caddy" | ||||
| 	"github.com/xenolf/lego/acmev2" | ||||
| ) | ||||
| @ -648,7 +648,7 @@ var defaultCiphersNonAESNI = []uint16{ | ||||
| // | ||||
| // See https://github.com/mholt/caddy/issues/1674 | ||||
| func getPreferredDefaultCiphers() []uint16 { | ||||
| 	if aesnicheck.HasAESNI() { | ||||
| 	if cpuid.CPU.AesNi() { | ||||
| 		return defaultCiphers | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -21,7 +21,7 @@ import ( | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/codahale/aesnicheck" | ||||
| 	"github.com/klauspost/cpuid" | ||||
| ) | ||||
| 
 | ||||
| func TestConvertTLSConfigProtocolVersions(t *testing.T) { | ||||
| @ -98,7 +98,7 @@ func TestConvertTLSConfigCipherSuites(t *testing.T) { | ||||
| 
 | ||||
| func TestGetPreferredDefaultCiphers(t *testing.T) { | ||||
| 	expectedCiphers := defaultCiphers | ||||
| 	if !aesnicheck.HasAESNI() { | ||||
| 	if !cpuid.CPU.AesNi() { | ||||
| 		expectedCiphers = defaultCiphersNonAESNI | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -341,7 +341,7 @@ func standaloneTLSTicketKeyRotation(c *tls.Config, ticker *time.Ticker, exitChan | ||||
| // Do not use this for cryptographic purposes. | ||||
| func fastHash(input []byte) string { | ||||
| 	h := fnv.New32a() | ||||
| 	h.Write([]byte(input)) | ||||
| 	h.Write(input) | ||||
| 	return fmt.Sprintf("%x", h.Sum32()) | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -25,6 +25,8 @@ import ( | ||||
| 	"sync" | ||||
| 	"sync/atomic" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/mholt/caddy/telemetry" | ||||
| ) | ||||
| 
 | ||||
| // configGroup is a type that keys configs by their hostname | ||||
| @ -97,7 +99,27 @@ func (cg configGroup) GetConfigForClient(clientHello *tls.ClientHelloInfo) (*tls | ||||
| // | ||||
| // This method is safe for use as a tls.Config.GetCertificate callback. | ||||
| func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { | ||||
| 	if ClientHelloTelemetry && len(clientHello.SupportedVersions) > 0 { | ||||
| 		// If no other plugin (such as the HTTP server type) is implementing ClientHello telemetry, we do it. | ||||
| 		// NOTE: The values in the Go standard lib's ClientHelloInfo aren't guaranteed to be in order. | ||||
| 		info := ClientHelloInfo{ | ||||
| 			Version:                   clientHello.SupportedVersions[0], // report the highest | ||||
| 			CipherSuites:              clientHello.CipherSuites, | ||||
| 			ExtensionsUnknown:         true, // no extension info... :( | ||||
| 			CompressionMethodsUnknown: true, // no compression methods... :( | ||||
| 			Curves: clientHello.SupportedCurves, | ||||
| 			Points: clientHello.SupportedPoints, | ||||
| 			// We also have, but do not yet use: SignatureSchemes, ServerName, and SupportedProtos (ALPN) | ||||
| 			// because the standard lib parses some extensions, but our MITM detector generally doesn't. | ||||
| 		} | ||||
| 		go telemetry.SetNested("tls_client_hello", info.Key(), info) | ||||
| 	} | ||||
| 
 | ||||
| 	// get the certificate and serve it up | ||||
| 	cert, err := cfg.getCertDuringHandshake(strings.ToLower(clientHello.ServerName), true, true) | ||||
| 	if err == nil { | ||||
| 		go telemetry.Increment("tls_handshake_count") // TODO: This is a "best guess" for now, we need something listener-level | ||||
| 	} | ||||
| 	return &cert.Certificate, err | ||||
| } | ||||
| 
 | ||||
| @ -463,6 +485,42 @@ func (cfg *Config) renewDynamicCertificate(name string, currentCert Certificate) | ||||
| 	return cfg.getCertDuringHandshake(name, true, false) | ||||
| } | ||||
| 
 | ||||
| // ClientHelloInfo is our own version of the standard lib's | ||||
| // tls.ClientHelloInfo. As of May 2018, any fields populated | ||||
| // by the Go standard library are not guaranteed to have their | ||||
| // values in the original order as on the wire. | ||||
| type ClientHelloInfo struct { | ||||
| 	Version            uint16        `json:"version,omitempty"` | ||||
| 	CipherSuites       []uint16      `json:"cipher_suites,omitempty"` | ||||
| 	Extensions         []uint16      `json:"extensions,omitempty"` | ||||
| 	CompressionMethods []byte        `json:"compression,omitempty"` | ||||
| 	Curves             []tls.CurveID `json:"curves,omitempty"` | ||||
| 	Points             []uint8       `json:"points,omitempty"` | ||||
| 
 | ||||
| 	// Whether a couple of fields are unknown; if not, the key will encode | ||||
| 	// differently to reflect that, as opposed to being known empty values. | ||||
| 	// (some fields may be unknown depending on what package is being used; | ||||
| 	// i.e. the Go standard lib doesn't expose some things) | ||||
| 	// (very important to NOT encode these to JSON) | ||||
| 	ExtensionsUnknown         bool `json:"-"` | ||||
| 	CompressionMethodsUnknown bool `json:"-"` | ||||
| } | ||||
| 
 | ||||
| // Key returns a standardized string form of the data in info, | ||||
| // useful for identifying duplicates. | ||||
| func (info ClientHelloInfo) Key() string { | ||||
| 	extensions, compressionMethods := "?", "?" | ||||
| 	if !info.ExtensionsUnknown { | ||||
| 		extensions = fmt.Sprintf("%x", info.Extensions) | ||||
| 	} | ||||
| 	if !info.CompressionMethodsUnknown { | ||||
| 		compressionMethods = fmt.Sprintf("%x", info.CompressionMethods) | ||||
| 	} | ||||
| 	return fastHash([]byte(fmt.Sprintf("%x-%x-%s-%s-%x-%x", | ||||
| 		info.Version, info.CipherSuites, extensions, | ||||
| 		compressionMethods, info.Curves, info.Points))) | ||||
| } | ||||
| 
 | ||||
| // obtainCertWaitChans is used to coordinate obtaining certs for each hostname. | ||||
| var obtainCertWaitChans = make(map[string]chan struct{}) | ||||
| var obtainCertWaitChansMu sync.Mutex | ||||
| @ -477,3 +535,8 @@ var failedIssuanceMu sync.RWMutex | ||||
| // If this value is recent, do not make any on-demand certificate requests. | ||||
| var lastIssueTime time.Time | ||||
| var lastIssueTimeMu sync.Mutex | ||||
| 
 | ||||
| // ClientHelloTelemetry determines whether to report | ||||
| // TLS ClientHellos to telemetry. Disable if doing | ||||
| // it from a different package. | ||||
| var ClientHelloTelemetry = true | ||||
|  | ||||
| @ -28,6 +28,7 @@ import ( | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/mholt/caddy" | ||||
| 	"github.com/mholt/caddy/telemetry" | ||||
| ) | ||||
| 
 | ||||
| func init() { | ||||
| @ -174,9 +175,11 @@ func setupTLS(c *caddy.Controller) error { | ||||
| 			case "max_certs": | ||||
| 				c.Args(&maxCerts) | ||||
| 				config.OnDemand = true | ||||
| 				telemetry.Increment("tls_on_demand_count") | ||||
| 			case "ask": | ||||
| 				c.Args(&askURL) | ||||
| 				config.OnDemand = true | ||||
| 				telemetry.Increment("tls_on_demand_count") | ||||
| 			case "dns": | ||||
| 				args := c.RemainingArgs() | ||||
| 				if len(args) != 1 { | ||||
| @ -283,6 +286,7 @@ func setupTLS(c *caddy.Controller) error { | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("self-signed: %v", err) | ||||
| 		} | ||||
| 		telemetry.Increment("tls_self_signed_count") | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
|  | ||||
							
								
								
									
										53
									
								
								plugins.go
									
									
									
									
									
								
							
							
						
						
									
										53
									
								
								plugins.go
									
									
									
									
									
								
							| @ -54,32 +54,58 @@ var ( | ||||
| 
 | ||||
| // DescribePlugins returns a string describing the registered plugins. | ||||
| func DescribePlugins() string { | ||||
| 	pl := ListPlugins() | ||||
| 
 | ||||
| 	str := "Server types:\n" | ||||
| 	for name := range serverTypes { | ||||
| 	for _, name := range pl["server_types"] { | ||||
| 		str += "  " + name + "\n" | ||||
| 	} | ||||
| 
 | ||||
| 	// List the loaders in registration order | ||||
| 	str += "\nCaddyfile loaders:\n" | ||||
| 	for _, name := range pl["caddyfile_loaders"] { | ||||
| 		str += "  " + name + "\n" | ||||
| 	} | ||||
| 
 | ||||
| 	if len(pl["event_hooks"]) > 0 { | ||||
| 		str += "\nEvent hook plugins:\n" | ||||
| 		for _, name := range pl["event_hooks"] { | ||||
| 			str += "  hook." + name + "\n" | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	str += "\nOther plugins:\n" | ||||
| 	for _, name := range pl["others"] { | ||||
| 		str += "  " + name + "\n" | ||||
| 	} | ||||
| 
 | ||||
| 	return str | ||||
| } | ||||
| 
 | ||||
| // ListPlugins makes a list of the registered plugins, | ||||
| // keyed by plugin type. | ||||
| func ListPlugins() map[string][]string { | ||||
| 	p := make(map[string][]string) | ||||
| 
 | ||||
| 	// server type plugins | ||||
| 	for name := range serverTypes { | ||||
| 		p["server_types"] = append(p["server_types"], name) | ||||
| 	} | ||||
| 
 | ||||
| 	// caddyfile loaders in registration order | ||||
| 	for _, loader := range caddyfileLoaders { | ||||
| 		str += "  " + loader.name + "\n" | ||||
| 		p["caddyfile_loaders"] = append(p["caddyfile_loaders"], loader.name) | ||||
| 	} | ||||
| 	if defaultCaddyfileLoader.name != "" { | ||||
| 		str += "  " + defaultCaddyfileLoader.name + "\n" | ||||
| 		p["caddyfile_loaders"] = append(p["caddyfile_loaders"], defaultCaddyfileLoader.name) | ||||
| 	} | ||||
| 
 | ||||
| 	// List the event hook plugins | ||||
| 	hooks := "" | ||||
| 	eventHooks.Range(func(k, _ interface{}) bool { | ||||
| 		hooks += "  hook." + k.(string) + "\n" | ||||
| 		p["event_hooks"] = append(p["event_hooks"], k.(string)) | ||||
| 		return true | ||||
| 	}) | ||||
| 	if hooks != "" { | ||||
| 		str += "\nEvent hook plugins:\n" | ||||
| 		str += hooks | ||||
| 	} | ||||
| 
 | ||||
| 	// Let's alphabetize the rest of these... | ||||
| 	// alphabetize the rest of the plugins | ||||
| 	var others []string | ||||
| 	for stype, stypePlugins := range plugins { | ||||
| 		for name := range stypePlugins { | ||||
| @ -93,12 +119,11 @@ func DescribePlugins() string { | ||||
| 	} | ||||
| 
 | ||||
| 	sort.Strings(others) | ||||
| 	str += "\nOther plugins:\n" | ||||
| 	for _, name := range others { | ||||
| 		str += "  " + name + "\n" | ||||
| 		p["others"] = append(p["others"], name) | ||||
| 	} | ||||
| 
 | ||||
| 	return str | ||||
| 	return p | ||||
| } | ||||
| 
 | ||||
| // ValidDirectives returns the list of all directives that are | ||||
|  | ||||
| @ -19,6 +19,8 @@ import ( | ||||
| 	"os" | ||||
| 	"os/signal" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"github.com/mholt/caddy/telemetry" | ||||
| ) | ||||
| 
 | ||||
| // TrapSignals create signal handlers for all applicable signals for this | ||||
| @ -52,6 +54,9 @@ func trapSignalsCrossPlatform() { | ||||
| 
 | ||||
| 			log.Println("[INFO] SIGINT: Shutting down") | ||||
| 
 | ||||
| 			telemetry.AppendUnique("sigtrap", "SIGINT") | ||||
| 			go telemetry.StopEmitting() // not guaranteed to finish in time; that's OK (just don't block!) | ||||
| 
 | ||||
| 			// important cleanup actions before shutdown callbacks | ||||
| 			for _, f := range OnProcessExit { | ||||
| 				f() | ||||
|  | ||||
| @ -21,6 +21,8 @@ import ( | ||||
| 	"os" | ||||
| 	"os/signal" | ||||
| 	"syscall" | ||||
| 
 | ||||
| 	"github.com/mholt/caddy/telemetry" | ||||
| ) | ||||
| 
 | ||||
| // trapSignalsPosix captures POSIX-only signals. | ||||
| @ -49,10 +51,15 @@ func trapSignalsPosix() { | ||||
| 					log.Printf("[ERROR] SIGTERM stop: %v", err) | ||||
| 					exitCode = 3 | ||||
| 				} | ||||
| 
 | ||||
| 				telemetry.AppendUnique("sigtrap", "SIGTERM") | ||||
| 				go telemetry.StopEmitting() // won't finish in time, but that's OK - just don't block | ||||
| 
 | ||||
| 				os.Exit(exitCode) | ||||
| 
 | ||||
| 			case syscall.SIGUSR1: | ||||
| 				log.Println("[INFO] SIGUSR1: Reloading") | ||||
| 				go telemetry.AppendUnique("sigtrap", "SIGUSR1") | ||||
| 
 | ||||
| 				// Start with the existing Caddyfile | ||||
| 				caddyfileToUse, inst, err := getCurrentCaddyfile() | ||||
| @ -92,12 +99,14 @@ func trapSignalsPosix() { | ||||
| 
 | ||||
| 			case syscall.SIGUSR2: | ||||
| 				log.Println("[INFO] SIGUSR2: Upgrading") | ||||
| 				go telemetry.AppendUnique("sigtrap", "SIGUSR2") | ||||
| 				if err := Upgrade(); err != nil { | ||||
| 					log.Printf("[ERROR] SIGUSR2: upgrading: %v", err) | ||||
| 				} | ||||
| 
 | ||||
| 			case syscall.SIGHUP: | ||||
| 				// ignore; this signal is sometimes sent outside of the user's control | ||||
| 				go telemetry.AppendUnique("sigtrap", "SIGHUP") | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
							
								
								
									
										296
									
								
								telemetry/collection.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										296
									
								
								telemetry/collection.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,296 @@ | ||||
| // Copyright 2015 Light Code Labs, LLC | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
| 
 | ||||
| package telemetry | ||||
| 
 | ||||
| import ( | ||||
| 	"log" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/google/uuid" | ||||
| ) | ||||
| 
 | ||||
| // Init initializes this package so that it may | ||||
| // be used. Do not call this function more than | ||||
| // once. Init panics if it is called more than | ||||
| // once or if the UUID value is empty. Once this | ||||
| // function is called, the rest of the package | ||||
| // may safely be used. If this function is not | ||||
| // called, the collector functions may still be | ||||
| // invoked, but they will be no-ops. | ||||
| // | ||||
| // Any metrics keys that are passed in the second | ||||
| // argument will be permanently disabled for the | ||||
| // lifetime of the process. | ||||
| func Init(instanceID uuid.UUID, disabledMetricsKeys []string) { | ||||
| 	if enabled { | ||||
| 		panic("already initialized") | ||||
| 	} | ||||
| 	if str := instanceID.String(); str == "" || | ||||
| 		str == "00000000-0000-0000-0000-000000000000" { | ||||
| 		panic("empty UUID") | ||||
| 	} | ||||
| 	instanceUUID = instanceID | ||||
| 	disabledMetricsMu.Lock() | ||||
| 	for _, key := range disabledMetricsKeys { | ||||
| 		disabledMetrics[strings.TrimSpace(key)] = false | ||||
| 	} | ||||
| 	disabledMetricsMu.Unlock() | ||||
| 	enabled = true | ||||
| } | ||||
| 
 | ||||
| // StartEmitting sends the current payload and begins the | ||||
| // transmission cycle for updates. This is the first | ||||
| // update sent, and future ones will be sent until | ||||
| // StopEmitting is called. | ||||
| // | ||||
| // This function is non-blocking (it spawns a new goroutine). | ||||
| // | ||||
| // This function panics if it was called more than once. | ||||
| // It is a no-op if this package was not initialized. | ||||
| func StartEmitting() { | ||||
| 	if !enabled { | ||||
| 		return | ||||
| 	} | ||||
| 	updateTimerMu.Lock() | ||||
| 	if updateTimer != nil { | ||||
| 		updateTimerMu.Unlock() | ||||
| 		panic("updates already started") | ||||
| 	} | ||||
| 	updateTimerMu.Unlock() | ||||
| 	updateMu.Lock() | ||||
| 	if updating { | ||||
| 		updateMu.Unlock() | ||||
| 		panic("update already in progress") | ||||
| 	} | ||||
| 	updateMu.Unlock() | ||||
| 	go logEmit(false) | ||||
| } | ||||
| 
 | ||||
| // StopEmitting sends the current payload and terminates | ||||
| // the update cycle. No more updates will be sent. | ||||
| // | ||||
| // It is a no-op if the package was never initialized | ||||
| // or if emitting was never started. | ||||
| // | ||||
| // NOTE: This function is blocking. Run in a goroutine if | ||||
| // you want to guarantee no blocking at critical times | ||||
| // like exiting the program. | ||||
| func StopEmitting() { | ||||
| 	if !enabled { | ||||
| 		return | ||||
| 	} | ||||
| 	updateTimerMu.Lock() | ||||
| 	if updateTimer == nil { | ||||
| 		updateTimerMu.Unlock() | ||||
| 		return | ||||
| 	} | ||||
| 	updateTimerMu.Unlock() | ||||
| 	logEmit(true) // likely too early; may take minutes to return | ||||
| } | ||||
| 
 | ||||
| // Reset empties the current payload buffer. | ||||
| func Reset() { | ||||
| 	resetBuffer() | ||||
| } | ||||
| 
 | ||||
| // Set puts a value in the buffer to be included | ||||
| // in the next emission. It overwrites any | ||||
| // previous value. | ||||
| // | ||||
| // This function is safe for multiple goroutines, | ||||
| // and it is recommended to call this using the | ||||
| // go keyword after the call to SendHello so it | ||||
| // doesn't block crucial code. | ||||
| func Set(key string, val interface{}) { | ||||
| 	if !enabled || isDisabled(key) { | ||||
| 		return | ||||
| 	} | ||||
| 	bufferMu.Lock() | ||||
| 	if _, ok := buffer[key]; !ok { | ||||
| 		if bufferItemCount >= maxBufferItems { | ||||
| 			bufferMu.Unlock() | ||||
| 			return | ||||
| 		} | ||||
| 		bufferItemCount++ | ||||
| 	} | ||||
| 	buffer[key] = val | ||||
| 	bufferMu.Unlock() | ||||
| } | ||||
| 
 | ||||
| // SetNested puts a value in the buffer to be included | ||||
| // in the next emission, nested under the top-level key | ||||
| // as subkey. It overwrites any previous value. | ||||
| // | ||||
| // This function is safe for multiple goroutines, | ||||
| // and it is recommended to call this using the | ||||
| // go keyword after the call to SendHello so it | ||||
| // doesn't block crucial code. | ||||
| func SetNested(key, subkey string, val interface{}) { | ||||
| 	if !enabled || isDisabled(key) { | ||||
| 		return | ||||
| 	} | ||||
| 	bufferMu.Lock() | ||||
| 	if topLevel, ok1 := buffer[key]; ok1 { | ||||
| 		topLevelMap, ok2 := topLevel.(map[string]interface{}) | ||||
| 		if !ok2 { | ||||
| 			bufferMu.Unlock() | ||||
| 			log.Printf("[PANIC] Telemetry: key %s is already used for non-nested-map value", key) | ||||
| 			return | ||||
| 		} | ||||
| 		if _, ok3 := topLevelMap[subkey]; !ok3 { | ||||
| 			// don't exceed max buffer size | ||||
| 			if bufferItemCount >= maxBufferItems { | ||||
| 				bufferMu.Unlock() | ||||
| 				return | ||||
| 			} | ||||
| 			bufferItemCount++ | ||||
| 		} | ||||
| 		topLevelMap[subkey] = val | ||||
| 	} else { | ||||
| 		// don't exceed max buffer size | ||||
| 		if bufferItemCount >= maxBufferItems { | ||||
| 			bufferMu.Unlock() | ||||
| 			return | ||||
| 		} | ||||
| 		bufferItemCount++ | ||||
| 		buffer[key] = map[string]interface{}{subkey: val} | ||||
| 	} | ||||
| 	bufferMu.Unlock() | ||||
| } | ||||
| 
 | ||||
| // Append appends value to a list named key. | ||||
| // If key is new, a new list will be created. | ||||
| // If key maps to a type that is not a list, | ||||
| // a panic is logged, and this is a no-op. | ||||
| func Append(key string, value interface{}) { | ||||
| 	if !enabled || isDisabled(key) { | ||||
| 		return | ||||
| 	} | ||||
| 	bufferMu.Lock() | ||||
| 	if bufferItemCount >= maxBufferItems { | ||||
| 		bufferMu.Unlock() | ||||
| 		return | ||||
| 	} | ||||
| 	// TODO: Test this... | ||||
| 	bufVal, inBuffer := buffer[key] | ||||
| 	sliceVal, sliceOk := bufVal.([]interface{}) | ||||
| 	if inBuffer && !sliceOk { | ||||
| 		bufferMu.Unlock() | ||||
| 		log.Printf("[PANIC] Telemetry: key %s already used for non-slice value", key) | ||||
| 		return | ||||
| 	} | ||||
| 	if sliceVal == nil { | ||||
| 		buffer[key] = []interface{}{value} | ||||
| 	} else if sliceOk { | ||||
| 		buffer[key] = append(sliceVal, value) | ||||
| 	} | ||||
| 	bufferItemCount++ | ||||
| 	bufferMu.Unlock() | ||||
| } | ||||
| 
 | ||||
| // AppendUnique adds value to a set named key. | ||||
| // Set items are unordered. Values in the set | ||||
| // are unique, but how many times they are | ||||
| // appended is counted. The value must be | ||||
| // hashable. | ||||
| // | ||||
| // If key is new, a new set will be created for | ||||
| // values with that key. If key maps to a type | ||||
| // that is not a counting set, a panic is logged, | ||||
| // and this is a no-op. | ||||
| func AppendUnique(key string, value interface{}) { | ||||
| 	if !enabled || isDisabled(key) { | ||||
| 		return | ||||
| 	} | ||||
| 	bufferMu.Lock() | ||||
| 	bufVal, inBuffer := buffer[key] | ||||
| 	setVal, setOk := bufVal.(countingSet) | ||||
| 	if inBuffer && !setOk { | ||||
| 		bufferMu.Unlock() | ||||
| 		log.Printf("[PANIC] Telemetry: key %s already used for non-counting-set value", key) | ||||
| 		return | ||||
| 	} | ||||
| 	if setVal == nil { | ||||
| 		// ensure the buffer is not too full, then add new unique value | ||||
| 		if bufferItemCount >= maxBufferItems { | ||||
| 			bufferMu.Unlock() | ||||
| 			return | ||||
| 		} | ||||
| 		buffer[key] = countingSet{value: 1} | ||||
| 		bufferItemCount++ | ||||
| 	} else if setOk { | ||||
| 		// unique value already exists, so just increment counter | ||||
| 		setVal[value]++ | ||||
| 	} | ||||
| 	bufferMu.Unlock() | ||||
| } | ||||
| 
 | ||||
| // Add adds amount to a value named key. | ||||
| // If it does not exist, it is created with | ||||
| // a value of 1. If key maps to a type that | ||||
| // is not an integer, a panic is logged, | ||||
| // and this is a no-op. | ||||
| func Add(key string, amount int) { | ||||
| 	atomicAdd(key, amount) | ||||
| } | ||||
| 
 | ||||
| // Increment is a shortcut for Add(key, 1) | ||||
| func Increment(key string) { | ||||
| 	atomicAdd(key, 1) | ||||
| } | ||||
| 
 | ||||
| // atomicAdd adds amount (negative to subtract) | ||||
| // to key. | ||||
| func atomicAdd(key string, amount int) { | ||||
| 	if !enabled || isDisabled(key) { | ||||
| 		return | ||||
| 	} | ||||
| 	bufferMu.Lock() | ||||
| 	bufVal, inBuffer := buffer[key] | ||||
| 	intVal, intOk := bufVal.(int) | ||||
| 	if inBuffer && !intOk { | ||||
| 		bufferMu.Unlock() | ||||
| 		log.Printf("[PANIC] Telemetry: key %s already used for non-integer value", key) | ||||
| 		return | ||||
| 	} | ||||
| 	if !inBuffer { | ||||
| 		if bufferItemCount >= maxBufferItems { | ||||
| 			bufferMu.Unlock() | ||||
| 			return | ||||
| 		} | ||||
| 		bufferItemCount++ | ||||
| 	} | ||||
| 	buffer[key] = intVal + amount | ||||
| 	bufferMu.Unlock() | ||||
| } | ||||
| 
 | ||||
| // isDisabled returns whether key is | ||||
| // a disabled metric key. ALL collection | ||||
| // functions should call this and not | ||||
| // save the value if this returns true. | ||||
| func isDisabled(key string) bool { | ||||
| 	// for keys that are augmented with data, such as | ||||
| 	// "tls_client_hello_ua:<hash>", just | ||||
| 	// check the prefix "tls_client_hello_ua" | ||||
| 	checkKey := key | ||||
| 	if idx := strings.Index(key, ":"); idx > -1 { | ||||
| 		checkKey = key[:idx] | ||||
| 	} | ||||
| 
 | ||||
| 	disabledMetricsMu.RLock() | ||||
| 	_, ok := disabledMetrics[checkKey] | ||||
| 	disabledMetricsMu.RUnlock() | ||||
| 	return ok | ||||
| } | ||||
							
								
								
									
										109
									
								
								telemetry/collection_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								telemetry/collection_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,109 @@ | ||||
| // Copyright 2015 Light Code Labs, LLC | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
| 
 | ||||
| package telemetry | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/google/uuid" | ||||
| ) | ||||
| 
 | ||||
| func TestInit(t *testing.T) { | ||||
| 	reset() | ||||
| 
 | ||||
| 	id := doInit(t) // should not panic | ||||
| 
 | ||||
| 	defer func() { | ||||
| 		if r := recover(); r == nil { | ||||
| 			t.Errorf("Second call to Init should have panicked") | ||||
| 		} | ||||
| 	}() | ||||
| 	Init(id, nil) // should panic | ||||
| } | ||||
| 
 | ||||
| func TestInitEmptyUUID(t *testing.T) { | ||||
| 	reset() | ||||
| 	defer func() { | ||||
| 		if r := recover(); r == nil { | ||||
| 			t.Errorf("Call to Init with empty UUID should have panicked") | ||||
| 		} | ||||
| 	}() | ||||
| 	Init(uuid.UUID([16]byte{}), nil) | ||||
| } | ||||
| 
 | ||||
| func TestSet(t *testing.T) { | ||||
| 	reset() | ||||
| 
 | ||||
| 	// should be no-op since we haven't called Init() yet | ||||
| 	Set("test1", "foobar") | ||||
| 	if _, ok := buffer["test"]; ok { | ||||
| 		t.Errorf("Should not have inserted item when not initialized") | ||||
| 	} | ||||
| 
 | ||||
| 	// should work after we've initialized | ||||
| 	doInit(t) | ||||
| 	Set("test1", "foobar") | ||||
| 	val, ok := buffer["test1"] | ||||
| 	if !ok { | ||||
| 		t.Errorf("Expected value to be in buffer, but it wasn't") | ||||
| 	} else if val.(string) != "foobar" { | ||||
| 		t.Errorf("Expected 'foobar', got '%v'", val) | ||||
| 	} | ||||
| 
 | ||||
| 	// should not overfill buffer | ||||
| 	maxBufferItemsTmp := maxBufferItems | ||||
| 	maxBufferItems = 10 | ||||
| 	for i := 0; i < maxBufferItems+1; i++ { | ||||
| 		Set(fmt.Sprintf("overfill_%d", i), "foobar") | ||||
| 	} | ||||
| 	if len(buffer) > maxBufferItems { | ||||
| 		t.Errorf("Should not exceed max buffer size (%d); has %d items", | ||||
| 			maxBufferItems, len(buffer)) | ||||
| 	} | ||||
| 	maxBufferItems = maxBufferItemsTmp | ||||
| 
 | ||||
| 	// Should overwrite values | ||||
| 	Set("test1", "foobar2") | ||||
| 	val, ok = buffer["test1"] | ||||
| 	if !ok { | ||||
| 		t.Errorf("Expected value to be in buffer, but it wasn't") | ||||
| 	} else if val.(string) != "foobar2" { | ||||
| 		t.Errorf("Expected 'foobar2', got '%v'", val) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // doInit calls Init() with a valid UUID | ||||
| // and returns it. | ||||
| func doInit(t *testing.T) uuid.UUID { | ||||
| 	id, err := uuid.Parse(testUUID) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Could not make UUID: %v", err) | ||||
| 	} | ||||
| 	Init(id, nil) | ||||
| 	return id | ||||
| } | ||||
| 
 | ||||
| // reset resets all the lovely package-level state; | ||||
| // can be used as a set up function in tests. | ||||
| func reset() { | ||||
| 	instanceUUID = uuid.UUID{} | ||||
| 	buffer = make(map[string]interface{}) | ||||
| 	bufferItemCount = 0 | ||||
| 	updating = false | ||||
| 	enabled = false | ||||
| } | ||||
| 
 | ||||
| const testUUID = "0b6cfa22-0d4c-11e8-b11b-7a0058e13201" | ||||
							
								
								
									
										413
									
								
								telemetry/telemetry.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										413
									
								
								telemetry/telemetry.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,413 @@ | ||||
| // Copyright 2015 Light Code Labs, LLC | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
| 
 | ||||
| // Package telemetry implements the client for server-side telemetry | ||||
| // of the network. Functions in this package are synchronous and blocking | ||||
| // unless otherwise specified. For convenience, most functions here do | ||||
| // not return errors, but errors are logged to the standard logger. | ||||
| // | ||||
| // To use this package, first call Init(). You can then call any of the | ||||
| // collection/aggregation functions. Call StartEmitting() when you are | ||||
| // ready to begin sending telemetry updates. | ||||
| // | ||||
| // When collecting metrics (functions like Set, AppendUnique, or Increment), | ||||
| // it may be desirable and even recommended to invoke them in a new | ||||
| // goroutine in case there is lock contention; they are thread-safe (unless | ||||
| // noted), and you may not want them to block the main thread of execution. | ||||
| // However, sometimes blocking may be necessary too; for example, adding | ||||
| // startup metrics to the buffer before the call to StartEmitting(). | ||||
| // | ||||
| // This package is designed to be as fast and space-efficient as reasonably | ||||
| // possible, so that it does not disrupt the flow of execution. | ||||
| package telemetry | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"math/rand" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/google/uuid" | ||||
| ) | ||||
| 
 | ||||
| // logEmit calls emit and then logs the error, if any. | ||||
| // See docs for emit. | ||||
| func logEmit(final bool) { | ||||
| 	err := emit(final) | ||||
| 	if err != nil { | ||||
| 		log.Printf("[ERROR] Sending telemetry: %v", err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // emit sends an update to the telemetry server. | ||||
| // Set final to true if this is the last call to emit. | ||||
| // If final is true, no future updates will be scheduled. | ||||
| // Otherwise, the next update will be scheduled. | ||||
| func emit(final bool) error { | ||||
| 	if !enabled { | ||||
| 		return fmt.Errorf("telemetry not enabled") | ||||
| 	} | ||||
| 
 | ||||
| 	// ensure only one update happens at a time; | ||||
| 	// skip update if previous one still in progress | ||||
| 	updateMu.Lock() | ||||
| 	if updating { | ||||
| 		updateMu.Unlock() | ||||
| 		log.Println("[NOTICE] Skipping this telemetry update because previous one is still working") | ||||
| 		return nil | ||||
| 	} | ||||
| 	updating = true | ||||
| 	updateMu.Unlock() | ||||
| 	defer func() { | ||||
| 		updateMu.Lock() | ||||
| 		updating = false | ||||
| 		updateMu.Unlock() | ||||
| 	}() | ||||
| 
 | ||||
| 	// terminate any pending update if this is the last one | ||||
| 	if final { | ||||
| 		stopUpdateTimer() | ||||
| 	} | ||||
| 
 | ||||
| 	payloadBytes, err := makePayloadAndResetBuffer() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// this will hold the server's reply | ||||
| 	var reply Response | ||||
| 
 | ||||
| 	// transmit the payload - use a loop to retry in case of failure | ||||
| 	for i := 0; i < 4; i++ { | ||||
| 		if i > 0 && err != nil { | ||||
| 			// don't hammer the server; first failure might have been | ||||
| 			// a fluke, but back off more after that | ||||
| 			log.Printf("[WARNING] Sending telemetry (attempt %d): %v - backing off and retrying", i, err) | ||||
| 			time.Sleep(time.Duration((i+1)*(i+1)*(i+1)) * time.Second) | ||||
| 		} | ||||
| 
 | ||||
| 		// send it | ||||
| 		var resp *http.Response | ||||
| 		resp, err = httpClient.Post(endpoint+instanceUUID.String(), "application/json", bytes.NewReader(payloadBytes)) | ||||
| 		if err != nil { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// check for any special-case response codes | ||||
| 		if resp.StatusCode == http.StatusGone { | ||||
| 			// the endpoint has been deprecated and is no longer servicing clients | ||||
| 			err = fmt.Errorf("telemetry server replied with HTTP %d; upgrade required", resp.StatusCode) | ||||
| 			if clen := resp.Header.Get("Content-Length"); clen != "0" && clen != "" { | ||||
| 				bodyBytes, readErr := ioutil.ReadAll(resp.Body) | ||||
| 				if readErr != nil { | ||||
| 					log.Printf("[ERROR] Reading response body from server: %v", readErr) | ||||
| 				} | ||||
| 				err = fmt.Errorf("%v - %s", err, bodyBytes) | ||||
| 			} | ||||
| 			resp.Body.Close() | ||||
| 			reply.Stop = true | ||||
| 			break | ||||
| 		} | ||||
| 		if resp.StatusCode == http.StatusUnavailableForLegalReasons { | ||||
| 			// the endpoint is unavailable, at least to this client, for legal reasons (!) | ||||
| 			err = fmt.Errorf("telemetry server replied with HTTP %d %s: please consult the project website and developers for guidance", resp.StatusCode, resp.Status) | ||||
| 			if clen := resp.Header.Get("Content-Length"); clen != "0" && clen != "" { | ||||
| 				bodyBytes, readErr := ioutil.ReadAll(resp.Body) | ||||
| 				if readErr != nil { | ||||
| 					log.Printf("[ERROR] Reading response body from server: %v", readErr) | ||||
| 				} | ||||
| 				err = fmt.Errorf("%v - %s", err, bodyBytes) | ||||
| 			} | ||||
| 			resp.Body.Close() | ||||
| 			reply.Stop = true | ||||
| 			break | ||||
| 		} | ||||
| 
 | ||||
| 		// okay, ensure we can interpret the response | ||||
| 		if ct := resp.Header.Get("Content-Type"); (resp.StatusCode < 300 || resp.StatusCode >= 400) && | ||||
| 			!strings.Contains(ct, "json") { | ||||
| 			err = fmt.Errorf("telemetry server replied with unknown content-type: '%s' and HTTP %s", ct, resp.Status) | ||||
| 			resp.Body.Close() | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// read the response body | ||||
| 		err = json.NewDecoder(resp.Body).Decode(&reply) | ||||
| 		resp.Body.Close() // close response body as soon as we're done with it | ||||
| 		if err != nil { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// update the list of enabled/disabled keys, if any | ||||
| 		for _, key := range reply.EnableKeys { | ||||
| 			disabledMetricsMu.Lock() | ||||
| 			// only re-enable this metric if it is temporarily disabled | ||||
| 			if temp, ok := disabledMetrics[key]; ok && temp { | ||||
| 				delete(disabledMetrics, key) | ||||
| 			} | ||||
| 			disabledMetricsMu.Unlock() | ||||
| 		} | ||||
| 		for _, key := range reply.DisableKeys { | ||||
| 			disabledMetricsMu.Lock() | ||||
| 			disabledMetrics[key] = true // all remotely-disabled keys are "temporarily" disabled | ||||
| 			disabledMetricsMu.Unlock() | ||||
| 		} | ||||
| 
 | ||||
| 		// make sure we didn't send the update too soon; if so, | ||||
| 		// just wait and try again -- this is a special case of | ||||
| 		// error that we handle differently, as you can see | ||||
| 		if resp.StatusCode == http.StatusTooManyRequests { | ||||
| 			if reply.NextUpdate <= 0 { | ||||
| 				raStr := resp.Header.Get("Retry-After") | ||||
| 				if ra, err := strconv.Atoi(raStr); err == nil { | ||||
| 					reply.NextUpdate = time.Duration(ra) * time.Second | ||||
| 				} | ||||
| 			} | ||||
| 			if !final { | ||||
| 				log.Printf("[NOTICE] Sending telemetry: we were too early; waiting %s before trying again", reply.NextUpdate) | ||||
| 				time.Sleep(reply.NextUpdate) | ||||
| 				continue | ||||
| 			} | ||||
| 		} else if resp.StatusCode >= 400 { | ||||
| 			err = fmt.Errorf("telemetry server returned status code %d", resp.StatusCode) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		break | ||||
| 	} | ||||
| 	if err == nil && !final { | ||||
| 		// (remember, if there was an error, we return it | ||||
| 		// below, so it WILL get logged if it's supposed to) | ||||
| 		log.Println("[INFO] Sending telemetry: success") | ||||
| 	} | ||||
| 
 | ||||
| 	// even if there was an error after all retries, we should | ||||
| 	// schedule the next update using our default update | ||||
| 	// interval because the server might be healthy later | ||||
| 
 | ||||
| 	// ensure we won't slam the telemetry server; add a little variance | ||||
| 	if reply.NextUpdate < 1*time.Second { | ||||
| 		reply.NextUpdate = defaultUpdateInterval + time.Duration(rand.Intn(int(1*time.Minute))) | ||||
| 	} | ||||
| 
 | ||||
| 	// schedule the next update (if this wasn't the last one and | ||||
| 	// if the remote server didn't tell us to stop sending) | ||||
| 	if !final && !reply.Stop { | ||||
| 		updateTimerMu.Lock() | ||||
| 		updateTimer = time.AfterFunc(reply.NextUpdate, func() { | ||||
| 			logEmit(false) | ||||
| 		}) | ||||
| 		updateTimerMu.Unlock() | ||||
| 	} | ||||
| 
 | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func stopUpdateTimer() { | ||||
| 	updateTimerMu.Lock() | ||||
| 	updateTimer.Stop() | ||||
| 	updateTimer = nil | ||||
| 	updateTimerMu.Unlock() | ||||
| } | ||||
| 
 | ||||
| // makePayloadAndResetBuffer prepares a payload | ||||
| // by emptying the collection buffer. It returns | ||||
| // the bytes of the payload to send to the server. | ||||
| // Since the buffer is reset by this, if the | ||||
| // resulting byte slice is lost, the payload is | ||||
| // gone with it. | ||||
| func makePayloadAndResetBuffer() ([]byte, error) { | ||||
| 	bufCopy := resetBuffer() | ||||
| 
 | ||||
| 	// encode payload in preparation for transmission | ||||
| 	payload := Payload{ | ||||
| 		InstanceID: instanceUUID.String(), | ||||
| 		Timestamp:  time.Now().UTC(), | ||||
| 		Data:       bufCopy, | ||||
| 	} | ||||
| 	return json.Marshal(payload) | ||||
| } | ||||
| 
 | ||||
| // resetBuffer makes a local pointer to the buffer, | ||||
| // then resets the buffer by assigning to be a newly- | ||||
| // made value to clear it out, then sets the buffer | ||||
| // item count to 0. It returns the copied pointer to | ||||
| // the original map so the old buffer value can be | ||||
| // used locally. | ||||
| func resetBuffer() map[string]interface{} { | ||||
| 	bufferMu.Lock() | ||||
| 	bufCopy := buffer | ||||
| 	buffer = make(map[string]interface{}) | ||||
| 	bufferItemCount = 0 | ||||
| 	bufferMu.Unlock() | ||||
| 	return bufCopy | ||||
| } | ||||
| 
 | ||||
| // Response contains the body of a response from the | ||||
| // telemetry server. | ||||
| type Response struct { | ||||
| 	// NextUpdate is how long to wait before the next update. | ||||
| 	NextUpdate time.Duration `json:"next_update"` | ||||
| 
 | ||||
| 	// Stop instructs the telemetry server to stop sending | ||||
| 	// telemetry. This would only be done under extenuating | ||||
| 	// circumstances, but we are prepared for it nonetheless. | ||||
| 	Stop bool `json:"stop,omitempty"` | ||||
| 
 | ||||
| 	// Error will be populated with an error message, if any. | ||||
| 	// This field should be empty if the status code is < 400. | ||||
| 	Error string `json:"error,omitempty"` | ||||
| 
 | ||||
| 	// DisableKeys will contain a list of keys/metrics that | ||||
| 	// should NOT be sent until further notice. The client | ||||
| 	// must NOT store these items in its buffer or send them | ||||
| 	// to the telemetry server while they are disabled. If | ||||
| 	// this list and EnableKeys have the same value (which is | ||||
| 	// not supposed to happen), this field should dominate. | ||||
| 	DisableKeys []string `json:"disable_keys,omitempty"` | ||||
| 
 | ||||
| 	// EnableKeys will contain a list of keys/metrics that | ||||
| 	// MAY be sent until further notice. | ||||
| 	EnableKeys []string `json:"enable_keys,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // Payload is the data that gets sent to the telemetry server. | ||||
| type Payload struct { | ||||
| 	// The universally unique ID of the instance | ||||
| 	InstanceID string `json:"instance_id"` | ||||
| 
 | ||||
| 	// The UTC timestamp of the transmission | ||||
| 	Timestamp time.Time `json:"timestamp"` | ||||
| 
 | ||||
| 	// The timestamp before which the next update is expected | ||||
| 	// (NOT populated by client - the server fills this in | ||||
| 	// before it stores the data) | ||||
| 	ExpectNext time.Time `json:"expect_next,omitempty"` | ||||
| 
 | ||||
| 	// The metrics | ||||
| 	Data map[string]interface{} `json:"data,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // Int returns the value of the data keyed by key | ||||
| // if it is an integer; otherwise it returns 0. | ||||
| func (p Payload) Int(key string) int { | ||||
| 	val, _ := p.Data[key] | ||||
| 	switch p.Data[key].(type) { | ||||
| 	case int: | ||||
| 		return val.(int) | ||||
| 	case float64: // after JSON-decoding, int becomes float64... | ||||
| 		return int(val.(float64)) | ||||
| 	} | ||||
| 	return 0 | ||||
| } | ||||
| 
 | ||||
| // countingSet implements a set that counts how many | ||||
| // times a key is inserted. It marshals to JSON in a | ||||
| // way such that keys are converted to values next | ||||
| // to their associated counts. | ||||
| type countingSet map[interface{}]int | ||||
| 
 | ||||
| // MarshalJSON implements the json.Marshaler interface. | ||||
| // It converts the set to an array so that the values | ||||
| // are JSON object values instead of keys, since keys | ||||
| // are difficult to query in databases. | ||||
| func (s countingSet) MarshalJSON() ([]byte, error) { | ||||
| 	type Item struct { | ||||
| 		Value interface{} `json:"value"` | ||||
| 		Count int         `json:"count"` | ||||
| 	} | ||||
| 	var list []Item | ||||
| 
 | ||||
| 	for k, v := range s { | ||||
| 		list = append(list, Item{Value: k, Count: v}) | ||||
| 	} | ||||
| 
 | ||||
| 	return json.Marshal(list) | ||||
| } | ||||
| 
 | ||||
| var ( | ||||
| 	// httpClient should be used for HTTP requests. It | ||||
| 	// is configured with a timeout for reliability. | ||||
| 	httpClient = http.Client{ | ||||
| 		Transport: &http.Transport{ | ||||
| 			TLSHandshakeTimeout: 30 * time.Second, | ||||
| 			DisableKeepAlives:   true, | ||||
| 		}, | ||||
| 		Timeout: 1 * time.Minute, | ||||
| 	} | ||||
| 
 | ||||
| 	// buffer holds the data that we are building up to send. | ||||
| 	buffer          = make(map[string]interface{}) | ||||
| 	bufferItemCount = 0 | ||||
| 	bufferMu        sync.RWMutex // protects both the buffer and its count | ||||
| 
 | ||||
| 	// updating is used to ensure only one | ||||
| 	// update happens at a time. | ||||
| 	updating bool | ||||
| 	updateMu sync.Mutex | ||||
| 
 | ||||
| 	// updateTimer fires off the next update. | ||||
| 	// If no update is scheduled, this is nil. | ||||
| 	updateTimer   *time.Timer | ||||
| 	updateTimerMu sync.Mutex | ||||
| 
 | ||||
| 	// disabledMetrics is a set of metric keys | ||||
| 	// that should NOT be saved to the buffer | ||||
| 	// or sent to the telemetry server. The value | ||||
| 	// indicates whether the entry is temporary. | ||||
| 	// If the value is true, it may be removed if | ||||
| 	// the metric is re-enabled remotely later. If | ||||
| 	// the value is false, it is permanent | ||||
| 	// (presumably becaues the user explicitly | ||||
| 	// disabled it) and can only be re-enabled | ||||
| 	// with user consent. | ||||
| 	disabledMetrics   = make(map[string]bool) | ||||
| 	disabledMetricsMu sync.RWMutex | ||||
| 
 | ||||
| 	// instanceUUID is the ID of the current instance. | ||||
| 	// This MUST be set to emit telemetry. | ||||
| 	// This MUST NOT be openly exposed to clients, for privacy. | ||||
| 	instanceUUID uuid.UUID | ||||
| 
 | ||||
| 	// enabled indicates whether the package has | ||||
| 	// been initialized and can be actively used. | ||||
| 	enabled bool | ||||
| 
 | ||||
| 	// maxBufferItems is the maximum number of items we'll allow | ||||
| 	// in the buffer before we start dropping new ones, in a | ||||
| 	// rough (simple) attempt to keep memory use under control. | ||||
| 	maxBufferItems = 100000 | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	// endpoint is the base URL to remote telemetry server; | ||||
| 	// the instance ID will be appended to it. | ||||
| 	endpoint = "https://telemetry-staging.caddyserver.com/v1/update/" | ||||
| 
 | ||||
| 	// defaultUpdateInterval is how long to wait before emitting | ||||
| 	// more telemetry data if all retires fail. This value is | ||||
| 	// only used if the client receives a nonsensical value, or | ||||
| 	// doesn't send one at all, or if a connection can't be made, | ||||
| 	// likely indicating a problem with the server. Thus, this | ||||
| 	// value should be a long duration to help alleviate extra | ||||
| 	// load on the server. | ||||
| 	defaultUpdateInterval = 1 * time.Hour | ||||
| ) | ||||
							
								
								
									
										59
									
								
								telemetry/telemetry_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								telemetry/telemetry_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | ||||
| // Copyright 2015 Light Code Labs, LLC | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
| 
 | ||||
| package telemetry | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| func TestMakePayloadAndResetBuffer(t *testing.T) { | ||||
| 	reset() | ||||
| 	id := doInit(t) | ||||
| 
 | ||||
| 	buffer = map[string]interface{}{ | ||||
| 		"foo1": "bar1", | ||||
| 		"foo2": "bar2", | ||||
| 	} | ||||
| 	bufferItemCount = 2 | ||||
| 
 | ||||
| 	payloadBytes, err := makePayloadAndResetBuffer() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Error making payload bytes: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if len(buffer) != 0 { | ||||
| 		t.Errorf("Expected buffer len to be 0, got %d", len(buffer)) | ||||
| 	} | ||||
| 	if bufferItemCount != 0 { | ||||
| 		t.Errorf("Expected buffer item count to be 0, got %d", bufferItemCount) | ||||
| 	} | ||||
| 
 | ||||
| 	var payload Payload | ||||
| 	err = json.Unmarshal(payloadBytes, &payload) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Error deserializing payload: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if payload.InstanceID != id.String() { | ||||
| 		t.Errorf("Expected instance ID to be set to '%s' but got '%s'", testUUID, payload.InstanceID) | ||||
| 	} | ||||
| 	if payload.Data == nil { | ||||
| 		t.Errorf("Expected data to be set, but was nil") | ||||
| 	} | ||||
| 	if payload.Timestamp.IsZero() { | ||||
| 		t.Errorf("Expected timestamp to be set, but was zero value") | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										9
									
								
								vendor/github.com/codahale/aesnicheck/asm_amd64.s
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/codahale/aesnicheck/asm_amd64.s
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,9 +0,0 @@ | ||||
| // func HasAESNI() bool | ||||
| TEXT ·HasAESNI(SB),$0 | ||||
| 	XORQ AX, AX | ||||
| 	INCL AX | ||||
| 	CPUID | ||||
| 	SHRQ $25, CX | ||||
| 	ANDQ $1, CX | ||||
| 	MOVB CX, ret+0(FP) | ||||
| 	RET | ||||
							
								
								
									
										6
									
								
								vendor/github.com/codahale/aesnicheck/check_asm.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/github.com/codahale/aesnicheck/check_asm.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,6 +0,0 @@ | ||||
| // +build amd64 | ||||
| 
 | ||||
| package aesnicheck | ||||
| 
 | ||||
| // HasAESNI returns whether AES-NI is supported by the CPU. | ||||
| func HasAESNI() bool | ||||
							
								
								
									
										8
									
								
								vendor/github.com/codahale/aesnicheck/check_generic.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								vendor/github.com/codahale/aesnicheck/check_generic.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,8 +0,0 @@ | ||||
| // +build !amd64 | ||||
| 
 | ||||
| package aesnicheck | ||||
| 
 | ||||
| // HasAESNI returns whether AES-NI is supported by the CPU. | ||||
| func HasAESNI() bool { | ||||
| 	return false | ||||
| } | ||||
							
								
								
									
										22
									
								
								vendor/github.com/codahale/aesnicheck/cmd/aesnicheck/aesnicheck.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/codahale/aesnicheck/cmd/aesnicheck/aesnicheck.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,22 +0,0 @@ | ||||
| // Command aesnicheck queries the CPU for AES-NI support. If AES-NI is supported, | ||||
| // aesnicheck will print "supported" and exit with a status of 0. If AES-NI is | ||||
| // not supported, aesnicheck will print "unsupported" and exit with a status of | ||||
| // -1. | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 
 | ||||
| 	"github.com/codahale/aesnicheck" | ||||
| ) | ||||
| 
 | ||||
| func main() { | ||||
| 	if aesnicheck.HasAESNI() { | ||||
| 		fmt.Println("supported") | ||||
| 		os.Exit(0) | ||||
| 	} else { | ||||
| 		fmt.Println("unsupported") | ||||
| 		os.Exit(-1) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										9
									
								
								vendor/github.com/codahale/aesnicheck/docs.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								vendor/github.com/codahale/aesnicheck/docs.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,9 +0,0 @@ | ||||
| // Package aesnicheck provides a simple check to see if crypto/aes is using | ||||
| // AES-NI instructions or if the AES transform is being done in software. AES-NI | ||||
| // is constant-time, which makes it impervious to cache-level timing attacks. For | ||||
| // security-conscious deployments on public cloud infrastructure (Amazon EC2, | ||||
| // Google Compute Engine, Microsoft Azure, etc.) this may be critical. | ||||
| // | ||||
| // See http://eprint.iacr.org/2014/248 for details on cross-VM timing attacks on | ||||
| // AES keys. | ||||
| package aesnicheck | ||||
							
								
								
									
										2
									
								
								vendor/github.com/google/uuid/hash.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/google/uuid/hash.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -27,7 +27,7 @@ var ( | ||||
| func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID { | ||||
| 	h.Reset() | ||||
| 	h.Write(space[:]) | ||||
| 	h.Write([]byte(data)) | ||||
| 	h.Write(data) | ||||
| 	s := h.Sum(nil) | ||||
| 	var uuid UUID | ||||
| 	copy(uuid[:], s) | ||||
|  | ||||
							
								
								
									
										31
									
								
								vendor/github.com/google/uuid/node.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										31
									
								
								vendor/github.com/google/uuid/node.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -5,16 +5,14 @@ | ||||
| package uuid | ||||
| 
 | ||||
| import ( | ||||
| 	"net" | ||||
| 	"sync" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	nodeMu     sync.Mutex | ||||
| 	interfaces []net.Interface // cached list of interfaces | ||||
| 	ifname     string          // name of interface being used | ||||
| 	nodeID     [6]byte         // hardware for version 1 UUIDs | ||||
| 	zeroID     [6]byte         // nodeID with only 0's | ||||
| 	nodeMu sync.Mutex | ||||
| 	ifname string  // name of interface being used | ||||
| 	nodeID [6]byte // hardware for version 1 UUIDs | ||||
| 	zeroID [6]byte // nodeID with only 0's | ||||
| ) | ||||
| 
 | ||||
| // NodeInterface returns the name of the interface from which the NodeID was | ||||
| @ -39,20 +37,12 @@ func SetNodeInterface(name string) bool { | ||||
| } | ||||
| 
 | ||||
| func setNodeInterface(name string) bool { | ||||
| 	if interfaces == nil { | ||||
| 		var err error | ||||
| 		interfaces, err = net.Interfaces() | ||||
| 		if err != nil && name != "" { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for _, ifs := range interfaces { | ||||
| 		if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) { | ||||
| 			copy(nodeID[:], ifs.HardwareAddr) | ||||
| 			ifname = ifs.Name | ||||
| 			return true | ||||
| 		} | ||||
| 	iname, addr := getHardwareInterface(name) // null implementation for js | ||||
| 	if iname != "" && addr != nil { | ||||
| 		ifname = iname | ||||
| 		copy(nodeID[:], addr) | ||||
| 		return true | ||||
| 	} | ||||
| 
 | ||||
| 	// We found no interfaces with a valid hardware address.  If name | ||||
| @ -94,9 +84,6 @@ func SetNodeID(id []byte) bool { | ||||
| // NodeID returns the 6 byte node id encoded in uuid.  It returns nil if uuid is | ||||
| // not valid.  The NodeID is only well defined for version 1 and 2 UUIDs. | ||||
| func (uuid UUID) NodeID() []byte { | ||||
| 	if len(uuid) != 16 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	var node [6]byte | ||||
| 	copy(node[:], uuid[10:]) | ||||
| 	return node[:] | ||||
|  | ||||
							
								
								
									
										12
									
								
								vendor/github.com/google/uuid/node_js.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								vendor/github.com/google/uuid/node_js.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| // Copyright 2017 Google Inc.  All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
| 
 | ||||
| // +build js | ||||
| 
 | ||||
| package uuid | ||||
| 
 | ||||
| // getHardwareInterface returns nil values for the JS version of the code. | ||||
| // This remvoves the "net" dependency, because it is not used in the browser. | ||||
| // Using the "net" library inflates the size of the transpiled JS code by 673k bytes. | ||||
| func getHardwareInterface(name string) (string, []byte) { return "", nil } | ||||
							
								
								
									
										33
									
								
								vendor/github.com/google/uuid/node_net.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								vendor/github.com/google/uuid/node_net.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| // Copyright 2017 Google Inc.  All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
| 
 | ||||
| // +build !js | ||||
| 
 | ||||
| package uuid | ||||
| 
 | ||||
| import "net" | ||||
| 
 | ||||
| var interfaces []net.Interface // cached list of interfaces | ||||
| 
 | ||||
| // getHardwareInterface returns the name and hardware address of interface name. | ||||
| // If name is "" then the name and hardware address of one of the system's | ||||
| // interfaces is returned.  If no interfaces are found (name does not exist or | ||||
| // there are no interfaces) then "", nil is returned. | ||||
| // | ||||
| // Only addresses of at least 6 bytes are returned. | ||||
| func getHardwareInterface(name string) (string, []byte) { | ||||
| 	if interfaces == nil { | ||||
| 		var err error | ||||
| 		interfaces, err = net.Interfaces() | ||||
| 		if err != nil { | ||||
| 			return "", nil | ||||
| 		} | ||||
| 	} | ||||
| 	for _, ifs := range interfaces { | ||||
| 		if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) { | ||||
| 			return ifs.Name, ifs.HardwareAddr | ||||
| 		} | ||||
| 	} | ||||
| 	return "", nil | ||||
| } | ||||
							
								
								
									
										6
									
								
								vendor/github.com/google/uuid/time.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/github.com/google/uuid/time.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -86,7 +86,7 @@ func clockSequence() int { | ||||
| 	return int(clockSeq & 0x3fff) | ||||
| } | ||||
| 
 | ||||
| // SetClockSeq sets the clock sequence to the lower 14 bits of seq.  Setting to | ||||
| // SetClockSequence sets the clock sequence to the lower 14 bits of seq.  Setting to | ||||
| // -1 causes a new sequence to be generated. | ||||
| func SetClockSequence(seq int) { | ||||
| 	defer timeMu.Unlock() | ||||
| @ -100,9 +100,9 @@ func setClockSequence(seq int) { | ||||
| 		randomBits(b[:]) // clock sequence | ||||
| 		seq = int(b[0])<<8 | int(b[1]) | ||||
| 	} | ||||
| 	old_seq := clockSeq | ||||
| 	oldSeq := clockSeq | ||||
| 	clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant | ||||
| 	if old_seq != clockSeq { | ||||
| 	if oldSeq != clockSeq { | ||||
| 		lasttime = 0 | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										19
									
								
								vendor/github.com/google/uuid/uuid.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								vendor/github.com/google/uuid/uuid.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -58,11 +58,11 @@ func Parse(s string) (UUID, error) { | ||||
| 		14, 16, | ||||
| 		19, 21, | ||||
| 		24, 26, 28, 30, 32, 34} { | ||||
| 		if v, ok := xtob(s[x], s[x+1]); !ok { | ||||
| 		v, ok := xtob(s[x], s[x+1]) | ||||
| 		if !ok { | ||||
| 			return uuid, errors.New("invalid UUID format") | ||||
| 		} else { | ||||
| 			uuid[i] = v | ||||
| 		} | ||||
| 		uuid[i] = v | ||||
| 	} | ||||
| 	return uuid, nil | ||||
| } | ||||
| @ -88,15 +88,22 @@ func ParseBytes(b []byte) (UUID, error) { | ||||
| 		14, 16, | ||||
| 		19, 21, | ||||
| 		24, 26, 28, 30, 32, 34} { | ||||
| 		if v, ok := xtob(b[x], b[x+1]); !ok { | ||||
| 		v, ok := xtob(b[x], b[x+1]) | ||||
| 		if !ok { | ||||
| 			return uuid, errors.New("invalid UUID format") | ||||
| 		} else { | ||||
| 			uuid[i] = v | ||||
| 		} | ||||
| 		uuid[i] = v | ||||
| 	} | ||||
| 	return uuid, nil | ||||
| } | ||||
| 
 | ||||
| // FromBytes creates a new UUID from a byte slice. Returns an error if the slice | ||||
| // does not have a length of 16. The bytes are copied from the slice. | ||||
| func FromBytes(b []byte) (uuid UUID, err error) { | ||||
| 	err = uuid.UnmarshalBinary(b) | ||||
| 	return uuid, err | ||||
| } | ||||
| 
 | ||||
| // Must returns uuid if err is nil and panics otherwise. | ||||
| func Must(uuid UUID, err error) UUID { | ||||
| 	if err != nil { | ||||
|  | ||||
							
								
								
									
										2
									
								
								vendor/github.com/google/uuid/version4.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/google/uuid/version4.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -14,7 +14,7 @@ func New() UUID { | ||||
| 	return Must(NewRandom()) | ||||
| } | ||||
| 
 | ||||
| // NewRandom returns a Random (Version 4) UUID or panics. | ||||
| // NewRandom returns a Random (Version 4) UUID. | ||||
| // | ||||
| // The strength of the UUIDs is based on the strength of the crypto/rand | ||||
| // package. | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| The MIT License (MIT) | ||||
| 
 | ||||
| Copyright (c) 2014 Coda Hale | ||||
| Copyright (c) 2015 Klaus Post | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| @ -9,13 +9,14 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be included in | ||||
| all copies or substantial portions of the Software. | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| THE SOFTWARE. | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
| 
 | ||||
							
								
								
									
										1030
									
								
								vendor/github.com/klauspost/cpuid/cpuid.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1030
									
								
								vendor/github.com/klauspost/cpuid/cpuid.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										42
									
								
								vendor/github.com/klauspost/cpuid/cpuid_386.s
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								vendor/github.com/klauspost/cpuid/cpuid_386.s
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| // Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file. | ||||
| 
 | ||||
| // +build 386,!gccgo | ||||
| 
 | ||||
| // func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32) | ||||
| TEXT ·asmCpuid(SB), 7, $0 | ||||
| 	XORL CX, CX | ||||
| 	MOVL op+0(FP), AX | ||||
| 	CPUID | ||||
| 	MOVL AX, eax+4(FP) | ||||
| 	MOVL BX, ebx+8(FP) | ||||
| 	MOVL CX, ecx+12(FP) | ||||
| 	MOVL DX, edx+16(FP) | ||||
| 	RET | ||||
| 
 | ||||
| // func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32) | ||||
| TEXT ·asmCpuidex(SB), 7, $0 | ||||
| 	MOVL op+0(FP), AX | ||||
| 	MOVL op2+4(FP), CX | ||||
| 	CPUID | ||||
| 	MOVL AX, eax+8(FP) | ||||
| 	MOVL BX, ebx+12(FP) | ||||
| 	MOVL CX, ecx+16(FP) | ||||
| 	MOVL DX, edx+20(FP) | ||||
| 	RET | ||||
| 
 | ||||
| // func xgetbv(index uint32) (eax, edx uint32) | ||||
| TEXT ·asmXgetbv(SB), 7, $0 | ||||
| 	MOVL index+0(FP), CX | ||||
| 	BYTE $0x0f; BYTE $0x01; BYTE $0xd0 // XGETBV
 | ||||
| 	MOVL AX, eax+4(FP) | ||||
| 	MOVL DX, edx+8(FP) | ||||
| 	RET | ||||
| 
 | ||||
| // func asmRdtscpAsm() (eax, ebx, ecx, edx uint32) | ||||
| TEXT ·asmRdtscpAsm(SB), 7, $0 | ||||
| 	BYTE $0x0F; BYTE $0x01; BYTE $0xF9 // RDTSCP
 | ||||
| 	MOVL AX, eax+0(FP) | ||||
| 	MOVL BX, ebx+4(FP) | ||||
| 	MOVL CX, ecx+8(FP) | ||||
| 	MOVL DX, edx+12(FP) | ||||
| 	RET | ||||
							
								
								
									
										42
									
								
								vendor/github.com/klauspost/cpuid/cpuid_amd64.s
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								vendor/github.com/klauspost/cpuid/cpuid_amd64.s
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| // Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file. | ||||
| 
 | ||||
| //+build amd64,!gccgo | ||||
| 
 | ||||
| // func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32) | ||||
| TEXT ·asmCpuid(SB), 7, $0 | ||||
| 	XORQ CX, CX | ||||
| 	MOVL op+0(FP), AX | ||||
| 	CPUID | ||||
| 	MOVL AX, eax+8(FP) | ||||
| 	MOVL BX, ebx+12(FP) | ||||
| 	MOVL CX, ecx+16(FP) | ||||
| 	MOVL DX, edx+20(FP) | ||||
| 	RET | ||||
| 
 | ||||
| // func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32) | ||||
| TEXT ·asmCpuidex(SB), 7, $0 | ||||
| 	MOVL op+0(FP), AX | ||||
| 	MOVL op2+4(FP), CX | ||||
| 	CPUID | ||||
| 	MOVL AX, eax+8(FP) | ||||
| 	MOVL BX, ebx+12(FP) | ||||
| 	MOVL CX, ecx+16(FP) | ||||
| 	MOVL DX, edx+20(FP) | ||||
| 	RET | ||||
| 
 | ||||
| // func asmXgetbv(index uint32) (eax, edx uint32) | ||||
| TEXT ·asmXgetbv(SB), 7, $0 | ||||
| 	MOVL index+0(FP), CX | ||||
| 	BYTE $0x0f; BYTE $0x01; BYTE $0xd0 // XGETBV
 | ||||
| 	MOVL AX, eax+8(FP) | ||||
| 	MOVL DX, edx+12(FP) | ||||
| 	RET | ||||
| 
 | ||||
| // func asmRdtscpAsm() (eax, ebx, ecx, edx uint32) | ||||
| TEXT ·asmRdtscpAsm(SB), 7, $0 | ||||
| 	BYTE $0x0F; BYTE $0x01; BYTE $0xF9 // RDTSCP
 | ||||
| 	MOVL AX, eax+0(FP) | ||||
| 	MOVL BX, ebx+4(FP) | ||||
| 	MOVL CX, ecx+8(FP) | ||||
| 	MOVL DX, edx+12(FP) | ||||
| 	RET | ||||
							
								
								
									
										17
									
								
								vendor/github.com/klauspost/cpuid/detect_intel.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/klauspost/cpuid/detect_intel.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| // Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file. | ||||
| 
 | ||||
| // +build 386,!gccgo amd64,!gccgo | ||||
| 
 | ||||
| package cpuid | ||||
| 
 | ||||
| func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32) | ||||
| func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32) | ||||
| func asmXgetbv(index uint32) (eax, edx uint32) | ||||
| func asmRdtscpAsm() (eax, ebx, ecx, edx uint32) | ||||
| 
 | ||||
| func initCPU() { | ||||
| 	cpuid = asmCpuid | ||||
| 	cpuidex = asmCpuidex | ||||
| 	xgetbv = asmXgetbv | ||||
| 	rdtscpAsm = asmRdtscpAsm | ||||
| } | ||||
							
								
								
									
										23
									
								
								vendor/github.com/klauspost/cpuid/detect_ref.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								vendor/github.com/klauspost/cpuid/detect_ref.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| // Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file. | ||||
| 
 | ||||
| // +build !amd64,!386 gccgo | ||||
| 
 | ||||
| package cpuid | ||||
| 
 | ||||
| func initCPU() { | ||||
| 	cpuid = func(op uint32) (eax, ebx, ecx, edx uint32) { | ||||
| 		return 0, 0, 0, 0 | ||||
| 	} | ||||
| 
 | ||||
| 	cpuidex = func(op, op2 uint32) (eax, ebx, ecx, edx uint32) { | ||||
| 		return 0, 0, 0, 0 | ||||
| 	} | ||||
| 
 | ||||
| 	xgetbv = func(index uint32) (eax, edx uint32) { | ||||
| 		return 0, 0 | ||||
| 	} | ||||
| 
 | ||||
| 	rdtscpAsm = func() (eax, ebx, ecx, edx uint32) { | ||||
| 		return 0, 0, 0, 0 | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										4
									
								
								vendor/github.com/klauspost/cpuid/generate.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/klauspost/cpuid/generate.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| package cpuid | ||||
| 
 | ||||
| //go:generate go run private-gen.go | ||||
| //go:generate gofmt -w ./private | ||||
							
								
								
									
										476
									
								
								vendor/github.com/klauspost/cpuid/private-gen.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										476
									
								
								vendor/github.com/klauspost/cpuid/private-gen.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,476 @@ | ||||
| // +build ignore | ||||
| 
 | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"go/ast" | ||||
| 	"go/parser" | ||||
| 	"go/printer" | ||||
| 	"go/token" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"unicode" | ||||
| 	"unicode/utf8" | ||||
| ) | ||||
| 
 | ||||
| var inFiles = []string{"cpuid.go", "cpuid_test.go"} | ||||
| var copyFiles = []string{"cpuid_amd64.s", "cpuid_386.s", "detect_ref.go", "detect_intel.go"} | ||||
| var fileSet = token.NewFileSet() | ||||
| var reWrites = []rewrite{ | ||||
| 	initRewrite("CPUInfo -> cpuInfo"), | ||||
| 	initRewrite("Vendor -> vendor"), | ||||
| 	initRewrite("Flags -> flags"), | ||||
| 	initRewrite("Detect -> detect"), | ||||
| 	initRewrite("CPU -> cpu"), | ||||
| } | ||||
| var excludeNames = map[string]bool{"string": true, "join": true, "trim": true, | ||||
| 	// cpuid_test.go | ||||
| 	"t": true, "println": true, "logf": true, "log": true, "fatalf": true, "fatal": true, | ||||
| } | ||||
| 
 | ||||
| var excludePrefixes = []string{"test", "benchmark"} | ||||
| 
 | ||||
| func main() { | ||||
| 	Package := "private" | ||||
| 	parserMode := parser.ParseComments | ||||
| 	exported := make(map[string]rewrite) | ||||
| 	for _, file := range inFiles { | ||||
| 		in, err := os.Open(file) | ||||
| 		if err != nil { | ||||
| 			log.Fatalf("opening input", err) | ||||
| 		} | ||||
| 
 | ||||
| 		src, err := ioutil.ReadAll(in) | ||||
| 		if err != nil { | ||||
| 			log.Fatalf("reading input", err) | ||||
| 		} | ||||
| 
 | ||||
| 		astfile, err := parser.ParseFile(fileSet, file, src, parserMode) | ||||
| 		if err != nil { | ||||
| 			log.Fatalf("parsing input", err) | ||||
| 		} | ||||
| 
 | ||||
| 		for _, rw := range reWrites { | ||||
| 			astfile = rw(astfile) | ||||
| 		} | ||||
| 
 | ||||
| 		// Inspect the AST and print all identifiers and literals. | ||||
| 		var startDecl token.Pos | ||||
| 		var endDecl token.Pos | ||||
| 		ast.Inspect(astfile, func(n ast.Node) bool { | ||||
| 			var s string | ||||
| 			switch x := n.(type) { | ||||
| 			case *ast.Ident: | ||||
| 				if x.IsExported() { | ||||
| 					t := strings.ToLower(x.Name) | ||||
| 					for _, pre := range excludePrefixes { | ||||
| 						if strings.HasPrefix(t, pre) { | ||||
| 							return true | ||||
| 						} | ||||
| 					} | ||||
| 					if excludeNames[t] != true { | ||||
| 						//if x.Pos() > startDecl && x.Pos() < endDecl { | ||||
| 						exported[x.Name] = initRewrite(x.Name + " -> " + t) | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 			case *ast.GenDecl: | ||||
| 				if x.Tok == token.CONST && x.Lparen > 0 { | ||||
| 					startDecl = x.Lparen | ||||
| 					endDecl = x.Rparen | ||||
| 					// fmt.Printf("Decl:%s -> %s\n", fileSet.Position(startDecl), fileSet.Position(endDecl)) | ||||
| 				} | ||||
| 			} | ||||
| 			if s != "" { | ||||
| 				fmt.Printf("%s:\t%s\n", fileSet.Position(n.Pos()), s) | ||||
| 			} | ||||
| 			return true | ||||
| 		}) | ||||
| 
 | ||||
| 		for _, rw := range exported { | ||||
| 			astfile = rw(astfile) | ||||
| 		} | ||||
| 
 | ||||
| 		var buf bytes.Buffer | ||||
| 
 | ||||
| 		printer.Fprint(&buf, fileSet, astfile) | ||||
| 
 | ||||
| 		// Remove package documentation and insert information | ||||
| 		s := buf.String() | ||||
| 		ind := strings.Index(buf.String(), "\npackage cpuid") | ||||
| 		s = s[ind:] | ||||
| 		s = "// Generated, DO NOT EDIT,\n" + | ||||
| 			"// but copy it to your own project and rename the package.\n" + | ||||
| 			"// See more at http://github.com/klauspost/cpuid\n" + | ||||
| 			s | ||||
| 
 | ||||
| 		outputName := Package + string(os.PathSeparator) + file | ||||
| 
 | ||||
| 		err = ioutil.WriteFile(outputName, []byte(s), 0644) | ||||
| 		if err != nil { | ||||
| 			log.Fatalf("writing output: %s", err) | ||||
| 		} | ||||
| 		log.Println("Generated", outputName) | ||||
| 	} | ||||
| 
 | ||||
| 	for _, file := range copyFiles { | ||||
| 		dst := "" | ||||
| 		if strings.HasPrefix(file, "cpuid") { | ||||
| 			dst = Package + string(os.PathSeparator) + file | ||||
| 		} else { | ||||
| 			dst = Package + string(os.PathSeparator) + "cpuid_" + file | ||||
| 		} | ||||
| 		err := copyFile(file, dst) | ||||
| 		if err != nil { | ||||
| 			log.Fatalf("copying file: %s", err) | ||||
| 		} | ||||
| 		log.Println("Copied", dst) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // CopyFile copies a file from src to dst. If src and dst files exist, and are | ||||
| // the same, then return success. Copy the file contents from src to dst. | ||||
| func copyFile(src, dst string) (err error) { | ||||
| 	sfi, err := os.Stat(src) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	if !sfi.Mode().IsRegular() { | ||||
| 		// cannot copy non-regular files (e.g., directories, | ||||
| 		// symlinks, devices, etc.) | ||||
| 		return fmt.Errorf("CopyFile: non-regular source file %s (%q)", sfi.Name(), sfi.Mode().String()) | ||||
| 	} | ||||
| 	dfi, err := os.Stat(dst) | ||||
| 	if err != nil { | ||||
| 		if !os.IsNotExist(err) { | ||||
| 			return | ||||
| 		} | ||||
| 	} else { | ||||
| 		if !(dfi.Mode().IsRegular()) { | ||||
| 			return fmt.Errorf("CopyFile: non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String()) | ||||
| 		} | ||||
| 		if os.SameFile(sfi, dfi) { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	err = copyFileContents(src, dst) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // copyFileContents copies the contents of the file named src to the file named | ||||
| // by dst. The file will be created if it does not already exist. If the | ||||
| // destination file exists, all it's contents will be replaced by the contents | ||||
| // of the source file. | ||||
| func copyFileContents(src, dst string) (err error) { | ||||
| 	in, err := os.Open(src) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer in.Close() | ||||
| 	out, err := os.Create(dst) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer func() { | ||||
| 		cerr := out.Close() | ||||
| 		if err == nil { | ||||
| 			err = cerr | ||||
| 		} | ||||
| 	}() | ||||
| 	if _, err = io.Copy(out, in); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	err = out.Sync() | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| type rewrite func(*ast.File) *ast.File | ||||
| 
 | ||||
| // Mostly copied from gofmt | ||||
| func initRewrite(rewriteRule string) rewrite { | ||||
| 	f := strings.Split(rewriteRule, "->") | ||||
| 	if len(f) != 2 { | ||||
| 		fmt.Fprintf(os.Stderr, "rewrite rule must be of the form 'pattern -> replacement'\n") | ||||
| 		os.Exit(2) | ||||
| 	} | ||||
| 	pattern := parseExpr(f[0], "pattern") | ||||
| 	replace := parseExpr(f[1], "replacement") | ||||
| 	return func(p *ast.File) *ast.File { return rewriteFile(pattern, replace, p) } | ||||
| } | ||||
| 
 | ||||
| // parseExpr parses s as an expression. | ||||
| // It might make sense to expand this to allow statement patterns, | ||||
| // but there are problems with preserving formatting and also | ||||
| // with what a wildcard for a statement looks like. | ||||
| func parseExpr(s, what string) ast.Expr { | ||||
| 	x, err := parser.ParseExpr(s) | ||||
| 	if err != nil { | ||||
| 		fmt.Fprintf(os.Stderr, "parsing %s %s at %s\n", what, s, err) | ||||
| 		os.Exit(2) | ||||
| 	} | ||||
| 	return x | ||||
| } | ||||
| 
 | ||||
| // Keep this function for debugging. | ||||
| /* | ||||
| func dump(msg string, val reflect.Value) { | ||||
| 	fmt.Printf("%s:\n", msg) | ||||
| 	ast.Print(fileSet, val.Interface()) | ||||
| 	fmt.Println() | ||||
| } | ||||
| */ | ||||
| 
 | ||||
| // rewriteFile applies the rewrite rule 'pattern -> replace' to an entire file. | ||||
| func rewriteFile(pattern, replace ast.Expr, p *ast.File) *ast.File { | ||||
| 	cmap := ast.NewCommentMap(fileSet, p, p.Comments) | ||||
| 	m := make(map[string]reflect.Value) | ||||
| 	pat := reflect.ValueOf(pattern) | ||||
| 	repl := reflect.ValueOf(replace) | ||||
| 
 | ||||
| 	var rewriteVal func(val reflect.Value) reflect.Value | ||||
| 	rewriteVal = func(val reflect.Value) reflect.Value { | ||||
| 		// don't bother if val is invalid to start with | ||||
| 		if !val.IsValid() { | ||||
| 			return reflect.Value{} | ||||
| 		} | ||||
| 		for k := range m { | ||||
| 			delete(m, k) | ||||
| 		} | ||||
| 		val = apply(rewriteVal, val) | ||||
| 		if match(m, pat, val) { | ||||
| 			val = subst(m, repl, reflect.ValueOf(val.Interface().(ast.Node).Pos())) | ||||
| 		} | ||||
| 		return val | ||||
| 	} | ||||
| 
 | ||||
| 	r := apply(rewriteVal, reflect.ValueOf(p)).Interface().(*ast.File) | ||||
| 	r.Comments = cmap.Filter(r).Comments() // recreate comments list | ||||
| 	return r | ||||
| } | ||||
| 
 | ||||
| // set is a wrapper for x.Set(y); it protects the caller from panics if x cannot be changed to y. | ||||
| func set(x, y reflect.Value) { | ||||
| 	// don't bother if x cannot be set or y is invalid | ||||
| 	if !x.CanSet() || !y.IsValid() { | ||||
| 		return | ||||
| 	} | ||||
| 	defer func() { | ||||
| 		if x := recover(); x != nil { | ||||
| 			if s, ok := x.(string); ok && | ||||
| 				(strings.Contains(s, "type mismatch") || strings.Contains(s, "not assignable")) { | ||||
| 				// x cannot be set to y - ignore this rewrite | ||||
| 				return | ||||
| 			} | ||||
| 			panic(x) | ||||
| 		} | ||||
| 	}() | ||||
| 	x.Set(y) | ||||
| } | ||||
| 
 | ||||
| // Values/types for special cases. | ||||
| var ( | ||||
| 	objectPtrNil = reflect.ValueOf((*ast.Object)(nil)) | ||||
| 	scopePtrNil  = reflect.ValueOf((*ast.Scope)(nil)) | ||||
| 
 | ||||
| 	identType     = reflect.TypeOf((*ast.Ident)(nil)) | ||||
| 	objectPtrType = reflect.TypeOf((*ast.Object)(nil)) | ||||
| 	positionType  = reflect.TypeOf(token.NoPos) | ||||
| 	callExprType  = reflect.TypeOf((*ast.CallExpr)(nil)) | ||||
| 	scopePtrType  = reflect.TypeOf((*ast.Scope)(nil)) | ||||
| ) | ||||
| 
 | ||||
| // apply replaces each AST field x in val with f(x), returning val. | ||||
| // To avoid extra conversions, f operates on the reflect.Value form. | ||||
| func apply(f func(reflect.Value) reflect.Value, val reflect.Value) reflect.Value { | ||||
| 	if !val.IsValid() { | ||||
| 		return reflect.Value{} | ||||
| 	} | ||||
| 
 | ||||
| 	// *ast.Objects introduce cycles and are likely incorrect after | ||||
| 	// rewrite; don't follow them but replace with nil instead | ||||
| 	if val.Type() == objectPtrType { | ||||
| 		return objectPtrNil | ||||
| 	} | ||||
| 
 | ||||
| 	// similarly for scopes: they are likely incorrect after a rewrite; | ||||
| 	// replace them with nil | ||||
| 	if val.Type() == scopePtrType { | ||||
| 		return scopePtrNil | ||||
| 	} | ||||
| 
 | ||||
| 	switch v := reflect.Indirect(val); v.Kind() { | ||||
| 	case reflect.Slice: | ||||
| 		for i := 0; i < v.Len(); i++ { | ||||
| 			e := v.Index(i) | ||||
| 			set(e, f(e)) | ||||
| 		} | ||||
| 	case reflect.Struct: | ||||
| 		for i := 0; i < v.NumField(); i++ { | ||||
| 			e := v.Field(i) | ||||
| 			set(e, f(e)) | ||||
| 		} | ||||
| 	case reflect.Interface: | ||||
| 		e := v.Elem() | ||||
| 		set(v, f(e)) | ||||
| 	} | ||||
| 	return val | ||||
| } | ||||
| 
 | ||||
| func isWildcard(s string) bool { | ||||
| 	rune, size := utf8.DecodeRuneInString(s) | ||||
| 	return size == len(s) && unicode.IsLower(rune) | ||||
| } | ||||
| 
 | ||||
| // match returns true if pattern matches val, | ||||
| // recording wildcard submatches in m. | ||||
| // If m == nil, match checks whether pattern == val. | ||||
| func match(m map[string]reflect.Value, pattern, val reflect.Value) bool { | ||||
| 	// Wildcard matches any expression.  If it appears multiple | ||||
| 	// times in the pattern, it must match the same expression | ||||
| 	// each time. | ||||
| 	if m != nil && pattern.IsValid() && pattern.Type() == identType { | ||||
| 		name := pattern.Interface().(*ast.Ident).Name | ||||
| 		if isWildcard(name) && val.IsValid() { | ||||
| 			// wildcards only match valid (non-nil) expressions. | ||||
| 			if _, ok := val.Interface().(ast.Expr); ok && !val.IsNil() { | ||||
| 				if old, ok := m[name]; ok { | ||||
| 					return match(nil, old, val) | ||||
| 				} | ||||
| 				m[name] = val | ||||
| 				return true | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Otherwise, pattern and val must match recursively. | ||||
| 	if !pattern.IsValid() || !val.IsValid() { | ||||
| 		return !pattern.IsValid() && !val.IsValid() | ||||
| 	} | ||||
| 	if pattern.Type() != val.Type() { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	// Special cases. | ||||
| 	switch pattern.Type() { | ||||
| 	case identType: | ||||
| 		// For identifiers, only the names need to match | ||||
| 		// (and none of the other *ast.Object information). | ||||
| 		// This is a common case, handle it all here instead | ||||
| 		// of recursing down any further via reflection. | ||||
| 		p := pattern.Interface().(*ast.Ident) | ||||
| 		v := val.Interface().(*ast.Ident) | ||||
| 		return p == nil && v == nil || p != nil && v != nil && p.Name == v.Name | ||||
| 	case objectPtrType, positionType: | ||||
| 		// object pointers and token positions always match | ||||
| 		return true | ||||
| 	case callExprType: | ||||
| 		// For calls, the Ellipsis fields (token.Position) must | ||||
| 		// match since that is how f(x) and f(x...) are different. | ||||
| 		// Check them here but fall through for the remaining fields. | ||||
| 		p := pattern.Interface().(*ast.CallExpr) | ||||
| 		v := val.Interface().(*ast.CallExpr) | ||||
| 		if p.Ellipsis.IsValid() != v.Ellipsis.IsValid() { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	p := reflect.Indirect(pattern) | ||||
| 	v := reflect.Indirect(val) | ||||
| 	if !p.IsValid() || !v.IsValid() { | ||||
| 		return !p.IsValid() && !v.IsValid() | ||||
| 	} | ||||
| 
 | ||||
| 	switch p.Kind() { | ||||
| 	case reflect.Slice: | ||||
| 		if p.Len() != v.Len() { | ||||
| 			return false | ||||
| 		} | ||||
| 		for i := 0; i < p.Len(); i++ { | ||||
| 			if !match(m, p.Index(i), v.Index(i)) { | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
| 		return true | ||||
| 
 | ||||
| 	case reflect.Struct: | ||||
| 		for i := 0; i < p.NumField(); i++ { | ||||
| 			if !match(m, p.Field(i), v.Field(i)) { | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
| 		return true | ||||
| 
 | ||||
| 	case reflect.Interface: | ||||
| 		return match(m, p.Elem(), v.Elem()) | ||||
| 	} | ||||
| 
 | ||||
| 	// Handle token integers, etc. | ||||
| 	return p.Interface() == v.Interface() | ||||
| } | ||||
| 
 | ||||
| // subst returns a copy of pattern with values from m substituted in place | ||||
| // of wildcards and pos used as the position of tokens from the pattern. | ||||
| // if m == nil, subst returns a copy of pattern and doesn't change the line | ||||
| // number information. | ||||
| func subst(m map[string]reflect.Value, pattern reflect.Value, pos reflect.Value) reflect.Value { | ||||
| 	if !pattern.IsValid() { | ||||
| 		return reflect.Value{} | ||||
| 	} | ||||
| 
 | ||||
| 	// Wildcard gets replaced with map value. | ||||
| 	if m != nil && pattern.Type() == identType { | ||||
| 		name := pattern.Interface().(*ast.Ident).Name | ||||
| 		if isWildcard(name) { | ||||
| 			if old, ok := m[name]; ok { | ||||
| 				return subst(nil, old, reflect.Value{}) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if pos.IsValid() && pattern.Type() == positionType { | ||||
| 		// use new position only if old position was valid in the first place | ||||
| 		if old := pattern.Interface().(token.Pos); !old.IsValid() { | ||||
| 			return pattern | ||||
| 		} | ||||
| 		return pos | ||||
| 	} | ||||
| 
 | ||||
| 	// Otherwise copy. | ||||
| 	switch p := pattern; p.Kind() { | ||||
| 	case reflect.Slice: | ||||
| 		v := reflect.MakeSlice(p.Type(), p.Len(), p.Len()) | ||||
| 		for i := 0; i < p.Len(); i++ { | ||||
| 			v.Index(i).Set(subst(m, p.Index(i), pos)) | ||||
| 		} | ||||
| 		return v | ||||
| 
 | ||||
| 	case reflect.Struct: | ||||
| 		v := reflect.New(p.Type()).Elem() | ||||
| 		for i := 0; i < p.NumField(); i++ { | ||||
| 			v.Field(i).Set(subst(m, p.Field(i), pos)) | ||||
| 		} | ||||
| 		return v | ||||
| 
 | ||||
| 	case reflect.Ptr: | ||||
| 		v := reflect.New(p.Type()).Elem() | ||||
| 		if elem := p.Elem(); elem.IsValid() { | ||||
| 			v.Set(subst(m, elem, pos).Addr()) | ||||
| 		} | ||||
| 		return v | ||||
| 
 | ||||
| 	case reflect.Interface: | ||||
| 		v := reflect.New(p.Type()).Elem() | ||||
| 		if elem := p.Elem(); elem.IsValid() { | ||||
| 			v.Set(subst(m, elem, pos)) | ||||
| 		} | ||||
| 		return v | ||||
| 	} | ||||
| 
 | ||||
| 	return pattern | ||||
| } | ||||
							
								
								
									
										1024
									
								
								vendor/github.com/klauspost/cpuid/private/cpuid.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1024
									
								
								vendor/github.com/klauspost/cpuid/private/cpuid.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										42
									
								
								vendor/github.com/klauspost/cpuid/private/cpuid_386.s
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								vendor/github.com/klauspost/cpuid/private/cpuid_386.s
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| // Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file. | ||||
| 
 | ||||
| // +build 386,!gccgo | ||||
| 
 | ||||
| // func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32) | ||||
| TEXT ·asmCpuid(SB), 7, $0 | ||||
| 	XORL CX, CX | ||||
| 	MOVL op+0(FP), AX | ||||
| 	CPUID | ||||
| 	MOVL AX, eax+4(FP) | ||||
| 	MOVL BX, ebx+8(FP) | ||||
| 	MOVL CX, ecx+12(FP) | ||||
| 	MOVL DX, edx+16(FP) | ||||
| 	RET | ||||
| 
 | ||||
| // func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32) | ||||
| TEXT ·asmCpuidex(SB), 7, $0 | ||||
| 	MOVL op+0(FP), AX | ||||
| 	MOVL op2+4(FP), CX | ||||
| 	CPUID | ||||
| 	MOVL AX, eax+8(FP) | ||||
| 	MOVL BX, ebx+12(FP) | ||||
| 	MOVL CX, ecx+16(FP) | ||||
| 	MOVL DX, edx+20(FP) | ||||
| 	RET | ||||
| 
 | ||||
| // func xgetbv(index uint32) (eax, edx uint32) | ||||
| TEXT ·asmXgetbv(SB), 7, $0 | ||||
| 	MOVL index+0(FP), CX | ||||
| 	BYTE $0x0f; BYTE $0x01; BYTE $0xd0 // XGETBV
 | ||||
| 	MOVL AX, eax+4(FP) | ||||
| 	MOVL DX, edx+8(FP) | ||||
| 	RET | ||||
| 
 | ||||
| // func asmRdtscpAsm() (eax, ebx, ecx, edx uint32) | ||||
| TEXT ·asmRdtscpAsm(SB), 7, $0 | ||||
| 	BYTE $0x0F; BYTE $0x01; BYTE $0xF9 // RDTSCP
 | ||||
| 	MOVL AX, eax+0(FP) | ||||
| 	MOVL BX, ebx+4(FP) | ||||
| 	MOVL CX, ecx+8(FP) | ||||
| 	MOVL DX, edx+12(FP) | ||||
| 	RET | ||||
							
								
								
									
										42
									
								
								vendor/github.com/klauspost/cpuid/private/cpuid_amd64.s
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								vendor/github.com/klauspost/cpuid/private/cpuid_amd64.s
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| // Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file. | ||||
| 
 | ||||
| //+build amd64,!gccgo | ||||
| 
 | ||||
| // func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32) | ||||
| TEXT ·asmCpuid(SB), 7, $0 | ||||
| 	XORQ CX, CX | ||||
| 	MOVL op+0(FP), AX | ||||
| 	CPUID | ||||
| 	MOVL AX, eax+8(FP) | ||||
| 	MOVL BX, ebx+12(FP) | ||||
| 	MOVL CX, ecx+16(FP) | ||||
| 	MOVL DX, edx+20(FP) | ||||
| 	RET | ||||
| 
 | ||||
| // func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32) | ||||
| TEXT ·asmCpuidex(SB), 7, $0 | ||||
| 	MOVL op+0(FP), AX | ||||
| 	MOVL op2+4(FP), CX | ||||
| 	CPUID | ||||
| 	MOVL AX, eax+8(FP) | ||||
| 	MOVL BX, ebx+12(FP) | ||||
| 	MOVL CX, ecx+16(FP) | ||||
| 	MOVL DX, edx+20(FP) | ||||
| 	RET | ||||
| 
 | ||||
| // func asmXgetbv(index uint32) (eax, edx uint32) | ||||
| TEXT ·asmXgetbv(SB), 7, $0 | ||||
| 	MOVL index+0(FP), CX | ||||
| 	BYTE $0x0f; BYTE $0x01; BYTE $0xd0 // XGETBV
 | ||||
| 	MOVL AX, eax+8(FP) | ||||
| 	MOVL DX, edx+12(FP) | ||||
| 	RET | ||||
| 
 | ||||
| // func asmRdtscpAsm() (eax, ebx, ecx, edx uint32) | ||||
| TEXT ·asmRdtscpAsm(SB), 7, $0 | ||||
| 	BYTE $0x0F; BYTE $0x01; BYTE $0xF9 // RDTSCP
 | ||||
| 	MOVL AX, eax+0(FP) | ||||
| 	MOVL BX, ebx+4(FP) | ||||
| 	MOVL CX, ecx+8(FP) | ||||
| 	MOVL DX, edx+12(FP) | ||||
| 	RET | ||||
							
								
								
									
										17
									
								
								vendor/github.com/klauspost/cpuid/private/cpuid_detect_intel.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/klauspost/cpuid/private/cpuid_detect_intel.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| // Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file. | ||||
| 
 | ||||
| // +build 386,!gccgo amd64,!gccgo | ||||
| 
 | ||||
| package cpuid | ||||
| 
 | ||||
| func asmCpuid(op uint32) (eax, ebx, ecx, edx uint32) | ||||
| func asmCpuidex(op, op2 uint32) (eax, ebx, ecx, edx uint32) | ||||
| func asmXgetbv(index uint32) (eax, edx uint32) | ||||
| func asmRdtscpAsm() (eax, ebx, ecx, edx uint32) | ||||
| 
 | ||||
| func initCPU() { | ||||
| 	cpuid = asmCpuid | ||||
| 	cpuidex = asmCpuidex | ||||
| 	xgetbv = asmXgetbv | ||||
| 	rdtscpAsm = asmRdtscpAsm | ||||
| } | ||||
							
								
								
									
										23
									
								
								vendor/github.com/klauspost/cpuid/private/cpuid_detect_ref.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								vendor/github.com/klauspost/cpuid/private/cpuid_detect_ref.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| // Copyright (c) 2015 Klaus Post, released under MIT License. See LICENSE file. | ||||
| 
 | ||||
| // +build !amd64,!386 gccgo | ||||
| 
 | ||||
| package cpuid | ||||
| 
 | ||||
| func initCPU() { | ||||
| 	cpuid = func(op uint32) (eax, ebx, ecx, edx uint32) { | ||||
| 		return 0, 0, 0, 0 | ||||
| 	} | ||||
| 
 | ||||
| 	cpuidex = func(op, op2 uint32) (eax, ebx, ecx, edx uint32) { | ||||
| 		return 0, 0, 0, 0 | ||||
| 	} | ||||
| 
 | ||||
| 	xgetbv = func(index uint32) (eax, edx uint32) { | ||||
| 		return 0, 0 | ||||
| 	} | ||||
| 
 | ||||
| 	rdtscpAsm = func() (eax, ebx, ecx, edx uint32) { | ||||
| 		return 0, 0, 0, 0 | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										10
									
								
								vendor/manifest
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								vendor/manifest
									
									
									
									
										vendored
									
									
								
							| @ -88,7 +88,7 @@ | ||||
| 			"importpath": "github.com/google/uuid", | ||||
| 			"repository": "https://github.com/google/uuid", | ||||
| 			"vcs": "git", | ||||
| 			"revision": "7e072fc3a7be179aee6d3359e46015aa8c995314", | ||||
| 			"revision": "dec09d789f3dba190787f8b4454c7d3c936fed9e", | ||||
| 			"branch": "master", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| @ -125,6 +125,14 @@ | ||||
| 			"path": "/basic", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| 		{ | ||||
| 			"importpath": "github.com/klauspost/cpuid", | ||||
| 			"repository": "https://github.com/klauspost/cpuid", | ||||
| 			"vcs": "git", | ||||
| 			"revision": "ae832f27941af41db13bd6d8efd2493e3b22415a", | ||||
| 			"branch": "master", | ||||
| 			"notests": true | ||||
| 		}, | ||||
| 		{ | ||||
| 			"importpath": "github.com/lucas-clemente/aes12", | ||||
| 			"repository": "https://github.com/lucas-clemente/aes12", | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user