1515package main
1616
1717import (
18+ "errors"
1819 "fmt"
1920 "net"
21+ "reflect"
22+ "strings"
2023
24+ "github.com/go-viper/mapstructure/v2"
2125 "gopkg.in/yaml.v3"
2226)
2327
28+ type Validator interface {
29+ // Validate checks that the type is valid.
30+ validate () error
31+ }
32+
2433type ServiceConfig struct {
2534 Listeners []ListenerConfig
2635 Keys []KeyConfig
@@ -29,15 +38,143 @@ type ServiceConfig struct {
2938
3039type ListenerType string
3140
32- const listenerTypeTCP ListenerType = "tcp"
41+ const (
42+ TCPListenerType = ListenerType ("tcp" )
43+ UDPListenerType = ListenerType ("udp" )
44+ WebsocketStreamListenerType = ListenerType ("websocket-stream" )
45+ WebsocketPacketListenerType = ListenerType ("websocket-packet" )
46+ )
47+
48+ type WebServerConfig struct {
49+ // Unique identifier of the web server to be referenced in Websocket connections.
50+ ID string
3351
34- const listenerTypeUDP ListenerType = "udp"
52+ // List of listener addresses (e.g., ":8080", "localhost:8081"). Should be localhost for HTTP.
53+ Listeners []string `yaml:"listen"`
54+ }
3555
56+ // ListenerConfig holds the configuration for a listener. It supports different
57+ // listener types, configured via the embedded type and unmarshalled based on
58+ // the "type" field in the YAML/JSON configuration. Only one of the fields will
59+ // be set, corresponding to the listener type.
3660type ListenerConfig struct {
37- Type ListenerType
61+ // TCP configuration for the listener.
62+ TCP * TCPUDPConfig
63+ // UDP configuration for the listener.
64+ UDP * TCPUDPConfig
65+ // Websocket stream configuration for the listener.
66+ WebsocketStream * WebsocketConfig
67+ // Websocket packet configuration for the listener.
68+ WebsocketPacket * WebsocketConfig
69+ }
70+
71+ var _ Validator = (* ListenerConfig )(nil )
72+ var _ yaml.Unmarshaler = (* ListenerConfig )(nil )
73+
74+ // Define a map to associate listener types with [ListenerConfig] field names.
75+ var listenerTypeMap = map [ListenerType ]string {
76+ TCPListenerType : "TCP" ,
77+ UDPListenerType : "UDP" ,
78+ WebsocketStreamListenerType : "WebsocketStream" ,
79+ WebsocketPacketListenerType : "WebsocketPacket" ,
80+ }
81+
82+ func (c * ListenerConfig ) UnmarshalYAML (value * yaml.Node ) error {
83+ var raw map [string ]interface {}
84+ if err := value .Decode (& raw ); err != nil {
85+ return err
86+ }
87+
88+ // Remove the "type" field so we can decode directly into the target struct.
89+ rawType , ok := raw ["type" ]
90+ if ! ok {
91+ return errors .New ("`type` field required" )
92+ }
93+ lnTypeStr , ok := rawType .(string )
94+ if ! ok {
95+ return fmt .Errorf ("`type` is not a string, but %T" , rawType )
96+ }
97+ lnType := ListenerType (lnTypeStr )
98+ delete (raw , "type" )
99+
100+ fieldName , ok := listenerTypeMap [lnType ]
101+ if ! ok {
102+ return fmt .Errorf ("invalid listener type: %v" , lnType )
103+ }
104+ v := reflect .ValueOf (c ).Elem ()
105+ field := v .FieldByName (fieldName )
106+ if ! field .IsValid () {
107+ return fmt .Errorf ("invalid field name: %s for type: %s" , fieldName , lnType )
108+ }
109+ fieldType := field .Type ()
110+ if fieldType .Kind () != reflect .Ptr || fieldType .Elem ().Kind () != reflect .Struct {
111+ return fmt .Errorf ("field %s is not a pointer to a struct" , fieldName )
112+ }
113+
114+ configValue := reflect .New (fieldType .Elem ())
115+ field .Set (configValue )
116+ if err := mapstructure .Decode (raw , configValue .Interface ()); err != nil {
117+ return fmt .Errorf ("failed to decode map: %w" , err )
118+ }
119+ return nil
120+ }
121+
122+ func (c * ListenerConfig ) validate () error {
123+ v := reflect .ValueOf (c ).Elem ()
124+
125+ for i := 0 ; i < v .NumField (); i ++ {
126+ field := v .Field (i )
127+ if field .Kind () == reflect .Ptr && field .IsNil () {
128+ continue
129+ }
130+ if validator , ok := field .Interface ().(Validator ); ok {
131+ if err := validator .validate (); err != nil {
132+ return fmt .Errorf ("invalid config: %v" , err )
133+ }
134+ }
135+ }
136+ return nil
137+ }
138+
139+ type TCPUDPConfig struct {
140+ // Address for the TCP or UDP listener. Should be in the format host:port.
38141 Address string
39142}
40143
144+ var _ Validator = (* TCPUDPConfig )(nil )
145+
146+ func (c * TCPUDPConfig ) validate () error {
147+ if c .Address == "" {
148+ return errors .New ("`address` must be specified" )
149+ }
150+ if err := validateAddress (c .Address ); err != nil {
151+ return fmt .Errorf ("invalid address: %v" , err )
152+ }
153+ return nil
154+ }
155+
156+ type WebsocketConfig struct {
157+ // Web server unique identifier to use for the websocket connection.
158+ WebServer string `mapstructure:"web_server"`
159+ // Path for the websocket connection.
160+ Path string
161+ }
162+
163+ var _ Validator = (* WebsocketConfig )(nil )
164+
165+ func (c * WebsocketConfig ) validate () error {
166+ if c .WebServer == "" {
167+ return errors .New ("`web_server` must be specified" )
168+ }
169+ if c .Path == "" {
170+ return errors .New ("`path` must be specified" )
171+ }
172+ if ! strings .HasPrefix (c .Path , "/" ) {
173+ return errors .New ("`path` must start with `/`" )
174+ }
175+ return nil
176+ }
177+
41178type DialerConfig struct {
42179 Fwmark uint
43180}
@@ -53,40 +190,54 @@ type LegacyKeyServiceConfig struct {
53190 Port int
54191}
55192
193+ type WebConfig struct {
194+ Servers []WebServerConfig `yaml:"servers"`
195+ }
196+
56197type Config struct {
198+ Web WebConfig
57199 Services []ServiceConfig
58200
59201 // Deprecated: `keys` exists for backward compatibility. Prefer to configure
60202 // using the newer `services` format.
61203 Keys []LegacyKeyServiceConfig
62204}
63205
64- // Validate checks that the config is valid.
65- func (c * Config ) Validate () error {
66- existingListeners := make (map [string ]bool )
67- for _ , serviceConfig := range c .Services {
68- for _ , lnConfig := range serviceConfig .Listeners {
69- // TODO: Support more listener types.
70- if lnConfig .Type != listenerTypeTCP && lnConfig .Type != listenerTypeUDP {
71- return fmt .Errorf ("unsupported listener type: %s" , lnConfig .Type )
72- }
73- host , _ , err := net .SplitHostPort (lnConfig .Address )
74- if err != nil {
75- return fmt .Errorf ("invalid listener address `%s`: %v" , lnConfig .Address , err )
76- }
77- if ip := net .ParseIP (host ); ip == nil {
78- return fmt .Errorf ("address must be IP, found: %s" , host )
206+ var _ Validator = (* Config )(nil )
207+
208+ func (c * Config ) validate () error {
209+ for _ , srv := range c .Web .Servers {
210+ if srv .ID == "" {
211+ return fmt .Errorf ("web server must have an ID" )
212+ }
213+ for _ , addr := range srv .Listeners {
214+ if err := validateAddress (addr ); err != nil {
215+ return fmt .Errorf ("invalid listener for web server `%s`: %w" , srv .ID , err )
79216 }
80- key := string (lnConfig .Type ) + "/" + lnConfig .Address
81- if _ , exists := existingListeners [key ]; exists {
82- return fmt .Errorf ("listener of type %s with address %s already exists." , lnConfig .Type , lnConfig .Address )
217+ }
218+ }
219+
220+ for _ , service := range c .Services {
221+ for _ , ln := range service .Listeners {
222+ if err := ln .validate (); err != nil {
223+ return fmt .Errorf ("invalid listener: %v" , err )
83224 }
84- existingListeners [key ] = true
85225 }
86226 }
87227 return nil
88228}
89229
230+ func validateAddress (addr string ) error {
231+ host , _ , err := net .SplitHostPort (addr )
232+ if err != nil {
233+ return err
234+ }
235+ if ip := net .ParseIP (host ); ip == nil {
236+ return fmt .Errorf ("address must be IP, found: %s" , host )
237+ }
238+ return nil
239+ }
240+
90241// readConfig attempts to read a config from a filename and parses it as a [Config].
91242func readConfig (configData []byte ) (* Config , error ) {
92243 config := Config {}
0 commit comments