refactor visitor IPs and allow exempting IP Ranges
Use netip.Addr instead of storing addresses as strings. This requires conversions at the database level and in tests, but is more memory efficient otherwise, and facilitates the following. Parse rate limit exemptions as netip.Prefix. This allows storing IP ranges in the exemption list. Regular IP addresses (entered explicitly or resolved from hostnames) are IPV4/32, denoting a range of one address.
This commit is contained in:
parent
e0ad926ce9
commit
c2382d29a1
12 changed files with 106 additions and 42 deletions
|
@ -11,6 +11,7 @@ import (
|
|||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
|
@ -42,7 +43,7 @@ type Server struct {
|
|||
smtpServerBackend *smtpBackend
|
||||
smtpSender mailer
|
||||
topics map[string]*topic
|
||||
visitors map[string]*visitor
|
||||
visitors map[netip.Addr]*visitor
|
||||
firebaseClient *firebaseClient
|
||||
messages int64
|
||||
auth auth.Auther
|
||||
|
@ -150,7 +151,7 @@ func New(conf *Config) (*Server, error) {
|
|||
smtpSender: mailer,
|
||||
topics: topics,
|
||||
auth: auther,
|
||||
visitors: make(map[string]*visitor),
|
||||
visitors: make(map[netip.Addr]*visitor),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -642,8 +643,8 @@ func (s *Server) parsePublishParams(r *http.Request, v *visitor, m *message) (ca
|
|||
return false, false, "", false, errHTTPBadRequestDelayTooLarge
|
||||
}
|
||||
m.Time = delay.Unix()
|
||||
m.Sender = v.ip // Important for rate limiting
|
||||
}
|
||||
m.Sender = v.ip // Important for rate limiting
|
||||
actionsStr := readParam(r, "x-actions", "actions", "action")
|
||||
if actionsStr != "" {
|
||||
m.Actions, err = parseActions(actionsStr)
|
||||
|
@ -1219,7 +1220,7 @@ func (s *Server) runFirebaseKeepaliver() {
|
|||
if s.firebaseClient == nil {
|
||||
return
|
||||
}
|
||||
v := newVisitor(s.config, s.messageCache, "0.0.0.0") // Background process, not a real visitor
|
||||
v := newVisitor(s.config, s.messageCache, netip.MustParseAddr("0.0.0.0")) // Background process, not a real visitor
|
||||
for {
|
||||
select {
|
||||
case <-time.After(s.config.FirebaseKeepaliveInterval):
|
||||
|
@ -1286,7 +1287,7 @@ func (s *Server) sendDelayedMessage(v *visitor, m *message) error {
|
|||
|
||||
func (s *Server) limitRequests(next handleFunc) handleFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
||||
if util.Contains(s.config.VisitorRequestExemptIPAddrs, v.ip) {
|
||||
if util.ContainsContains(s.config.VisitorRequestExemptIPAddrs, v.ip) {
|
||||
return next(w, r, v)
|
||||
} else if err := v.RequestAllowed(); err != nil {
|
||||
return errHTTPTooManyRequestsLimitRequests
|
||||
|
@ -1436,21 +1437,29 @@ func extractUserPass(r *http.Request) (username string, password string, ok bool
|
|||
// This function was taken from https://www.alexedwards.net/blog/how-to-rate-limit-http-requests (MIT).
|
||||
func (s *Server) visitor(r *http.Request) *visitor {
|
||||
remoteAddr := r.RemoteAddr
|
||||
ip, _, err := net.SplitHostPort(remoteAddr)
|
||||
ipport, err := netip.ParseAddrPort(remoteAddr)
|
||||
ip := ipport.Addr()
|
||||
if err != nil {
|
||||
ip = remoteAddr // This should not happen in real life; only in tests.
|
||||
ip = netip.MustParseAddr(remoteAddr) // This should not happen in real life; only in tests. So, using MustParse, which panics on error.
|
||||
}
|
||||
if s.config.BehindProxy && strings.TrimSpace(r.Header.Get("X-Forwarded-For")) != "" {
|
||||
// X-Forwarded-For can contain multiple addresses (see #328). If we are behind a proxy,
|
||||
// only the right-most address can be trusted (as this is the one added by our proxy server).
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For for details.
|
||||
ips := util.SplitNoEmpty(r.Header.Get("X-Forwarded-For"), ",")
|
||||
ip = strings.TrimSpace(util.LastString(ips, remoteAddr))
|
||||
myip, err := netip.ParseAddr(strings.TrimSpace(util.LastString(ips, remoteAddr)))
|
||||
if err != nil {
|
||||
log.Error("Invalid IP Address Received from proxy in X-Forwarded-For header. This should NEVER happen, your proxy is seriously broken: ", ip, err)
|
||||
// fall back to regular remote address if x forwarded for is damaged
|
||||
} else {
|
||||
ip = myip
|
||||
}
|
||||
|
||||
}
|
||||
return s.visitorFromIP(ip)
|
||||
}
|
||||
|
||||
func (s *Server) visitorFromIP(ip string) *visitor {
|
||||
func (s *Server) visitorFromIP(ip netip.Addr) *visitor {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
v, exists := s.visitors[ip]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue