Skip to content
/ rrl Public

Response Rate Limiting (RRL) package for DNS servers

License

Notifications You must be signed in to change notification settings

markdingo/rrl

Repository files navigation

rrl - Response Rate Limiting for DNS Servers

Introduction

rrl is a standalone go package which implements the ISC Response Rate Limiting algorithms as originally implemented in Bind 9. The goal of "Response Rate Limiting" is to help authoritative DNS servers mitigate against being used as part of an amplification attack. Such attacks are very easy to orchestrate since most authoritative DNS servers respond to UDP queries from any putative source address.

If you are the developer of an authoritative DNS server then in the interest of Internet hygiene you really should incorporate a "Response Rate Limiting" capability - whether with this package or some other. If you don't, your server is more vulnerable to being used as part of an amplification attack which will not be regarded highly by other DNS operators.

This package is designed to be very easy to use. It consists of a configuration mechanism and a single public function to check limits. That's it; that's the interface.

If you use miekg/dns you might find it convenient to use markdingo/miekgrrl which provides an adaptor function for passing miekg.dns.Msg attributes to this package.

Genesis

This package is derived from coredns/rrl which mimics the ISC algorithms.

The main differences between this package and coredns/rrl is that all coredns dependencies and external interfaces have been removed so that this package can be used by programs unrelated to coredns. For example the external logging and statistics functions have been removed and are now the responsibility of the caller. In short, all external interactions and dependencies have been eliminated, but otherwise the underlying implementation is largely unchanged.

(Needless to say, this package only exists because of the efforts of the coredns/rrl developers. A big "thank you" to them.)

Project Status

Build Status codecov CodeQL Go Report Card Go Reference

Description

rrl is called by an authoritative DNS server prior to sending each response to a query. rrl tracks the query-per-second rate in a unique "account" assigned to each "Response Tuple" destined for a particular Client Network.

"Accounts" are credited each second with a configured amount and debited once for each call to [Debit]. At most an "account" can gain up to one second of credits or up to a configurable 15 seconds of debits. While the "account" is in credit rrl indicates that the caller should send their planned response. Otherwise rrl indicates that the caller should Drop or 'Slip' their response.

The "Response Tuple" is formulated from the most salient features of the response message. This formulation is somewhat convoluted because DNS responses are somewhat convoluted.

'Slip' is ISC terminology which means to respond with a BADCOOKIE response or a truncated response depending on whether the query contained a valid client cookie or not. The goal of a 'Slip' response is to give genuine clients a small chance of getting a response even when their source addresses are in a range being used as part of an amplification attack.

rrl plays no part in processing DNS messages or modifying them for output - it solely tracks rate-limiting "accounts" and returns a recommended course of action. All DNS actions, statistics gathering and logging are the responsibility of the caller.

"Response Tuple" and Client Network

"Response Tuple" and Client Network are used to uniquely identity rate-limiting "accounts". In effect they form keys to an internal rrl "accounts" cache.

A "Response Tuple" is formulated from various features of the response message - the exact details depend on the nature of the response (NXDomain, Error, referral, etc). To paraphrase ISC, the formulation of the "Response Tuple" is not simplistic. The intent is for responses indicative of potential abuse to be assigned to a small set of tuples whereas responses indicative of genuine requests are assigned to a large set of tuples. The goal being to cause "accounts" of suspect queries to run out of credits far sooner than the "accounts" of genuine queries.

The package documentation describes how to formulate a "Response Tuple".

A Client Network is the putative source address of the request masked by the configured size of the "network". The default configured sizes being 24 for ipv4 and 56 for ipv6.

ISC Terminology

As a general rule, this documentation uses ISC terminology, such as "accounts" and "debits" and so on. The one exception being "Response Tuple" which is used in preference to "Identical Response", or "Token" in coredns/rrl parlance. While there is obvious merit in common terminology, "Response Tuple" seem to better convey intent and outcome.

Sample Code

package main

import "github.com/markdingo/rrl"

func main() {

  server:= dnsListenSocket()
  db := myDatabase()

  cfg := rrl.NewConfig()
  cfg.SetValue(...)             // Configure limits relevant to our deployment
  R := NewRRL(cfg)              // Create our `rrl` instance

  for {
      srcIP, request := server.GetRequest()      // Accept a query
      response := db.lookupResponse(request)     // Create the response

      tuple := makeTuple(response)               // Formulate the "Response Tuple"...
      action, _, _ := R.Debit(srcIP, tuple)      // ... and debit the corresponding accounts

      switch action {                            // Dispatch on the recommended action

      case rrl.Drop:                             // Drop is easy, do nothing

      case rrl.Send:                             // No rate limit applies, ship it!
          server.Send(response)

      case rrl.Slip:
          if request.ValidClientCookie() {       // Slip response varies depending on
              server.SendBadCookie(response)     // whether the client sent a cooke or not
          } else {
              response.makeTruncatedIfAble()     // No valid client cookie means
              server.Send(response)              // send a truncated response
          }
      }
  }
}

Note that some error responses such as REFUSED and SERVFAIL cannot be replaced with truncated responses thus the makeTruncatedIfAble function needs some intelligence.

Installation

rrl requires go version 1.19 or later.

Once your application imports "github.com/markdingo/rrl", then "go build" or "go mod tidy" in your application directory should download and compile rrl automatically.

Further Reading

The rrl API is described in the package documentation which is mirrored online at pkg.go.dev. Other background material can be found at the coredns rrl plugin home page.

Community

If you have any problems using rrl or suggestions on how it can do a better job, don't hesitate to create an issue on the project home page. This package can only improve with your feedback.

Motivation

rrl was originally created for autoreverse, the no muss, no fuss reverse DNS server; check it out if you want an example of how rrl is used in the wild.

Copyright and License

rrl is Copyright ©️ 2023 Mark Delany and is licensed under the BSD 2-Clause "Simplified" License.