-
Notifications
You must be signed in to change notification settings - Fork 189
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add WebSocket support to the existing outline-ss-server
#225
base: sbruens/udp-split-serving
Are you sure you want to change the base?
Changes from all commits
dd5f439
e769585
c0e942b
6eb828e
831371a
db09289
21cae17
e210fb0
3c1277c
0c45bad
fffb6a2
38e2eae
3241298
ed4f4de
5b14062
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,71 +22,128 @@ import ( | |
) | ||
|
||
type ServiceConfig struct { | ||
Listeners []ListenerConfig | ||
Keys []KeyConfig | ||
Dialer DialerConfig | ||
Listeners []ListenerConfig `yaml:"listeners"` | ||
Keys []KeyConfig `yaml:"keys"` | ||
Dialer DialerConfig `yaml:"dialer"` | ||
} | ||
|
||
type ListenerType string | ||
|
||
const listenerTypeTCP ListenerType = "tcp" | ||
const ( | ||
listenerTypeTCP ListenerType = "tcp" | ||
|
||
const listenerTypeUDP ListenerType = "udp" | ||
listenerTypeUDP ListenerType = "udp" | ||
listenerTypeWebsocketStream ListenerType = "websocket-stream" | ||
listenerTypeWebsocketPacket ListenerType = "websocket-packet" | ||
) | ||
|
||
type WebServerConfig struct { | ||
ID string `yaml:"id"` | ||
sbruens marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Listeners []string `yaml:"listen"` | ||
} | ||
|
||
type ListenerConfig struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add comments here? Also, this needs to be redesigned. Each type needs different parameters, but we can't simply merge them all in one struct. They each need separate definitions. |
||
Type ListenerType | ||
Address string | ||
Type ListenerType `yaml:"type"` | ||
Address string `yaml:"address,omitempty"` | ||
WebServer string `yaml:"web_server,omitempty"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found it best to avoid tags, since that ties you to a specific implementation. You can't use this for JSON or other libraries. Better to just name the fields like you want. In this case, you could use |
||
Path string `yaml:"path,omitempty"` | ||
} | ||
|
||
type DialerConfig struct { | ||
Fwmark uint | ||
} | ||
|
||
type KeyConfig struct { | ||
ID string | ||
Cipher string | ||
Secret string | ||
ID string `yaml:"id"` | ||
sbruens marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Cipher string `yaml:"cipher"` | ||
Secret string `yaml:"secret"` | ||
} | ||
|
||
type LegacyKeyServiceConfig struct { | ||
KeyConfig `yaml:",inline"` | ||
Port int | ||
Port int `yaml:"port"` | ||
} | ||
|
||
type WebConfig struct { | ||
Servers []WebServerConfig `yaml:"servers"` | ||
} | ||
|
||
type Config struct { | ||
Services []ServiceConfig | ||
Web WebConfig `yaml:"web"` | ||
Services []ServiceConfig `yaml:"services"` | ||
|
||
// Deprecated: `keys` exists for backward compatibility. Prefer to configure | ||
// using the newer `services` format. | ||
Keys []LegacyKeyServiceConfig | ||
Keys []LegacyKeyServiceConfig `yaml:"keys"` | ||
} | ||
|
||
// Validate checks that the config is valid. | ||
func (c *Config) Validate() error { | ||
existingWebServers := make(map[string]bool) | ||
for _, srv := range c.Web.Servers { | ||
if srv.ID == "" { | ||
return fmt.Errorf("web server must have an ID") | ||
} | ||
if _, exists := existingWebServers[srv.ID]; exists { | ||
return fmt.Errorf("web server with ID `%s` already exists", srv.ID) | ||
} | ||
existingWebServers[srv.ID] = true | ||
|
||
for _, addr := range srv.Listeners { | ||
if err := validateAddress(addr); err != nil { | ||
return fmt.Errorf("invalid listener for web server `%s`: %w", srv.ID, err) | ||
} | ||
} | ||
} | ||
|
||
existingListeners := make(map[string]bool) | ||
for _, serviceConfig := range c.Services { | ||
for _, lnConfig := range serviceConfig.Listeners { | ||
// TODO: Support more listener types. | ||
if lnConfig.Type != listenerTypeTCP && lnConfig.Type != listenerTypeUDP { | ||
var key string | ||
switch lnConfig.Type { | ||
case listenerTypeTCP, listenerTypeUDP: | ||
if err := validateAddress(lnConfig.Address); err != nil { | ||
return err | ||
} | ||
key = fmt.Sprintf("%s/%s", lnConfig.Type, lnConfig.Address) | ||
if _, exists := existingListeners[key]; exists { | ||
return fmt.Errorf("listener of type `%s` with address `%s` already exists.", lnConfig.Type, lnConfig.Address) | ||
} | ||
case listenerTypeWebsocketStream, listenerTypeWebsocketPacket: | ||
if lnConfig.WebServer == "" { | ||
return fmt.Errorf("listener type `%s` requires a `web_server`", lnConfig.Type) | ||
} | ||
if lnConfig.Path == "" { | ||
return fmt.Errorf("listener type `%s` requires a `path`", lnConfig.Type) | ||
} | ||
if _, exists := existingWebServers[lnConfig.WebServer]; !exists { | ||
return fmt.Errorf("listener type `%s` references unknown web server `%s`", lnConfig.Type, lnConfig.WebServer) | ||
} | ||
key = fmt.Sprintf("%s/%s", lnConfig.Type, lnConfig.WebServer) | ||
if _, exists := existingListeners[key]; exists { | ||
return fmt.Errorf("listener of type `%s` with web server `%s` already exists.", lnConfig.Type, lnConfig.WebServer) | ||
} | ||
default: | ||
return fmt.Errorf("unsupported listener type: %s", lnConfig.Type) | ||
} | ||
host, _, err := net.SplitHostPort(lnConfig.Address) | ||
if err != nil { | ||
return fmt.Errorf("invalid listener address `%s`: %v", lnConfig.Address, err) | ||
} | ||
if ip := net.ParseIP(host); ip == nil { | ||
return fmt.Errorf("address must be IP, found: %s", host) | ||
} | ||
key := string(lnConfig.Type) + "/" + lnConfig.Address | ||
if _, exists := existingListeners[key]; exists { | ||
return fmt.Errorf("listener of type %s with address %s already exists.", lnConfig.Type, lnConfig.Address) | ||
} | ||
|
||
existingListeners[key] = true | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func validateAddress(addr string) error { | ||
host, _, err := net.SplitHostPort(addr) | ||
if err != nil { | ||
return fmt.Errorf("invalid listener address `%s`: %v", addr, err) | ||
} | ||
if ip := net.ParseIP(host); ip == nil { | ||
return fmt.Errorf("address must be IP, found: %s", host) | ||
} | ||
return nil | ||
} | ||
|
||
// readConfig attempts to read a config from a filename and parses it as a [Config]. | ||
func readConfig(configData []byte) (*Config, error) { | ||
config := Config{} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm assuming these are always HTTP?
Perhaps we should add a comment that this should probably listen on localhost addresses, since it's not safe to expose HTTP publicly