diff --git a/bittorrent/request_sanitizer.go b/bittorrent/request_sanitizer.go deleted file mode 100644 index 1e6c48a..0000000 --- a/bittorrent/request_sanitizer.go +++ /dev/null @@ -1,61 +0,0 @@ -package bittorrent - -import ( - "net" - - log "github.com/sirupsen/logrus" -) - -// ErrInvalidIP indicates an invalid IP for an Announce. -var ErrInvalidIP = ClientError("invalid IP") - -// RequestSanitizer is used to replace unreasonable values in requests parsed -// from a frontend into sane values. -type RequestSanitizer struct { - MaxNumWant uint32 `yaml:"max_numwant"` - DefaultNumWant uint32 `yaml:"default_numwant"` - MaxScrapeInfoHashes uint32 `yaml:"max_scrape_infohashes"` -} - -// SanitizeAnnounce enforces a max and default NumWant and coerces the peer's -// IP address into the proper format. -func (rs *RequestSanitizer) SanitizeAnnounce(r *AnnounceRequest) error { - if !r.NumWantProvided { - r.NumWant = rs.DefaultNumWant - } else if r.NumWant > rs.MaxNumWant { - r.NumWant = rs.MaxNumWant - } - - if ip := r.Peer.IP.To4(); ip != nil { - r.Peer.IP.IP = ip - r.Peer.IP.AddressFamily = IPv4 - } else if len(r.Peer.IP.IP) == net.IPv6len { // implies r.Peer.IP.To4() == nil - r.Peer.IP.AddressFamily = IPv6 - } else { - return ErrInvalidIP - } - - log.Debug("sanitized announce", rs, r) - return nil -} - -// SanitizeScrape enforces a max number of infohashes for a single scrape -// request. -func (rs *RequestSanitizer) SanitizeScrape(r *ScrapeRequest) error { - if len(r.InfoHashes) > int(rs.MaxScrapeInfoHashes) { - r.InfoHashes = r.InfoHashes[:rs.MaxScrapeInfoHashes] - } - - log.Debug("sanitized scrape", rs, r) - return nil -} - -// LogFields renders the request sanitizer's configuration as a set of loggable -// fields. -func (rs *RequestSanitizer) LogFields() log.Fields { - return log.Fields{ - "maxNumWant": rs.MaxNumWant, - "defaultNumWant": rs.DefaultNumWant, - "maxScrapeInfohashes": rs.MaxScrapeInfoHashes, - } -} diff --git a/bittorrent/sanitize.go b/bittorrent/sanitize.go new file mode 100644 index 0000000..9cc7402 --- /dev/null +++ b/bittorrent/sanitize.go @@ -0,0 +1,48 @@ +package bittorrent + +import ( + "net" + + "github.com/chihaya/chihaya/pkg/log" +) + +// ErrInvalidIP indicates an invalid IP for an Announce. +var ErrInvalidIP = ClientError("invalid IP") + +// SanitizeAnnounce enforces a max and default NumWant and coerces the peer's +// IP address into the proper format. +func SanitizeAnnounce(r *AnnounceRequest, maxNumWant, defaultNumWant uint32) error { + if !r.NumWantProvided { + r.NumWant = defaultNumWant + } else if r.NumWant > maxNumWant { + r.NumWant = maxNumWant + } + + if ip := r.Peer.IP.To4(); ip != nil { + r.Peer.IP.IP = ip + r.Peer.IP.AddressFamily = IPv4 + } else if len(r.Peer.IP.IP) == net.IPv6len { // implies r.Peer.IP.To4() == nil + r.Peer.IP.AddressFamily = IPv6 + } else { + return ErrInvalidIP + } + + log.Debug("sanitized announce", r, log.Fields{ + "maxNumWant": maxNumWant, + "defaultNumWant": defaultNumWant, + }) + return nil +} + +// SanitizeScrape enforces a max number of infohashes for a single scrape +// request. +func SanitizeScrape(r *ScrapeRequest, maxScrapeInfoHashes uint32) error { + if len(r.InfoHashes) > int(maxScrapeInfoHashes) { + r.InfoHashes = r.InfoHashes[:maxScrapeInfoHashes] + } + + log.Debug("sanitized scrape", r, log.Fields{ + "maxScrapeInfoHashes": maxScrapeInfoHashes, + }) + return nil +} diff --git a/frontend/http/frontend.go b/frontend/http/frontend.go index dc10dab..1ed1323 100644 --- a/frontend/http/frontend.go +++ b/frontend/http/frontend.go @@ -21,9 +21,6 @@ func init() { prometheus.MustRegister(promResponseDurationMilliseconds) } -// ErrInvalidIP indicates an invalid IP. -var ErrInvalidIP = bittorrent.ClientError("invalid IP") - var promResponseDurationMilliseconds = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: "chihaya_http_response_duration_milliseconds", @@ -84,7 +81,7 @@ func (cfg Config) LogFields() log.Fields { "realIPHeader": cfg.RealIPHeader, "maxNumWant": cfg.MaxNumWant, "defaultNumWant": cfg.DefaultNumWant, - "maxScrapeInfohashes": cfg.MaxScrapeInfoHashes, + "maxScrapeInfoHashes": cfg.MaxScrapeInfoHashes, } } @@ -280,7 +277,7 @@ func (f *Frontend) scrapeRoute(w http.ResponseWriter, r *http.Request, _ httprou req.AddressFamily = bittorrent.IPv6 } else { log.Error("http: invalid IP: neither v4 nor v6", log.Fields{"RemoteAddr": r.RemoteAddr}) - WriteError(w, ErrInvalidIP) + WriteError(w, bittorrent.ErrInvalidIP) return } af = new(bittorrent.AddressFamily) diff --git a/frontend/http/parser.go b/frontend/http/parser.go index 29178bd..c463936 100644 --- a/frontend/http/parser.go +++ b/frontend/http/parser.go @@ -13,9 +13,11 @@ import ( // If RealIPHeader is not empty string, the value of the first HTTP Header with // that name will be used. type ParseOptions struct { - AllowIPSpoofing bool `yaml:"allowIPSpoofing"` - RealIPHeader string `yaml:"realIPHeader"` - bittorrent.RequestSanitizer `yaml:",inline"` + AllowIPSpoofing bool `yaml:"allow_ip_spoofing"` + RealIPHeader string `yaml:"real_ip_header"` + MaxNumWant uint32 `yaml:"max_numwant"` + DefaultNumWant uint32 `yaml:"default_numwant"` + MaxScrapeInfoHashes uint32 `yaml:"max_scrape_infohashes"` } // ParseAnnounce parses an bittorrent.AnnounceRequest from an http.Request. @@ -102,7 +104,7 @@ func ParseAnnounce(r *http.Request, opts ParseOptions) (*bittorrent.AnnounceRequ return nil, bittorrent.ClientError("failed to parse peer IP address") } - if err := opts.SanitizeAnnounce(request); err != nil { + if err := bittorrent.SanitizeAnnounce(request, opts.MaxNumWant, opts.DefaultNumWant); err != nil { return nil, err } @@ -126,7 +128,7 @@ func ParseScrape(r *http.Request, opts ParseOptions) (*bittorrent.ScrapeRequest, Params: qp, } - if err := opts.SanitizeScrape(request); err != nil { + if err := bittorrent.SanitizeScrape(request, opts.MaxScrapeInfoHashes); err != nil { return nil, err } diff --git a/frontend/udp/frontend.go b/frontend/udp/frontend.go index e50d182..54a3b23 100644 --- a/frontend/udp/frontend.go +++ b/frontend/udp/frontend.go @@ -26,9 +26,6 @@ func init() { prometheus.MustRegister(promResponseDurationMilliseconds) } -// ErrInvalidIP indicates an invalid IP. -var ErrInvalidIP = bittorrent.ClientError("invalid IP") - var promResponseDurationMilliseconds = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: "chihaya_udp_response_duration_milliseconds", @@ -84,7 +81,7 @@ func (cfg Config) LogFields() log.Fields { "allowIPSpoofing": cfg.AllowIPSpoofing, "maxNumWant": cfg.MaxNumWant, "defaultNumWant": cfg.DefaultNumWant, - "maxScrapeInfohashes": cfg.MaxScrapeInfoHashes, + "maxScrapeInfoHashes": cfg.MaxScrapeInfoHashes, } } @@ -317,7 +314,7 @@ func (t *Frontend) handleRequest(r Request, w ResponseWriter) (actionName string req.AddressFamily = bittorrent.IPv6 } else { log.Error("udp: invalid IP: neither v4 nor v6", log.Fields{"IP": r.IP}) - WriteError(w, txID, ErrInvalidIP) + WriteError(w, txID, bittorrent.ErrInvalidIP) return } af = new(bittorrent.AddressFamily) diff --git a/frontend/udp/parser.go b/frontend/udp/parser.go index e35e3b4..5a668f6 100644 --- a/frontend/udp/parser.go +++ b/frontend/udp/parser.go @@ -49,8 +49,10 @@ var ( // // If AllowIPSpoofing is true, IPs provided via params will be used. type ParseOptions struct { - AllowIPSpoofing bool `yaml:"allowIPSpoofing"` - bittorrent.RequestSanitizer `yaml:",inline"` + AllowIPSpoofing bool `yaml:"allow_ip_spoofing"` + MaxNumWant uint32 `yaml:"max_numwant"` + DefaultNumWant uint32 `yaml:"default_numwant"` + MaxScrapeInfoHashes uint32 `yaml:"max_scrape_infohashes"` } // ParseAnnounce parses an AnnounceRequest from a UDP request. @@ -117,7 +119,7 @@ func ParseAnnounce(r Request, v6 bool, opts ParseOptions) (*bittorrent.AnnounceR Params: params, } - if err := opts.SanitizeAnnounce(request); err != nil { + if err := bittorrent.SanitizeAnnounce(request, opts.MaxNumWant, opts.DefaultNumWant); err != nil { return nil, err } @@ -208,7 +210,7 @@ func ParseScrape(r Request, opts ParseOptions) (*bittorrent.ScrapeRequest, error // Sanitize the request. request := &bittorrent.ScrapeRequest{InfoHashes: infohashes} - if err := opts.SanitizeScrape(request); err != nil { + if err := bittorrent.SanitizeScrape(request, opts.MaxScrapeInfoHashes); err != nil { return nil, err }