diff --git a/lib/cnf/cnf_file/defaults.go b/lib/cnf/cnf_file/defaults.go deleted file mode 100644 index 4657068..0000000 --- a/lib/cnf/cnf_file/defaults.go +++ /dev/null @@ -1,43 +0,0 @@ -package cnf_file - -import "github.com/superstes/calamary/cnf" - -func applyConfigDefaults(newCnf *cnf.Config) { - for i := range newCnf.Service.Listen { - applyListenerDefaults(&newCnf.Service.Listen[i]) - } - applyTimeoutDefaults(newCnf) - applyMetricsDefaults(newCnf) - applyOutputDefaults(newCnf) -} - -func applyListenerDefaults(lncnf *cnf.ServiceListener) { - if len(lncnf.IP4) == 0 && len(lncnf.IP6) == 0 { - lncnf.IP4 = []string{"127.0.0.1"} - lncnf.IP6 = []string{"::1"} - } -} - -func applyTimeoutDefaults(newCnf *cnf.Config) { - if newCnf.Service.Timeout.Connect == 0 { - newCnf.Service.Timeout.Connect = cnf.DefaultTimeoutConnect - } - if newCnf.Service.Timeout.Process == 0 { - newCnf.Service.Timeout.Process = cnf.DefaultTimeoutProcess - } - if newCnf.Service.Timeout.Idle == 0 { - newCnf.Service.Timeout.Idle = cnf.DefaultTimeoutIdle - } -} - -func applyMetricsDefaults(newCnf *cnf.Config) { - if newCnf.Service.Metrics.Port == 0 { - newCnf.Service.Metrics.Port = cnf.DefaultMetricsPort - } -} - -func applyOutputDefaults(newCnf *cnf.Config) { - if newCnf.Service.Output.Retries == 0 { - newCnf.Service.Output.Retries = cnf.DefaultConnectRetries - } -} diff --git a/lib/cnf/cnf_file/defaults_test.go b/lib/cnf/cnf_file/defaults_test.go deleted file mode 100644 index d9d8da7..0000000 --- a/lib/cnf/cnf_file/defaults_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package cnf_file - -import ( - "testing" - - "github.com/superstes/calamary/cnf" -) - -func TestApplyListenerDefaults(t *testing.T) { - ln1 := cnf.ServiceListener{} - if len(ln1.IP4) != 0 || len(ln1.IP6) != 0 { - t.Error("Timeout config-defaults #1") - } - applyListenerDefaults(&ln1) - if len(ln1.IP4) != 1 || - ln1.IP4[0] != "127.0.0.1" || - len(ln1.IP6) != 1 || - ln1.IP6[0] != "::1" { - t.Error("Timeout config-defaults #2") - } -} - -func TestApplyTimeoutDefaults(t *testing.T) { - cnf1 := cnf.Config{} - if cnf1.Service.Timeout.Connect != 0 || - cnf1.Service.Timeout.Process != 0 || - cnf1.Service.Timeout.Idle != 0 { - t.Error("Timeout config-defaults #1") - } - applyTimeoutDefaults(&cnf1) - if cnf1.Service.Timeout.Connect != cnf.DefaultTimeoutConnect || - cnf1.Service.Timeout.Process != cnf.DefaultTimeoutProcess || - cnf1.Service.Timeout.Idle != cnf.DefaultTimeoutIdle { - t.Error("Timeout config-defaults #2") - } -} - -func TestApplyMetricsDefaults(t *testing.T) { - cnf1 := cnf.Config{} - if cnf1.Service.Metrics.Port != 0 { - t.Error("Timeout config-defaults #1") - } - applyMetricsDefaults(&cnf1) - if cnf1.Service.Metrics.Port != cnf.DefaultMetricsPort { - t.Error("Timeout config-defaults #2") - } -} - -func TestApplyOutputDefaults(t *testing.T) { - cnf1 := cnf.Config{} - if cnf1.Service.Output.Retries != 0 { - t.Error("Timeout config-defaults #1") - } - applyOutputDefaults(&cnf1) - if uint16(cnf1.Service.Output.Retries) != uint16(cnf.DefaultConnectRetries) { - t.Error("Timeout config-defaults #2") - } -} diff --git a/lib/cnf/cnf_file/main.go b/lib/cnf/cnf_file/main.go index bc430e9..19f553d 100644 --- a/lib/cnf/cnf_file/main.go +++ b/lib/cnf/cnf_file/main.go @@ -4,9 +4,10 @@ import ( "fmt" "os" + "gopkg.in/yaml.v3" + "github.com/superstes/calamary/cnf" "github.com/superstes/calamary/log" - "gopkg.in/yaml.v3" ) func readConfigFile(file string) (config []byte, err error) { @@ -52,7 +53,6 @@ func Load(validationMode bool, fail bool) { } panic(fmt.Errorf("failed to parse config: %v", err)) } - applyConfigDefaults(&newConfig) if !validateConfig(newConfig, fail) { if !fail { log.ErrorS("config", "Failed to validate config!") diff --git a/lib/cnf/config.go b/lib/cnf/config.go index ac3b73f..4fd643f 100644 --- a/lib/cnf/config.go +++ b/lib/cnf/config.go @@ -2,7 +2,6 @@ package cnf import ( "github.com/superstes/calamary/proc/meta" - "gopkg.in/yaml.v3" ) var LOG_TIME bool = true @@ -20,7 +19,7 @@ type ServiceConfig struct { Listen []ServiceListener `yaml:"listen"` Certs ServiceCertificates `yaml:"certs"` Output ServiceOutput `yaml:"output"` - Debug bool `yaml:"debug" default="false"` + Debug bool `yaml:"debug" default:"false"` Metrics ServiceMetrics `yaml:"metrics"` } @@ -28,61 +27,36 @@ type ServiceConfig struct { // todo: make sure mode is valid // todo: if no listeners were provided - start only transparent type ServiceListener struct { - Mode meta.ListenMode `yaml:"mode" default="transparent"` + Mode meta.ListenMode `yaml:"mode" default:"transparent"` Port uint16 `yaml:"port"` - IP4 []string `yaml:"ip4"` - IP6 []string `yaml:"ip6"` - Tcp bool `yaml:"tcp" default="true"` - Udp bool `yaml:"udp" default="false"` // not implemented - TProxy bool `yaml:"tproxy" "default=false"` + IP4 []string `yaml:"ip4" default:"[\"127.0.0.1\"]"` + IP6 []string `yaml:"ip6" default:"[\"::1\"]"` + Tcp bool `yaml:"tcp" default:"true"` + Udp bool `yaml:"udp" default:"false"` // not implemented + TProxy bool `yaml:"tproxy" default:"false"` } -// todo: defaults not working; set to 0 -var DefaultTimeoutConnect = uint(2000) -var DefaultTimeoutProcess = uint(1000) -var DefaultTimeoutIdle = uint(30000) - type ServiceTimeout struct { - Connect uint `yaml:"connect"` // dial - Process uint `yaml:"process"` // parsing packet - Idle uint `yaml:"idle"` // close connection if no data was sent or received + Connect uint `yaml:"connect" default:"2000"` // dial + Process uint `yaml:"process" default:"1000"` // parsing packet + Idle uint `yaml:"idle" default:"30000"` // close connection if no data was sent or received } -var DefaultConnectRetries = uint8(1) var DefaultConnectRetryWait = uint(1000) // ms type ServiceOutput struct { - FwMark uint8 `yaml:"fwmark"` - Interface string `yaml:"interface"` + FwMark uint8 `yaml:"fwmark" default:"0"` + Interface string `yaml:"interface" default:""` // IP4 []string `ip4:"ip4"` // IP6 []string `ip6:"ip6"` - Retries uint8 `yaml:"retries" default="1"` -} - -// allow single string to be supplied -type YamlStringArray []string - -func (a *YamlStringArray) UnmarshalYAML(value *yaml.Node) error { - var multi []string - err := value.Decode(&multi) - if err != nil { - var single string - err := value.Decode(&single) - if err != nil { - return err - } - *a = []string{single} - } else { - *a = multi - } - return nil + Retries uint8 `yaml:"retries" default:"1"` } var DefaultMetricsPort = uint16(9512) type ServiceMetrics struct { - Enabled bool `yaml:"enabled" default="false"` - Port uint16 `yaml:"port" default="9512"` + Enabled bool `yaml:"enabled" default:"false"` + Port uint16 `yaml:"port" default:"9512"` } type ServiceCertificates struct { diff --git a/lib/cnf/testdata/unmarshal_test_defaults.yml b/lib/cnf/testdata/unmarshal_test_defaults.yml new file mode 100644 index 0000000..be3d1cd --- /dev/null +++ b/lib/cnf/testdata/unmarshal_test_defaults.yml @@ -0,0 +1,8 @@ +--- + +service: + listen: + - mode: 'transparent' + port: 4128 + + timeout: diff --git a/lib/cnf/unmarshal.go b/lib/cnf/unmarshal.go new file mode 100644 index 0000000..6fc16a9 --- /dev/null +++ b/lib/cnf/unmarshal.go @@ -0,0 +1,81 @@ +package cnf + +import ( + "github.com/creasty/defaults" + "gopkg.in/yaml.v3" +) + +// allow single string to be supplied +type YamlStringArray []string + +func (a *YamlStringArray) UnmarshalYAML(value *yaml.Node) error { + var multi []string + err := value.Decode(&multi) + if err != nil { + var single string + err := value.Decode(&single) + if err != nil { + return err + } + *a = []string{single} + } else { + *a = multi + } + return nil +} + +// apply defaults from tags on unmarshal +func (s *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { + defaults.Set(s) + + type plain Config + if err := unmarshal((*plain)(s)); err != nil { + return err + } + + return nil +} + +func (s *ServiceListener) UnmarshalYAML(unmarshal func(interface{}) error) error { + defaults.Set(s) + + type plain ServiceListener + if err := unmarshal((*plain)(s)); err != nil { + return err + } + + return nil +} + +func (s *ServiceTimeout) UnmarshalYAML(unmarshal func(interface{}) error) error { + defaults.Set(s) + + type plain ServiceTimeout + if err := unmarshal((*plain)(s)); err != nil { + return err + } + + return nil +} + +func (s *ServiceOutput) UnmarshalYAML(unmarshal func(interface{}) error) error { + defaults.Set(s) + + type plain ServiceOutput + if err := unmarshal((*plain)(s)); err != nil { + return err + } + + return nil +} + +func (s *ServiceMetrics) UnmarshalYAML(unmarshal func(interface{}) error) error { + defaults.Set(s) + + type plain ServiceMetrics + if err := unmarshal((*plain)(s)); err != nil { + return err + } + + return nil +} diff --git a/lib/cnf/unmarshal_test.go b/lib/cnf/unmarshal_test.go new file mode 100644 index 0000000..ccbdfc8 --- /dev/null +++ b/lib/cnf/unmarshal_test.go @@ -0,0 +1,63 @@ +package cnf + +import ( + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/creasty/defaults" + "gopkg.in/yaml.v3" +) + +func TestConfigDefaults(t *testing.T) { + cnfListen1 := ServiceListener{} + if err := defaults.Set(&cnfListen1); err != nil { + t.Errorf("Unmarshal defaults - unable to set defaults #1! (%v)", err) + } + + if len(cnfListen1.IP4) != 1 || cnfListen1.IP4[0] != "127.0.0.1" { + t.Errorf("Unmarshal defaults-service #1 (%+v)", cnfListen1) + } + if len(cnfListen1.IP6) != 1 || cnfListen1.IP6[0] != "::1" { + t.Errorf("Unmarshal defaults-service #2 (%+v)", cnfListen1) + } + if !cnfListen1.Tcp { + t.Errorf("Unmarshal defaults-service #3 (%+v)", cnfListen1) + } + + // test with acutal config file + _, pathToTest, _, _ := runtime.Caller(0) + pathToTestConfig := filepath.Dir(pathToTest) + "/testdata/unmarshal_test_defaults.yml" + + cnf2 := Config{} + configRaw, err := os.ReadFile(pathToTestConfig) + if err != nil { + t.Errorf("Unmarshal defaults - unable to load test-config! (%v)", err) + } + err = yaml.Unmarshal(configRaw, &cnf2) + if err != nil { + t.Errorf("Unmarshal defaults - unable to unmarshal test-config! (%v)", err) + } + + if len(cnf2.Service.Listen[0].IP4) != 1 || cnf2.Service.Listen[0].IP4[0] != "127.0.0.1" { + t.Errorf("Unmarshal defaults-file #1 (%+v)", cnf2.Service.Listen[0]) + } + if len(cnf2.Service.Listen[0].IP6) != 1 || cnf2.Service.Listen[0].IP6[0] != "::1" { + t.Errorf("Unmarshal defaults-file #2 (%+v)", cnf2.Service.Listen[0]) + } + if !cnf2.Service.Listen[0].Tcp { + t.Errorf("Unmarshal defaults-file #3 (%+v)", cnf2.Service.Listen[0]) + } + if cnf2.Service.Timeout.Connect != 2000 || + cnf2.Service.Timeout.Process != 1000 || + cnf2.Service.Timeout.Idle != 30000 { + t.Errorf("Unmarshal defaults-file #4 (%+v)", cnf2.Service.Timeout) + } + if cnf2.Service.Output.Retries != 1 || cnf2.Service.Output.FwMark != 0 { + t.Errorf("Unmarshal defaults-file #5 (%+v)", cnf2.Service.Output) + } + if cnf2.Service.Metrics.Port != 9512 { + t.Errorf("Unmarshal defaults-file #6 (%+v)", cnf2.Service.Metrics) + } +} diff --git a/lib/go.mod b/lib/go.mod index 35af53b..ccc626e 100644 --- a/lib/go.mod +++ b/lib/go.mod @@ -5,8 +5,10 @@ go 1.21 require golang.org/x/sys v0.12.0 require ( + github.com/pires/go-proxyproto v0.7.0 github.com/prometheus/client_golang v1.17.0 gopkg.in/yaml.v3 v3.0.1 + github.com/creasty/defaults v1.7.0 ) require ( @@ -15,7 +17,6 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/kr/text v0.2.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/pires/go-proxyproto v0.7.0 // indirect github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect diff --git a/lib/go.sum b/lib/go.sum index 6462645..0adb6da 100644 --- a/lib/go.sum +++ b/lib/go.sum @@ -3,6 +3,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA= +github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= diff --git a/lib/main/service.go b/lib/main/service.go index dd10214..2a01ec4 100644 --- a/lib/main/service.go +++ b/lib/main/service.go @@ -60,8 +60,9 @@ func (svc *service) shutdown(cancel context.CancelFunc) { if u.IsIn(string(server.Cnf.Mode), []string{"http", "https"}) { server.HttpServer.Close() time.Sleep(time.Millisecond * 500) + } else { + server.Listener.Close() } - server.Listener.Close() } log.Info("service", "Stopped") os.Exit(0) @@ -76,21 +77,19 @@ func (svc *service) serve(srv rcv.Server) (err error) { // log.Info("service", fmt.Sprintf("Serving %s://%s", ln.Addr().Network(), ln.Addr().String())) if srv.Cnf.Mode == meta.ListenModeHttp { - log.Debug("service", "Starting HTTP server") err = srv.HttpServer.ListenAndServe() - if err != nil { - log.ErrorS("service", fmt.Sprintf("Failed to start HTTP server: %v", err)) + if err != nil && !strings.Contains(fmt.Sprintf("%v", err), "Server closed") { + log.ErrorS("service", fmt.Sprintf("HTTP server failure: %v", err)) return err } } else if srv.Cnf.Mode == meta.ListenModeHttps { - log.Debug("service", "Starting HTTPS server") err = srv.HttpServer.ListenAndServeTLS( cnf.C.Service.Certs.ServerPublic, cnf.C.Service.Certs.ServerPrivate, ) - if err != nil { - log.ErrorS("service", fmt.Sprintf("Failed to start HTTPS server: %v", err)) + if err != nil && !strings.Contains(fmt.Sprintf("%v", err), "Server closed") { + log.ErrorS("service", fmt.Sprintf("HTTPS server failure: %v", err)) return err } diff --git a/lib/rcv/http.go b/lib/rcv/http.go index b7ad8e0..19517ce 100644 --- a/lib/rcv/http.go +++ b/lib/rcv/http.go @@ -15,9 +15,8 @@ func newServerHttpTcp(addr string, lncnf cnf.ServiceListener) (Server, error) { httpMux.HandleFunc("/", proc_http.HandleRequest) httpSrv := &http.Server{ - Addr: fmt.Sprintf("%s:%v", addr, lncnf.Port), - Handler: httpMux, - TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0), + Addr: fmt.Sprintf("%s:%v", addr, lncnf.Port), + Handler: httpMux, } return Server{ HttpServer: httpSrv,