Perm structs and config

This commit is contained in:
Edgaru089 2025-03-27 22:03:38 +08:00
parent 19a48ee7ed
commit 5d48ed258d
2 changed files with 195 additions and 0 deletions

105
internal/perm/config.go Normal file
View File

@ -0,0 +1,105 @@
package perm
import (
"errors"
"strconv"
"strings"
)
// Action defines what to do when a ruleset matches a target address.
//
// The zero value is ActionDeny.
type Action int
// we really should keep the more severe actions numeric less
const (
ActionDeny Action = iota // Returns 502 Bad Gateway, and then logs the offending access.
ActionIgnore // Returns 502 Bad Gateway, and then don't log.
ActionAccept // Connects as usual.
)
// MostSevere returns the most severe of the two actions.
// E.g., ActionIgnore and ActionAccept will return ActionIgnore.
//
// This is the default behavior when the same address is matched multiple times.
func MostSevere(a, b Action) Action {
return min(a, b)
}
// Marshal/Unmarshal for Action
func (a *Action) UnmarshalText(text []byte) error {
switch strings.ToLower(string(text)) {
case "deny":
*a = ActionDeny
case "ignore":
*a = ActionIgnore
case "accept":
*a = ActionAccept
default:
return errors.New("unknown action: " + string(text))
}
return nil
}
func (a Action) MarshalText() ([]byte, error) {
var name string
switch a {
case ActionDeny:
name = "deny"
case ActionIgnore:
name = "ignore"
case ActionAccept:
name = "accept"
default:
name = "<" + strconv.Itoa(int(a)) + ">"
}
return []byte(name), nil
}
// Config is a list of address and actions.
// It can just be Marshal/Unmarshaled into/from json.
type Config struct {
DefaultAction Action // What we should do when no action is matched.
DefaultPort []uint // Port numbers to add to address without port numbers already in them. Don't put too many entries in here.
// Object which holds addresses and optionally ports, mapping to actions.
//
// Port number is extracted by net/url.splitHostPort, copied below.
// Port number is optional, but must be numeric when present.
Match map[string]Action
}
// validOptionalPort reports whether port is either an empty string
// or matches /^:\d*$/
func validOptionalPort(port string) bool {
if port == "" {
return true
}
if port[0] != ':' {
return false
}
for _, b := range port[1:] {
if b < '0' || b > '9' {
return false
}
}
return true
}
// splitHostPort separates host and port. If the port is not valid, it returns
// the entire input as host, and it doesn't check the validity of the host.
// Unlike net.SplitHostPort, but per RFC 3986, it requires ports to be numeric.
func splitHostPort(hostPort string) (host, port string) {
host = hostPort
colon := strings.LastIndexByte(host, ':')
if colon != -1 && validOptionalPort(host[colon:]) {
host, port = host[:colon], host[colon+1:]
}
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
host = host[1 : len(host)-1]
}
return
}

90
internal/perm/perm.go Normal file
View File

@ -0,0 +1,90 @@
package perm
import (
"net"
"strconv"
"sync"
)
// Perm matches address:port strings to Actions
// loaded from a Config.
//
// It's thread safe.
type Perm struct {
lock sync.RWMutex
perm map[string]Action
def Action
}
// New creates a new Perm struct from a Config.
//
// You can also &perm.Perm{} and then call Load on it.
func New(c *Config) (p *Perm) {
p = &Perm{}
p.Load(c)
return
}
// Load loads/reloads the Perm struct.
func (p *Perm) Load(c *Config) {
p.lock.Lock()
defer p.lock.Unlock()
if p.perm == nil {
p.perm = make(map[string]Action)
} else {
clear(p.perm)
}
p.def = c.DefaultAction
// insert helper to use the most severe action existing
insert := func(addrport string, action Action) {
existing_action, ok := p.perm[addrport]
if ok {
p.perm[addrport] = MostSevere(existing_action, action)
} else {
p.perm[addrport] = action
}
}
// loop around the Match map
for addrport, act := range c.Match {
addr, port := splitHostPort(addrport)
if port != "" {
insert(net.JoinHostPort(addr, port), act)
} else {
// so this is why def_port shouldn't be that big
// TODO change this to sth faster
for def_port := range c.DefaultPort {
insert(net.JoinHostPort(addr, strconv.Itoa(def_port)), act)
}
}
}
return
}
// Match matches an address to an action.
// addr must be in net.JoinHostPort format.
func (p *Perm) Match(addr string) Action {
// sanity check
if p == nil {
return ActionDeny
}
p.lock.RLock()
defer p.lock.RUnlock()
// sanity check no.2
if p.perm == nil {
return p.def
}
action, ok := p.perm[addr]
if !ok {
return p.def
}
return action
}