diff --git a/internal/config/config.go b/internal/config/config.go index 04c5db2..b8d0570 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -373,6 +373,9 @@ func ParseClient(args []string, errOut io.Writer) (*Client, error) { return nil, err } c.Obf.Key = key + if err := validateObfTiming(c.Obf, c.Proxy.Mode); err != nil { + return nil, err + } if c.TURN.N <= 0 { c.TURN.N = 10 } @@ -443,10 +446,28 @@ func ParseServer(args []string, errOut io.Writer) (*Server, error) { return nil, err } s.Obf.Key = key + if err := validateObfTiming(s.Obf, s.Proxy.Mode); err != nil { + return nil, err + } return s, nil } +// validateObfTiming ограничивает -obf-timing UDP-релеем с включённой обфускацией: +// без RTP-профиля паковать нечего, а в tcp-режиме pacing ломает RTT/конгешн KCP. +func validateObfTiming(o ObfOpts, mode ProxyMode) error { + if o.Timing <= 0 { + return nil + } + if !o.Enabled() { + return errors.New("-obf-timing requires -obf-profile != none") + } + if mode != ProxyModeUDP { + return errors.New("-obf-timing supported only with -mode udp") + } + return nil +} + // validateObfProfile проверяет что -obf-profile содержит известное значение. func validateObfProfile(p ObfProfile) error { switch p { diff --git a/internal/config/config_test.go b/internal/config/config_test.go index f9cbadb..ca377b7 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -7,8 +7,11 @@ import ( "io" "strings" "testing" + "time" ) +const testObfKey = "0000000000000000000000000000000000000000000000000000000000000000" + func validClientArgs() []string { return []string{ "-peer", "1.2.3.4:5000", @@ -99,7 +102,7 @@ func TestParseClient_MultiLinks(t *testing.T) { func TestParseClient_MissingVKLink(t *testing.T) { _, err := ParseClient([]string{"-peer", "1.2.3.4:5000"}, io.Discard) - if err == nil || !(strings.Contains(err.Error(), "-links") || strings.Contains(err.Error(), "-link")) { + if err == nil || (!strings.Contains(err.Error(), "-links") && !strings.Contains(err.Error(), "-link")) { t.Errorf("expected vk-link error, got %v", err) } } @@ -153,6 +156,41 @@ func TestParseClient_ObfProfileInvalid(t *testing.T) { } } +func TestParseClient_ObfTimingRequiresObf(t *testing.T) { + args := append(validClientArgs(), "-obf-timing", "20ms") + _, err := ParseClient(args, io.Discard) + if err == nil || !strings.Contains(err.Error(), "-obf-timing") { + t.Errorf("expected obf-timing error without profile, got %v", err) + } +} + +func TestParseClient_ObfTimingRejectsTCP(t *testing.T) { + args := append(validClientArgs(), "-mode", "tcp", "-obf-profile", "rtpopus3", "-obf-key", testObfKey, "-obf-timing", "20ms") + _, err := ParseClient(args, io.Discard) + if err == nil || !strings.Contains(err.Error(), "-obf-timing") { + t.Errorf("expected obf-timing/mode error, got %v", err) + } +} + +func TestParseClient_ObfTimingValid(t *testing.T) { + args := append(validClientArgs(), "-obf-profile", "rtpopus3", "-obf-key", testObfKey, "-obf-timing", "20ms") + c, err := ParseClient(args, io.Discard) + if err != nil { + t.Fatal(err) + } + if c.Obf.Timing != 20*time.Millisecond { + t.Errorf("Obf.Timing = %v, want 20ms", c.Obf.Timing) + } +} + +func TestParseServer_ObfTimingRejectsTCP(t *testing.T) { + args := []string{"-connect", "127.0.0.1:51820", "-mode", "tcp", "-obf-profile", "rtpopus3", "-obf-key", testObfKey, "-obf-timing", "10ms"} + _, err := ParseServer(args, io.Discard) + if err == nil || !strings.Contains(err.Error(), "-obf-timing") { + t.Errorf("expected obf-timing/mode error, got %v", err) + } +} + func TestParseClient_StreamsPerCredNonPositive(t *testing.T) { args := append(validClientArgs(), "-streams-per-cred", "0") _, err := ParseClient(args, io.Discard)