-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconfig.go
More file actions
175 lines (150 loc) · 5.2 KB
/
Copy pathconfig.go
File metadata and controls
175 lines (150 loc) · 5.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
//
// Copyright (c) 2025 Cedrik Pischem
// SPDX-License-Identifier: BSD-2-Clause
//
// config.go - Runtime configuration from command-line flags
//
// Parses flags to control proxy behavior: RA forwarding, route installation,
// DAD handling, link-layer option rewriting, caching parameters, and debug.
//
package main
import (
"flag"
"fmt"
"log"
"net/netip"
"os"
"strings"
"time"
)
// version is set via ldflags at build time
var version = "dev"
// These are used both as flag defaults and as safe fallbacks.
const (
defaultCacheTTL = 10 * time.Minute
defaultCacheMax = 4096
defaultRouteQPS = 50
defaultPFQPS = 50
defaultPcapTimeout = 50 * time.Millisecond
)
// pfTableFlag implements flag.Value for repeatable --pf flags.
type pfTableFlag struct {
m map[string][]string
}
func (f *pfTableFlag) String() string {
return ""
}
func (f *pfTableFlag) Set(value string) error {
parts := strings.SplitN(value, ":", 2)
if len(parts) != 2 || parts[1] == "" { // only table is required
return fmt.Errorf("invalid format, expected [interface]:table")
}
if f.m == nil {
f.m = make(map[string][]string)
}
f.m[parts[0]] = append(f.m[parts[0]], parts[1])
return nil
}
// staticPrefixFlag implements flag.Value for repeatable --static-prefix flags.
type staticPrefixFlag []netip.Prefix
func (f *staticPrefixFlag) String() string {
return ""
}
func (f *staticPrefixFlag) Set(value string) error {
prefix, err := netip.ParsePrefix(value)
if err != nil || !prefix.Addr().Is6() {
return fmt.Errorf("invalid IPv6 prefix %q", value)
}
*f = append(*f, prefix.Masked())
return nil
}
// staticRouterFlag implements flag.Value for repeatable --static-router flags.
type staticRouterFlag []netip.Addr
func (f *staticRouterFlag) String() string {
return ""
}
func (f *staticRouterFlag) Set(value string) error {
addr, err := netip.ParseAddr(value)
if err != nil || !addr.Is6() || !addr.IsLinkLocalUnicast() {
return fmt.Errorf("invalid IPv6 link-local router address %q", value)
}
*f = append(*f, addr)
return nil
}
// Config holds runtime configuration parsed from command-line flags.
type Config struct {
NoRA bool
NoRoutes bool
NoDAD bool
NoRewrite bool
Debug bool
CacheTTL time.Duration
CacheMax int
RouteQPS int
PFQPS int
PcapTimeout time.Duration
PFTables map[string][]string // interface -> list of tables
CacheFile string // path to persistent cache file (optional)
StaticPrefixes []netip.Prefix
StaticRouters []netip.Addr
}
// ShouldForwardType returns true if the given ICMPv6 type should be forwarded.
func (c *Config) ShouldForwardType(icmpType uint8) bool {
if icmpType == 134 && c.NoRA {
return false
}
return icmpType >= 133 && icmpType <= 136
}
// ParseFlags parses command-line flags and returns a Config.
func ParseFlags() *Config {
showVersion := flag.Bool("version", false, "show version and exit")
cfg := &Config{}
var pfTables pfTableFlag
var staticPrefixes staticPrefixFlag
var staticRouters staticRouterFlag
flag.BoolVar(&cfg.NoRA, "no-ra", false, "disable forwarding of Router Advertisements (ICMPv6 type 134)")
flag.BoolVar(&cfg.NoRoutes, "no-routes", false, "disable per-host route installation and cleanup")
flag.BoolVar(&cfg.NoDAD, "no-dad", false, "disable DAD proxying (RFC 4389 non-compliant, may cause conflicts)")
flag.BoolVar(&cfg.NoRewrite, "no-rewrite-lla", false, "do not rewrite SLLA/TLLA options (unsafe in L2-isolated setups)")
flag.BoolVar(&cfg.Debug, "debug", false, "enable verbose debug logging")
flag.DurationVar(&cfg.CacheTTL, "cache-ttl", defaultCacheTTL, "neighbor cache TTL")
flag.IntVar(&cfg.CacheMax, "cache-max", defaultCacheMax, "max neighbors to track")
flag.IntVar(&cfg.RouteQPS, "route-qps", defaultRouteQPS, "max /sbin/route operations per second (rate limited)")
flag.IntVar(&cfg.PFQPS, "pf-qps", defaultPFQPS, "max /sbin/pfctl operations per second (rate limited)")
flag.DurationVar(&cfg.PcapTimeout, "pcap-timeout", defaultPcapTimeout, "packet capture timeout (lower = less latency, higher = less CPU)")
flag.Var(&pfTables, "pf", "populate PF table with learned clients (format: interface:table, repeatable)")
flag.StringVar(&cfg.CacheFile, "cache-file", "", "path to persistent cache file for state across restarts (SIGUSR1 to save)")
flag.Var(&staticPrefixes, "static-prefix", "manually trust an IPv6 prefix for learning/proxying (repeatable)")
flag.Var(&staticRouters, "static-router", "manually trust an upstream router link-local address (repeatable)")
flag.Parse()
// Sanitize values
if cfg.CacheTTL <= 0 {
cfg.CacheTTL = defaultCacheTTL
}
if cfg.CacheMax < 1 {
cfg.CacheMax = defaultCacheMax
}
if cfg.RouteQPS < 1 {
cfg.RouteQPS = defaultRouteQPS
}
if cfg.PFQPS < 1 {
cfg.PFQPS = defaultPFQPS
}
if cfg.PcapTimeout <= 0 {
cfg.PcapTimeout = defaultPcapTimeout
}
if *showVersion {
fmt.Printf("ndp-proxy-go %s\n", version)
os.Exit(0)
}
cfg.PFTables = pfTables.m
cfg.StaticPrefixes = []netip.Prefix(staticPrefixes)
cfg.StaticRouters = []netip.Addr(staticRouters)
return cfg
}
// DebugLog logs a message only if debug mode is enabled.
func (c *Config) DebugLog(format string, args ...any) {
if c.Debug {
log.Printf(format, args...)
}
}