Files
router/tools/ipadm/portfwd.go
T
mike 8591223aca ipadm: Port-Forwarding hinzufügen (-pa/-pl/-pd) + nftables-Include verdrahten
- ipadm verwaltet jetzt auch WAN->LAN Port-Forwards, referenziert per
  Hostname aus der bestehenden Host-DB (folgt IP-Änderungen automatisch)
- ipadm -u generiert zusätzlich /etc/nftables.d/portforward.conf, validiert
  via 'nft -c -f' und reloadet nftables (Rollback bei ungültiger Config,
  wie beim dnsmasq-Teil)
- /etc/nftables.conf bindet dafür neu /etc/nftables.d/*.conf ein
- Host-Store-Locking-Logik in generischen LineStore[T] extrahiert, von
  Host- und PortForward-Store gemeinsam genutzt
2026-08-18 11:43:29 +02:00

132 lines
3.7 KiB
Go

package main
import (
"fmt"
"sort"
"strconv"
"strings"
)
// PortForward maps a WAN port/protocol to a port on a host that must exist
// in the ipadm host database (referenced by name, so the forward keeps
// working if the host's IP changes later).
type PortForward struct {
WanPort int
Proto string // "tcp", "udp", or "both"
Host string
LanPort int
}
var validProtos = map[string]bool{"tcp": true, "udp": true, "both": true}
const portFwdHeader = "# ipadm port-forward database - managed with `ipadm`, do not edit while ipadm is running\n" +
"# wanport\tproto\thost\tlanport\n"
// One line per forward, tab-separated: wanport\tproto\thost\tlanport
func parsePortFwdLine(line string) (PortForward, error) {
parts := strings.Split(line, "\t")
if len(parts) != 4 {
return PortForward{}, fmt.Errorf("ungültige Zeile (erwarte 4 Tab-getrennte Felder): %q", line)
}
wanPort, err := strconv.Atoi(parts[0])
if err != nil {
return PortForward{}, fmt.Errorf("ungültiger wan-port: %q", parts[0])
}
lanPort, err := strconv.Atoi(parts[3])
if err != nil {
return PortForward{}, fmt.Errorf("ungültiger lan-port: %q", parts[3])
}
proto := parts[1]
if !validProtos[proto] {
return PortForward{}, fmt.Errorf("ungültiges Protokoll: %q", proto)
}
return PortForward{WanPort: wanPort, Proto: proto, Host: parts[2], LanPort: lanPort}, nil
}
func formatPortFwdLine(p PortForward) string {
return fmt.Sprintf("%d\t%s\t%s\t%d", p.WanPort, p.Proto, p.Host, p.LanPort)
}
func openPortFwdStore(path string) (*LineStore[PortForward], error) {
return openLineStore(path, portFwdHeader, parsePortFwdLine, formatPortFwdLine)
}
func sortPortFwds(fwds []PortForward) {
sort.Slice(fwds, func(i, j int) bool {
if fwds[i].WanPort != fwds[j].WanPort {
return fwds[i].WanPort < fwds[j].WanPort
}
return fwds[i].Proto < fwds[j].Proto
})
}
func validatePort(label string, port int) error {
if port < 1 || port > 65535 {
return fmt.Errorf("ungültiger %s: %d (erlaubt: 1-65535)", label, port)
}
return nil
}
// protosOverlap reports whether two port-forward entries on the same WAN
// port would both try to handle the same traffic (e.g. "tcp" and "both").
func protosOverlap(a, b string) bool {
if a == b {
return true
}
return a == "both" || b == "both"
}
// findConflictingPortFwds returns the indexes of entries that occupy the
// same (wanport, proto) as the given one, excluding exceptIdx.
func findConflictingPortFwds(fwds []PortForward, wanPort int, proto string, exceptIdx int) []int {
var out []int
for i, f := range fwds {
if i == exceptIdx {
continue
}
if f.WanPort == wanPort && protosOverlap(f.Proto, proto) {
out = append(out, i)
}
}
return out
}
func findPortFwd(fwds []PortForward, wanPort int, proto string) []int {
var out []int
for i, f := range fwds {
if f.WanPort == wanPort && (proto == "" || f.Proto == proto) {
out = append(out, i)
}
}
return out
}
// classifyPortFwdArgs sorts the optional trailing arguments of `ipadm -pa`
// (lan-port and/or protocol, in any order) into their fields. Returns an
// error on unrecognized or duplicate tokens.
func classifyPortFwdArgs(args []string, wanPort int) (lanPort int, proto string, err error) {
lanPort = wanPort
proto = "tcp"
lanPortSet, protoSet := false, false
for _, a := range args {
if validProtos[a] {
if protoSet {
return 0, "", fmt.Errorf("Protokoll mehrfach angegeben: %q", a)
}
proto = a
protoSet = true
continue
}
if n, convErr := strconv.Atoi(a); convErr == nil {
if lanPortSet {
return 0, "", fmt.Errorf("lan-port mehrfach angegeben: %q", a)
}
lanPort = n
lanPortSet = true
continue
}
return 0, "", fmt.Errorf("unbekanntes Argument %q (erwarte lan-port oder tcp/udp/both)", a)
}
return lanPort, proto, nil
}