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
This commit is contained in:
+26
-85
@@ -1,12 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Host is one static host entry: hostname, IP, optional MAC, free-text comment.
|
||||
@@ -17,99 +14,43 @@ type Host struct {
|
||||
Comment string
|
||||
}
|
||||
|
||||
// Store gives locked read-modify-write access to the flat-file host database.
|
||||
const hostHeader = "# ipadm host database - managed with `ipadm`, do not edit while ipadm is running\n" +
|
||||
"# name\tip\tmac\tcomment\n"
|
||||
|
||||
// One line per host, tab-separated: name\tip\tmac\tcomment
|
||||
// MAC is stored as "-" when empty. Lines starting with '#' and blank lines are ignored.
|
||||
type Store struct {
|
||||
path string
|
||||
file *os.File
|
||||
// MAC is stored as "-" when empty.
|
||||
func parseHostLine(line string) (Host, error) {
|
||||
parts := strings.SplitN(line, "\t", 4)
|
||||
if len(parts) < 3 {
|
||||
return Host{}, fmt.Errorf("ungültige Zeile (erwarte mind. 3 Tab-getrennte Felder): %q", line)
|
||||
}
|
||||
mac := parts[2]
|
||||
if mac == "-" {
|
||||
mac = ""
|
||||
}
|
||||
comment := ""
|
||||
if len(parts) == 4 {
|
||||
comment = parts[3]
|
||||
}
|
||||
return Host{Name: parts[0], IP: parts[1], MAC: mac, Comment: comment}, nil
|
||||
}
|
||||
|
||||
func openStore(path string) (*Store, error) {
|
||||
if err := os.MkdirAll(dirOf(path), 0755); err != nil {
|
||||
return nil, fmt.Errorf("kann Verzeichnis für %s nicht anlegen: %w", path, err)
|
||||
func formatHostLine(h Host) string {
|
||||
mac := h.MAC
|
||||
if mac == "" {
|
||||
mac = "-"
|
||||
}
|
||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("kann %s nicht öffnen: %w", path, err)
|
||||
}
|
||||
if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX); err != nil {
|
||||
f.Close()
|
||||
return nil, fmt.Errorf("kann Lock auf %s nicht setzen: %w", path, err)
|
||||
}
|
||||
return &Store{path: path, file: f}, nil
|
||||
return fmt.Sprintf("%s\t%s\t%s\t%s", h.Name, h.IP, mac, h.Comment)
|
||||
}
|
||||
|
||||
func (s *Store) Close() error {
|
||||
syscall.Flock(int(s.file.Fd()), syscall.LOCK_UN)
|
||||
return s.file.Close()
|
||||
func openHostStore(path string) (*LineStore[Host], error) {
|
||||
return openLineStore(path, hostHeader, parseHostLine, formatHostLine)
|
||||
}
|
||||
|
||||
func (s *Store) Load() ([]Host, error) {
|
||||
if _, err := s.file.Seek(0, 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var hosts []Host
|
||||
sc := bufio.NewScanner(s.file)
|
||||
lineNo := 0
|
||||
for sc.Scan() {
|
||||
lineNo++
|
||||
line := strings.TrimRight(sc.Text(), "\r\n")
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "\t", 4)
|
||||
if len(parts) < 3 {
|
||||
return nil, fmt.Errorf("%s:%d: ungültige Zeile (erwarte mind. 3 Tab-getrennte Felder): %q", s.path, lineNo, line)
|
||||
}
|
||||
mac := parts[2]
|
||||
if mac == "-" {
|
||||
mac = ""
|
||||
}
|
||||
comment := ""
|
||||
if len(parts) == 4 {
|
||||
comment = parts[3]
|
||||
}
|
||||
hosts = append(hosts, Host{Name: parts[0], IP: parts[1], MAC: mac, Comment: comment})
|
||||
}
|
||||
if err := sc.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
func (s *Store) Save(hosts []Host) error {
|
||||
func sortHosts(hosts []Host) {
|
||||
sort.Slice(hosts, func(i, j int) bool {
|
||||
return strings.ToLower(hosts[i].Name) < strings.ToLower(hosts[j].Name)
|
||||
})
|
||||
var b strings.Builder
|
||||
b.WriteString("# ipadm host database - managed with `ipadm`, do not edit while ipadm is running\n")
|
||||
b.WriteString("# name\tip\tmac\tcomment\n")
|
||||
for _, h := range hosts {
|
||||
mac := h.MAC
|
||||
if mac == "" {
|
||||
mac = "-"
|
||||
}
|
||||
fmt.Fprintf(&b, "%s\t%s\t%s\t%s\n", h.Name, h.IP, mac, h.Comment)
|
||||
}
|
||||
if err := s.file.Truncate(0); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := s.file.Seek(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := s.file.WriteString(b.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.file.Sync()
|
||||
}
|
||||
|
||||
func dirOf(path string) string {
|
||||
i := strings.LastIndexByte(path, '/')
|
||||
if i <= 0 {
|
||||
return "."
|
||||
}
|
||||
return path[:i]
|
||||
}
|
||||
|
||||
func findHost(hosts []Host, name string) int {
|
||||
|
||||
Reference in New Issue
Block a user