Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

package middleware

import (
	
	
	
	
	
	
	
	
	

	
	rrate 
	
	
	
	
	
)

var (
	keyQuotaBlocked = tag.MustNewKey("quota.blocked")
	quotaResults    = stats.Int64(
		"go-discovery/quota_result_count",
		"The result of a quota check.",
		stats.UnitDimensionless,
QuotaResultCount is a counter of quota results, by whether the request was blocked or not.
	QuotaResultCount = &view.View{
		Name:        "go-discovery/quota/result_count",
		Measure:     quotaResults,
		Aggregation: view.Count(),
		Description: "quota results, by blocked or allowed",
		TagKeys:     []tag.Key{keyQuotaBlocked},
	}
)

func ( context.Context,  string) {
	stats.RecordWithTags(, []tag.Mutator{
		tag.Upsert(keyQuotaBlocked, ),
	}, quotaResults.M(1))
}

func ( string) string {
First field is the originating IP address.
	 := strings.TrimSpace([0])
	 := net.ParseIP()
	if  == nil {
		return ""
Zero out last byte, to cover ranges of IPv4 addresses. (It's less clear what the effect will be on IPv6 addresses: it will certainly cover a range of them, but we don't know if that range is likely to be allocated to a single organization.)
	[len()-1] = 0
	return .String()
}
Quota implements a simple IP-based rate limiter. Each set of incoming IP addresses with the same low-order byte gets settings.QPS requests per second. Information is kept in a redis instance. If a request is disallowed, a 429 (TooManyRequests) will be served.
func ( config.QuotaSettings,  *redis.Client) Middleware {
	return func( http.Handler) http.Handler {
		return http.HandlerFunc(func( http.ResponseWriter,  *http.Request) {
			 := .Context()
			if !.Enable {
				recordQuotaMetric(, "disabled")
				.ServeHTTP(, )
				return
			}
			 := .Header.Get(config.BypassQuotaAuthHeader)
			for ,  := range .AuthValues {
				if  ==  {
					recordQuotaMetric(, "bypassed")
					log.Infof(, "Quota: accepting %q", )
					.ServeHTTP(, )
					return
				}
			}
			 := .Header.Get("X-Godoc-Forwarded-For")
			if  == "" {
				 = .Header.Get("X-Forwarded-For")
			}
			,  := enforceQuota(, , .QPS, , .HMACKey)
			recordQuotaMetric(, )
			if  && .RecordOnly != nil && !*.RecordOnly {
				const  = http.StatusTooManyRequests
				http.Error(, http.StatusText(), )
				return
			}
			.ServeHTTP(, )
		})
	}
}

Fail open if header is missing or can't be parsed.
	if  == "" {
		return false, "no header"
	}
	 := ipKey()
	if  == "" {
		return false, "bad header"
	}
	 := hmac.New(sha256.New, )
	io.WriteString(, )
	 := string(.Sum(nil))
	,  := rrate.NewLimiter(.WithTimeout(15*time.Millisecond)).Allow(, , rrate.PerSecond())
	if  != nil {
		var  *net.OpError
		if errors.Is(, context.DeadlineExceeded) || (errors.As(, &) && .Timeout()) {
			log.Warningf(, "quota: redis limiter: %v", )
			return false, "timeout"
		}
		log.Errorf(, "quota: redis limiter: %v", )
		return false, "error"
	}
	if .Allowed > 0 {
		return false, "allowed"
	}
	return true, "blocked"