Perm structs and config
This commit is contained in:
parent
19a48ee7ed
commit
5d48ed258d
105
internal/perm/config.go
Normal file
105
internal/perm/config.go
Normal 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
90
internal/perm/perm.go
Normal 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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user