connlimit

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 22, 2020 License: MPL-2.0 Imports: 7 Imported by: 30

README

Go Server Client Connection Tracking

This package provides a library for network servers to track how many concurrent connections they have from a given client address.

It's designed to be very simple and shared between several HashiCorp products that provide network servers and need this kind of control to impose limits on the resources that can be consumed by a single client.

Usage

TCP Server
// During server setup:
s.limiter = NewLimiter(Config{
  MaxConnsPerClientIP: 10,
})

// handleConn is called in its own goroutine for each net.Conn accepted by
// a net.Listener.
func (s *Server) handleConn(conn net.Conn) {
  defer conn.Close()

  // Track the connection
  free, err := s.limiter.Accept(conn)
  if err != nil {
    // Not accepted as limit has been reached (or some other error), log error
    // or warning and close.

    // The standard err.Error() message when limit is reached is generic so it
    // doesn't leak information which may potentially be sensitive (e.g. current
    // limits set or number of connections). This also allows comparison to
    // ErrPerClientIPLimitReached if it's important to handle it differently
    // from an internal library or io error (currently not possible but might be
    // in the future if additional functionality is added).

    // If you would like to log more information about the current limit that
    // can be obtained with s.limiter.Config().
    return
  }
  // Defer a call to free to decrement the counter for this client IP once we
  // are done with this conn.
  defer free()


  // Handle the conn
}
HTTP Server
lim := NewLimiter(Config{
  MaxConnsPerClientIP: 10,
})
s := http.Server{
  // Other config here
  ConnState: lim.HTTPConnStateFunc(),
}
Dynamic Configuration

The limiter supports dynamic reconfiguration. At any time, any goroutine may call limiter.SetConfig(c Config) which will atomically update the config. All subsequent calls to Accept will use the newly configured limits in their decisions and calls to limiter.Config() will return the new config.

Note that if the limits are reduced that will only prevent further connections beyond the new limit - existing connections are not actively closed to meet the limit. In cases where this is critical it's often preferable to mitigate in a more focussed way e.g. by adding an iptables rule that blocks all connections from one malicious client without affecting the whole server.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrPerClientIPLimitReached is returned if accepting a new conn would exceed
	// the per-client-ip limit set.
	ErrPerClientIPLimitReached = errors.New("client connection limit reached")
)

Functions

func Wrap

func Wrap(conn net.Conn, free func()) net.Conn

Wrap wraps a net.Conn's Close method so free() is called when Close is called. Useful when handing off tracked connections to libraries that close them.

Types

type Config

type Config struct {
	// MaxConnsPerClientIP limits how many concurrent connections are allowed from
	// a given client IP. The IP is the one reported by the connection so cannot
	// be relied upon if clients are connecting through multiple proxies or able
	// to spoof their source IP address in some way. Similarly, multiple clients
	// connected via a proxy or NAT gateway or similar will all be seen as coming
	// from the same IP and so limited as one client.
	MaxConnsPerClientIP int
}

Config is the configuration for the limiter.

type Limiter

type Limiter struct {
	// contains filtered or unexported fields
}

Limiter implements a simple limiter that tracks the number of connections from each client IP. It may be used in it's zero value although no limits will be configured initially - they can be set later with SetConfig.

func NewLimiter

func NewLimiter(cfg Config) *Limiter

NewLimiter returns a limiter with the specified config.

func (*Limiter) Accept

func (l *Limiter) Accept(conn net.Conn) (func(), error)

Accept is called as early as possible when handling a new conn. If the connection should be accepted according to the Limiter's Config, it will return a free func and nil error. The free func must be called when the connection is no longer being handled - typically in a defer statement in the main connection handling goroutine, this will decrement the counter for that client IP. If the configured limit has been reached, a no-op func is returned (doesn't need to be called), and ErrPerClientIPLimitReached is returned.

If any other error is returned it signifies something wrong with the config or transient failure to read or parse the remote IP. The free func will be a no-op in this case and need not be called.

func (*Limiter) Config

func (l *Limiter) Config() Config

Config returns the current limiter configuration. It is safe to call from any goroutine and does not block new connections being accepted.

func (*Limiter) HTTPConnStateFunc

func (l *Limiter) HTTPConnStateFunc() func(net.Conn, http.ConnState)

HTTPConnStateFunc is here for ascending compatibility reasons.

func (*Limiter) HTTPConnStateFuncWithDefault429Handler added in v0.3.0

func (l *Limiter) HTTPConnStateFuncWithDefault429Handler(writeDeadlineMaxDelay time.Duration) func(net.Conn, http.ConnState)

HTTPConnStateFuncWithDefault429Handler return an HTTP 429 if too many connections occur. BEWARE that returning HTTP 429 is done on critical path, you might choose to use HTTPConnStateFuncWithErrorHandler if you want to use a non-blocking strategy.

func (*Limiter) HTTPConnStateFuncWithErrorHandler added in v0.3.0

func (l *Limiter) HTTPConnStateFuncWithErrorHandler(errorHandler func(error, net.Conn)) func(net.Conn, http.ConnState)

HTTPConnStateFuncWithErrorHandler returns a func that can be passed as the ConnState field of an http.Server. This intercepts new HTTP connections to the server and applies the limiting to new connections.

Note that if the conn is hijacked from the HTTP server then it will be freed in the limiter as if it was closed. Servers that use Hijacking must implement their own calls if they need to continue limiting the number of concurrent hijacked connections. errorHandler MUST close the connection itself

func (*Limiter) NumOpen

func (l *Limiter) NumOpen(addr net.Addr) int

func (*Limiter) SetConfig

func (l *Limiter) SetConfig(c Config)

SetConfig dynamically updates the limiter configuration. It is safe to call from any goroutine. Note that if the limit is lowered, active conns will not be closed and may remain over the limit until they close naturally.

type WrappedConn

type WrappedConn struct {
	net.Conn
	// contains filtered or unexported fields
}

WrappedConn wraps a net.Conn and free() func returned by Limiter.Accept so that when the wrapped connections Close method is called, its free func is also called.

func (*WrappedConn) Close

func (w *WrappedConn) Close() error

Close frees the tracked connection and closes the underlying net.Conn.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL