Files
routedns/mac-db.go
2025-01-14 07:06:13 +01:00

121 lines
2.8 KiB
Go

package rdns
import (
"bytes"
"encoding/hex"
"fmt"
"net"
"strings"
"github.com/miekg/dns"
)
// MACDB holds a list of MAC addresses used to clients with the given MAC (as per
// EDNS0 option 65001).
type MACDB struct {
name string
loader BlocklistLoader
macs [][]byte // TODO: for large lists, a trie would be more efficient
}
var _ BlocklistDB = &MACDB{}
// NewMACDB returns a new instance of a matcher for a list of MAC addresses.
func NewMACDB(name string, loader BlocklistLoader) (*MACDB, error) {
rules, err := loader.Load()
if err != nil {
return nil, err
}
db := &MACDB{
name: name,
macs: make([][]byte, 0, len(rules)),
loader: loader,
}
for _, r := range rules {
r = strings.TrimSpace(r)
if strings.HasPrefix(r, "#") || r == "" {
continue
}
mac, err := parseMAC(r)
if err != nil {
return nil, err
}
db.macs = append(db.macs, mac)
}
return db, nil
}
func (m *MACDB) Reload() (BlocklistDB, error) {
return NewMACDB(m.name, m.loader)
}
func (m *MACDB) Match(msg *dns.Msg) ([]net.IP, []string, *BlocklistMatch, bool) {
// Do we have an EDNS0 record?
edns0 := msg.IsEdns0()
if edns0 == nil {
return nil, nil, nil, false
}
// Check if there's an option 65001 in it
var opt65001 []byte
for _, opt := range edns0.Option {
// Option 65001 is currently decoded into *dns.EDNS0_LOCAL. This
// will break when/if it's standardized and gets a dedicate type.
local, ok := opt.(*dns.EDNS0_LOCAL)
if !ok {
continue
}
if local.Code != 65001 {
continue
}
if len(local.Data) != 6 { // Not a MAC?
continue
}
opt65001 = local.Data
}
// Did we find a EDNS0 record with option 65001
if len(opt65001) != 6 {
return nil, nil, nil, false
}
// Match against the MAC addresses on the blocklist
for _, mac := range m.macs {
if bytes.Equal(mac, opt65001) {
return nil, nil, &BlocklistMatch{List: m.name, Rule: hex.EncodeToString(mac)}, true
}
}
return nil, nil, nil, false
}
func (m *MACDB) Close() error {
return nil
}
func (m *MACDB) String() string {
return "MAC-blocklist"
}
// ParseMAC decodes a MAC address given in the format 01:23:45:ab:cd:ef to
// an array of 6 bytes.
func parseMAC(addr string) ([]byte, error) {
b := []byte(addr)
if len(b) != 17 { // check total length
return nil, fmt.Errorf("unable to parse mac address %q, expected format 01:23:45:ab:cd:ef", addr)
}
// Check the format, we need 6 parts with 5 separator characters (:) in it
for i := 0; i < 5; i++ {
if b[(i*3)+2] != ':' {
return nil, fmt.Errorf("unable to parse mac address %q, expected format 01:23:45:ab:cd:ef", addr)
}
}
b = bytes.ReplaceAll(b, []byte{':'}, nil)
out := make([]byte, 6)
n, err := hex.Decode(out[:], b)
if err != nil || n != 6 {
return nil, fmt.Errorf("unable to parse mac address %q, expected format 01:23:45:ab:cd:ef", addr)
}
return out, nil
}