Files
tomswall/internal/config/portgroups.go
T
unkinben 2a3eb3b04d Initial scaffold for tomswall
Spiritual successor to shorewall — manages nftables directly via
google/nftables. Reads a single YAML config covering zones, interfaces,
hosts, policy, rules, snat, and named portgroups. Computes differential
changes against the running nftables state and applies them atomically.
Supports detecting and purging rules added outside of tomswall.
2026-06-28 23:43:16 +10:00

59 lines
1.6 KiB
Go

package config
import (
"fmt"
"strconv"
"strings"
)
type PortGroup struct {
Proto string `yaml:"proto"`
Ports PortSpec `yaml:"ports"`
}
// ParsedPorts returns individual port numbers and ranges as (start, end) pairs.
func (pg *PortGroup) ParsedPorts() (singles []uint16, ranges [][2]uint16, err error) {
for _, p := range pg.Ports {
if strings.Contains(p, "-") {
parts := strings.SplitN(p, "-", 2)
start, err := strconv.ParseUint(parts[0], 10, 16)
if err != nil {
return nil, nil, fmt.Errorf("invalid port range start %q: %w", parts[0], err)
}
end, err := strconv.ParseUint(parts[1], 10, 16)
if err != nil {
return nil, nil, fmt.Errorf("invalid port range end %q: %w", parts[1], err)
}
if start > end {
return nil, nil, fmt.Errorf("port range %d-%d: start > end", start, end)
}
ranges = append(ranges, [2]uint16{uint16(start), uint16(end)})
} else {
port, err := strconv.ParseUint(p, 10, 16)
if err != nil {
return nil, nil, fmt.Errorf("invalid port %q: %w", p, err)
}
singles = append(singles, uint16(port))
}
}
return singles, ranges, nil
}
func (c *Config) validatePortGroups() error {
for name, pg := range c.PortGroups {
if pg.Proto == "" {
return fmt.Errorf("portgroup %q: proto required", name)
}
if pg.Proto != "tcp" && pg.Proto != "udp" {
return fmt.Errorf("portgroup %q: proto must be tcp or udp, got %q", name, pg.Proto)
}
if len(pg.Ports) == 0 {
return fmt.Errorf("portgroup %q: at least one port required", name)
}
if _, _, err := pg.ParsedPorts(); err != nil {
return fmt.Errorf("portgroup %q: %w", name, err)
}
}
return nil
}