Files
router/tools/ipadm/portfwd.go
T
mike 1a39f54858 ipadm: translate to English, fix help alignment
- All ipadm-internal messages (usage, prompts, errors) are now English
- Usage text is built programmatically from a (left, desc) row list with
  computed column width instead of hand-aligned spaces, so it can't drift
  out of alignment again when rows are added/changed
- README's literal usage block updated to match; rest of the repo docs
  stay German
2026-08-18 11:49:47 +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("invalid line (expected 4 tab-separated fields): %q", line)
}
wanPort, err := strconv.Atoi(parts[0])
if err != nil {
return PortForward{}, fmt.Errorf("invalid wan-port: %q", parts[0])
}
lanPort, err := strconv.Atoi(parts[3])
if err != nil {
return PortForward{}, fmt.Errorf("invalid lan-port: %q", parts[3])
}
proto := parts[1]
if !validProtos[proto] {
return PortForward{}, fmt.Errorf("invalid protocol: %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("invalid %s: %d (allowed: 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("protocol given more than once: %q", a)
}
proto = a
protoSet = true
continue
}
if n, convErr := strconv.Atoi(a); convErr == nil {
if lanPortSet {
return 0, "", fmt.Errorf("lan-port given more than once: %q", a)
}
lanPort = n
lanPortSet = true
continue
}
return 0, "", fmt.Errorf("unrecognized argument %q (expected lan-port or tcp/udp/both)", a)
}
return lanPort, proto, nil
}