From 63f3b4a89f8d83a32685668d63138e3a6dc6949f Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Sun, 15 Sep 2019 15:29:29 +0100 Subject: [PATCH] Allow server to listen on multiple ports --- cmd/ck-server/ck-server.go | 94 ++++++++++++++++++++++++------------ example_config/ckserver.json | 4 ++ internal/server/state.go | 28 +++++++---- 3 files changed, 85 insertions(+), 41 deletions(-) diff --git a/cmd/ck-server/ck-server.go b/cmd/ck-server/ck-server.go index 6ecdd0a..2ca1402 100644 --- a/cmd/ck-server/ck-server.go +++ b/cmd/ck-server/ck-server.go @@ -40,7 +40,8 @@ func dispatchConnection(conn net.Conn, sta *server.State) { data := buf[:i] goWeb := func() { - webConn, err := net.Dial("tcp", sta.RedirAddr) + _, remotePort, _ := net.SplitHostPort(conn.LocalAddr().String()) + webConn, err := net.Dial("tcp", net.JoinHostPort(sta.RedirAddr.String(), remotePort)) if err != nil { log.Errorf("Making connection to redirection server: %v", err) return @@ -186,19 +187,20 @@ func dispatchConnection(conn net.Conn, sta *server.State) { } func main() { - // server in ss config, the outbound listening ip - var bindHost string - // Outbound listening ip, should be 443 - var bindPort string + // set TLS bind host through commandline for legacy support, default 0.0.0,0 + var ssRemoteHost string + // set TLS bind port through commandline for legacy support, default 443 + var ssRemotePort string var config string - if os.Getenv("SS_LOCAL_HOST") != "" { - bindHost = os.Getenv("SS_REMOTE_HOST") - bindPort = os.Getenv("SS_REMOTE_PORT") + var pluginMode bool + + if os.Getenv("SS_LOCAL_HOST") != "" && os.Getenv("SS_LOCAL_PORT") != "" { + pluginMode = true + ssRemoteHost = os.Getenv("SS_REMOTE_HOST") + ssRemotePort = os.Getenv("SS_REMOTE_PORT") config = os.Getenv("SS_PLUGIN_OPTIONS") } else { - flag.StringVar(&bindHost, "s", "0.0.0.0", "bindHost: ip to bind to, set to 0.0.0.0 to listen to everything") - flag.StringVar(&bindPort, "p", "443", "bindPort: port to bind to, should be 443") flag.StringVar(&config, "c", "server.json", "config: path to the configuration file or its content") askVersion := flag.Bool("v", false, "Print the version number") printUsage := flag.Bool("h", false, "Print this message") @@ -244,31 +246,67 @@ func main() { } log.SetLevel(lvl) - log.Infof("Starting standalone mode, listening on %v:%v", bindHost, bindPort) + log.Infof("Starting standalone mode") } - sta, _ := server.InitState(bindHost, bindPort, time.Now) + sta, _ := server.InitState(time.Now) err := sta.ParseConfig(config) if err != nil { log.Fatalf("Configuration file error: %v", err) } + if !pluginMode && len(sta.BindAddr) == 0 { + log.Fatalf("bind address cannot be empty") + } + // when cloak is started as a shadowsocks plugin - if os.Getenv("SS_LOCAL_HOST") != "" && os.Getenv("SS_LOCAL_PORT") != "" { + if pluginMode { ssLocalHost := os.Getenv("SS_LOCAL_HOST") ssLocalPort := os.Getenv("SS_LOCAL_PORT") - if net.ParseIP(ssLocalHost).To4() == nil { - ssLocalHost = "[" + ssLocalHost + "]" - } - sta.ProxyBook["shadowsocks"], err = net.ResolveTCPAddr("tcp", ssLocalHost+":"+ssLocalPort) + + sta.ProxyBook["shadowsocks"], err = net.ResolveTCPAddr("tcp", net.JoinHostPort(ssLocalHost, ssLocalPort)) if err != nil { log.Fatal(err) } + + var ssBind string + // When listening on an IPv6 and IPv4, SS gives REMOTE_HOST as e.g. ::|0.0.0.0 + v4nv6 := len(strings.Split(ssRemoteHost, "|")) == 2 + if v4nv6 { + ssBind = ":" + ssRemotePort + } else { + ssBind = net.JoinHostPort(ssRemoteHost, ssRemotePort) + } + ssBindAddr, err := net.ResolveTCPAddr("tcp", ssBind) + if err != nil { + log.Fatalf("unable to resolve bind address provided by SS: %v", err) + } + + shouldAppend := true + for i, addr := range sta.BindAddr { + if addr.String() == ssBindAddr.String() { + shouldAppend = false + } + if addr.String() == ":"+ssRemotePort { // already listening on all interfaces + shouldAppend = false + } + if addr.String() == "0.0.0.0:"+ssRemotePort || addr.String() == "[::]:"+ssRemotePort { + // if config listens on one ip version but ss wants to listen on both, + // listen on both + if ssBindAddr.String() == ":"+ssRemotePort { + shouldAppend = true + sta.BindAddr[i] = ssBindAddr + } + } + } + if shouldAppend { + sta.BindAddr = append(sta.BindAddr, ssBindAddr) + } } - listen := func(addr, port string) { - listener, err := net.Listen("tcp", addr+":"+port) - log.Infof("Listening on " + addr + ":" + port) + listen := func(bindAddr net.Addr) { + listener, err := net.Listen("tcp", bindAddr.String()) + log.Infof("Listening on %v", bindAddr) if err != nil { log.Fatal(err) } @@ -282,19 +320,11 @@ func main() { } } - // When listening on an IPv6 and IPv4, SS gives REMOTE_HOST as e.g. ::|0.0.0.0 - listeningIP := strings.Split(sta.BindHost, "|") - for i, ip := range listeningIP { - if net.ParseIP(ip).To4() == nil { - // IPv6 needs square brackets - ip = "[" + ip + "]" - } - - // The last listener must block main() because the program exits on main return. - if i == len(listeningIP)-1 { - listen(ip, sta.BindPort) + for i, addr := range sta.BindAddr { + if i != len(sta.BindAddr)-1 { + go listen(addr) } else { - go listen(ip, sta.BindPort) + listen(addr) } } diff --git a/example_config/ckserver.json b/example_config/ckserver.json index 3db9983..42220f2 100644 --- a/example_config/ckserver.json +++ b/example_config/ckserver.json @@ -13,6 +13,10 @@ "127.0.0.1:9001" ] }, + "BindAddr": [ + ":443", + ":80" + ], "BypassUID": [ "1rmq6Ag1jZJCImLBIL5wzQ==" ], diff --git a/internal/server/state.go b/internal/server/state.go index 172c70e..e878a75 100644 --- a/internal/server/state.go +++ b/internal/server/state.go @@ -18,6 +18,7 @@ import ( type rawConfig struct { ProxyBook map[string][]string + BindAddr []string BypassUID [][]byte RedirAddr string PrivateKey string @@ -29,11 +30,9 @@ type rawConfig struct { // State type stores the global state of the program type State struct { + BindAddr []net.Addr ProxyBook map[string]net.Addr - BindHost string - BindPort string - Now func() time.Time AdminUID []byte Timeout time.Duration @@ -41,7 +40,7 @@ type State struct { BypassUID map[[16]byte]struct{} staticPv crypto.PrivateKey - RedirAddr string + RedirAddr net.Addr usedRandomM sync.RWMutex usedRandom map[[32]byte]int64 @@ -50,10 +49,8 @@ type State struct { LocalAPIRouter *gmux.Router } -func InitState(bindHost, bindPort string, nowFunc func() time.Time) (*State, error) { +func InitState(nowFunc func() time.Time) (*State, error) { ret := &State{ - BindHost: bindHost, - BindPort: bindPort, Now: nowFunc, BypassUID: make(map[[16]byte]struct{}), ProxyBook: map[string]net.Addr{}, @@ -92,12 +89,25 @@ func (sta *State) ParseConfig(conf string) (err error) { sta.LocalAPIRouter = manager.Router } - sta.RedirAddr = preParse.RedirAddr sta.Timeout = time.Duration(preParse.StreamTimeout) * time.Second + sta.RedirAddr, err = net.ResolveIPAddr("ip", preParse.RedirAddr) + if err != nil { + return fmt.Errorf("unable to resolve RedirAddr: %v", err) + } + + for _, addr := range preParse.BindAddr { + bindAddr, err := net.ResolveTCPAddr("tcp", addr) + if err != nil { + return err + } + sta.BindAddr = append(sta.BindAddr, bindAddr) + } + for name, pair := range preParse.ProxyBook { + name = strings.ToLower(name) if len(pair) != 2 { - return fmt.Errorf("invalid protocol and address pair for %v: %v", name, pair) + return fmt.Errorf("invalid proxy endpoint and address pair for %v: %v", name, pair) } network := strings.ToLower(pair[0]) switch network {