| 164 | } |
| 165 | |
| 166 | func (p *Profile) validate() error { |
| 167 | if p.Listen == "" { |
| 168 | return errors.New("listen is required") |
| 169 | } |
| 170 | if _, err := netip.ParseAddrPort(p.Listen); err != nil { |
| 171 | return fmt.Errorf("invalid listen %q: %w", p.Listen, err) |
| 172 | } |
| 173 | if p.Connect.Host == "" { |
| 174 | return errors.New("connect.host is required") |
| 175 | } |
| 176 | if p.Connect.Port == 0 { |
| 177 | p.Connect.Port = 443 |
| 178 | } |
| 179 | for _, ip := range p.Connect.FallbackIPs { |
| 180 | if _, err := netip.ParseAddr(ip); err != nil { |
| 181 | return fmt.Errorf("invalid fallback_ip %q: %w", ip, err) |
| 182 | } |
| 183 | } |
| 184 | if _, err := bypass.ByName(p.Spoof.Strategy); err != nil { |
| 185 | return fmt.Errorf("invalid strategy: %w", err) |
| 186 | } |
| 187 | for _, s := range p.Spoof.StrategyRotation { |
| 188 | if _, err := bypass.ByName(s); err != nil { |
| 189 | return fmt.Errorf("invalid strategy_rotation entry %q: %w", s, err) |
| 190 | } |
| 191 | } |
| 192 | switch p.Spoof.SNISelection { |
| 193 | case "", "random", "round_robin": |
| 194 | // ok |
| 195 | default: |
| 196 | return fmt.Errorf("invalid sni_selection %q (want random or round_robin)", p.Spoof.SNISelection) |
| 197 | } |
| 198 | if len(p.Spoof.SNIPool) == 0 && p.Spoof.SNI == "" { |
| 199 | return errors.New("spoof: either sni_pool or sni must be set") |
| 200 | } |
| 201 | for _, s := range p.Spoof.SNIPool { |
| 202 | if len(s) == 0 || len(s) > 219 { |
| 203 | return fmt.Errorf("sni %q: length must be 1..219", s) |
| 204 | } |
| 205 | } |
| 206 | if p.Spoof.SNI != "" && (len(p.Spoof.SNI) > 219) { |
| 207 | return fmt.Errorf("sni too long") |
| 208 | } |
| 209 | // Padding bounds — the injector clips at 65000 bytes of extra padding. |
| 210 | // Anything larger would also fragment at the IP layer, defeating the |
| 211 | // "looks like one handshake packet" invariant. Clamp user input. |
| 212 | if p.Spoof.MinExtraPad < 0 { |
| 213 | return fmt.Errorf("min_extra_pad must be >= 0") |
| 214 | } |
| 215 | if p.Spoof.MaxExtraPad < 0 || p.Spoof.MaxExtraPad > 65000 { |
| 216 | return fmt.Errorf("max_extra_pad must be in [0, 65000]") |
| 217 | } |
| 218 | if p.Spoof.MaxExtraPad < p.Spoof.MinExtraPad { |
| 219 | return fmt.Errorf("max_extra_pad (%d) < min_extra_pad (%d)", |
| 220 | p.Spoof.MaxExtraPad, p.Spoof.MinExtraPad) |
| 221 | } |
| 222 | // Timing bounds — reject negatives and MaxDelay < MinDelay. |
| 223 | if p.Spoof.MinDelay < 0 || p.Spoof.MaxDelay < 0 { |