diff --git a/cmd/norouter/manager.go b/cmd/norouter/manager.go index 5dcda54..4999ced 100644 --- a/cmd/norouter/manager.go +++ b/cmd/norouter/manager.go @@ -24,6 +24,7 @@ import ( "os/exec" "os/signal" "strings" + "syscall" "time" "github.com/davecgh/go-spew/spew" @@ -59,6 +60,7 @@ var managerFlags = []cli.Flag{ var sigCh = make(chan os.Signal) func managerAction(clicontext *cli.Context) error { + signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) openEditor := clicontext.Bool("open-editor") manifestPath := clicontext.Args().First() if openEditor { @@ -71,14 +73,18 @@ func managerAction(clicontext *cli.Context) error { return errors.Errorf("no manifest file path was specified, run `%s show-example` to show an example, or run `%s --open-editor` to open an editor with an example file", os.Args[0], os.Args[0]) } - return runManager(manifestPath) + err := runManager(manifestPath) + if err == errInterrupted { + logrus.Info("Interrupted. Exiting...") + err = nil + } + return err } func runManagerWithEditor() error { if !isatty.IsTerminal(os.Stdout.Fd()) { return errors.New("`--open-editor` requires stdout to be a terminal") } - signal.Notify(sigCh, os.Interrupt) editor := editorcmd.Detect() if editor == "" { return errors.New("could not detect a text editor binary, try setting $EDITOR") diff --git a/go.mod b/go.mod index ffeda23..099eb9a 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/norouter/norouter go 1.15 require ( + github.com/cybozu-go/usocksd v1.1.0 github.com/davecgh/go-spew v1.1.1 github.com/elazarl/goproxy v0.0.0-20201021153353-00ad82a08272 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 diff --git a/go.sum b/go.sum index 3a13ad3..76c1382 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,7 @@ cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7 cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= @@ -37,6 +38,14 @@ github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cybozu-go/log v1.5.0 h1:cjLr+pNga4NL5sj5vnnG00xKmKXSWx0grQQ4LnV1Ris= +github.com/cybozu-go/log v1.5.0/go.mod h1:zpfovuCgUx+a/ErvQrThoT+/z1RVQoLDOf95wkBeRiw= +github.com/cybozu-go/netutil v1.2.0 h1:UBO0+hB43zd5mIXRfD195eBMHvgWlHP2mYuQ2F5Yxtg= +github.com/cybozu-go/netutil v1.2.0/go.mod h1:Wx92iF1dPrtuSzLUMEidtrKTFiDWpLcsYvbQ1lHSmxY= +github.com/cybozu-go/usocksd v1.1.0 h1:0g0MbyhOFWaR0Iv3Kc+6YnlWeNXWRj3Wh67cdmvG3dk= +github.com/cybozu-go/usocksd v1.1.0/go.mod h1:9nuohcMvU4K2oNIcyJG5ysaNdXJ8ZCQmDAaWvrO0WcU= +github.com/cybozu-go/well v1.8.1 h1:YlEPreiDBI+KxE5rcAkkaB5j/Iyow6nIVmUpq3u5DYQ= +github.com/cybozu-go/well v1.8.1/go.mod h1:9PK1AltjltFwZBtTWVXnCJ0fIeZMxGovYfLmCcZxQog= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -55,6 +64,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -104,6 +115,8 @@ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -119,8 +132,12 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.4-0.20190131011033-7dc38fb350b1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I= +github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mohae/deepcopy v0.0.0-20170308212314-bb9b5e7adda9/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= @@ -128,6 +145,9 @@ github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59P github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20181111125026-1722abf79c2f/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -146,9 +166,19 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg= +github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.2.1 h1:bIcUwXqLseLF3BDAZduuNfekWG87ibtFxi59Bq+oI9M= +github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -196,6 +226,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -205,6 +236,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -220,6 +252,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2By golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -242,6 +275,7 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20u golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -314,6 +348,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= diff --git a/integration/test-integration.sh b/integration/test-integration.sh index e4a9a5f..ef48c9c 100755 --- a/integration/test-integration.sh +++ b/integration/test-integration.sh @@ -10,6 +10,8 @@ cleanup() { if [[ -n "$pid" && -d "/proc/$pid" ]]; then kill $pid; fi docker rm -f host1 host2 host3 make clean + sleep 3 + if [[ -n "$pid" && -d "/proc/$pid" ]]; then echo "process still running?"; exit 1; fi set -e } cleanup @@ -95,6 +97,24 @@ for ((i = 0; i < $N; i++)); do done set +x +echo "Testing SOCKS4a mode" +set -x +for ((i = 0; i < $N; i++)); do + for f in host1 host2 host3; do + curl -fsS -o /dev/null --socks4a http://127.0.0.1:18081 http://${f}:8080 + done +done +set +x + +echo "Testing SOCKS5h mode" +set -x +for ((i = 0; i < $N; i++)); do + for f in host1 host2 host3; do + curl -fsS -o /dev/null --socks5-hostname http://127.0.0.1:18081 http://${f}:8080 + done +done +set +x + echo "iperf3 from host2 to host1" docker exec host1 iperf3 -s > /dev/null & iperf3_exec_pid=$! diff --git a/integration/test-integration.yaml b/integration/test-integration.yaml index 2e48eb3..8cca222 100644 --- a/integration/test-integration.yaml +++ b/integration/test-integration.yaml @@ -4,6 +4,8 @@ hosts: vip: "127.0.42.100" http: listen: "127.0.0.1:18080" + socks: + listen: "127.0.0.1:18081" host1: cmd: "docker exec -i host1 /mnt/norouter" vip: "127.0.42.101" diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index a172d10..1a5b7bc 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -26,6 +26,7 @@ import ( agenthttp "github.com/norouter/norouter/pkg/agent/http" "github.com/norouter/norouter/pkg/agent/loopback" + agentsocks "github.com/norouter/norouter/pkg/agent/socks" "github.com/norouter/norouter/pkg/bicopy/bicopyutil" "github.com/norouter/norouter/pkg/netstackutil" "github.com/norouter/norouter/pkg/stream" @@ -156,23 +157,52 @@ func (a *Agent) configure(args *jsonmsg.ConfigureRequestArgs) error { } } } + if a.config.HTTP.Listen != "" { - logrus.Debugf("http listen=%q", a.config.HTTP.Listen) - l, err := net.Listen("tcp", a.config.HTTP.Listen) - if err != nil { + if err := a.configureHTTP(); err != nil { return err } - httpHandler, err := agenthttp.NewHandler(a.stack, a.config.HostnameMap) - if err != nil { - return err - } - srv := &http.Server{Handler: httpHandler} - go srv.Serve(l) } + + if a.config.SOCKS.Listen != "" { + if err := a.configureSOCKS(); err != nil { + return err + } + } + go a.sendL3Routine() return nil } +func (a *Agent) configureHTTP() error { + logrus.Debugf("http listen=%q", a.config.HTTP.Listen) + l, err := net.Listen("tcp", a.config.HTTP.Listen) + if err != nil { + return err + } + httpHandler, err := agenthttp.NewHandler(a.stack, a.config.HostnameMap) + if err != nil { + return err + } + srv := &http.Server{Handler: httpHandler} + go srv.Serve(l) + return nil +} + +func (a *Agent) configureSOCKS() error { + logrus.Debugf("socks listen=%q (supports SOCKS4/4a/5)", a.config.SOCKS.Listen) + l, err := net.Listen("tcp", a.config.SOCKS.Listen) + if err != nil { + return err + } + srv, err := agentsocks.NewServer(a.stack, a.config.HostnameMap) + if err != nil { + return err + } + go srv.Serve(l) + return nil +} + func (a *Agent) goGonetForward(me net.IP, f jsonmsg.Forward) error { if f.Proto != "tcp" { return errors.Errorf("expected proto be \"tcp\", got %q", f.Proto) diff --git a/pkg/agent/socks/socks.go b/pkg/agent/socks/socks.go new file mode 100644 index 0000000..adea50d --- /dev/null +++ b/pkg/agent/socks/socks.go @@ -0,0 +1,79 @@ +/* + Copyright (C) NoRouter authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package socks + +import ( + "context" + "net" + + "github.com/cybozu-go/usocksd/socks" + "github.com/pkg/errors" + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" + "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" + "gvisor.dev/gvisor/pkg/tcpip/stack" +) + +func NewServer(st *stack.Stack, hostnameMap map[string]net.IP) (*socks.Server, error) { + d, err := NewDialer(st, hostnameMap) + if err != nil { + return nil, err + } + s := &socks.Server{ + Dialer: d, + } + return s, nil +} + +func NewDialer(st *stack.Stack, hostnameMap map[string]net.IP) (socks.Dialer, error) { + d := &dialer{ + stack: st, + hostnameMap: hostnameMap, + } + return d, nil +} + +type dialer struct { + stack *stack.Stack + hostnameMap map[string]net.IP +} + +func (d *dialer) Dial(req *socks.Request) (net.Conn, error) { + ip := req.IP + if len(ip) == 0 { + if req.Hostname != "" { + if parsed := net.ParseIP(req.Hostname); len(parsed) != 0 { + ip = parsed + } + if resolved, ok := d.hostnameMap[req.Hostname]; ok { + ip = resolved + } + } + } + if len(ip) == 0 { + reqWithoutPassword := *req + if reqWithoutPassword.Password != "" { + reqWithoutPassword.Password = "********" + } + return nil, errors.Errorf("failed to determine IP for request %+v", reqWithoutPassword) + } + fullAddr := tcpip.FullAddress{ + Addr: tcpip.Address(ip), + Port: uint16(req.Port), + } + return gonet.DialContextTCP(context.TODO(), d.stack, fullAddr, ipv4.ProtocolNumber) +} diff --git a/pkg/manager/cmdclient.go b/pkg/manager/cmdclient.go index d04a229..dc6aeb0 100644 --- a/pkg/manager/cmdclient.go +++ b/pkg/manager/cmdclient.go @@ -83,6 +83,7 @@ func NewCmdClient(ctx context.Context, hostname string, pm *parsed.ParsedManifes configRequestArgs.HostnameMap[k] = v.VIP } configRequestArgs.HTTP.Listen = h.HTTP.Listen + configRequestArgs.SOCKS.Listen = h.SOCKS.Listen configRequestArgs.Loopback.Disable = h.Loopback.Disable configRequestArgsB, err := json.Marshal(configRequestArgs) if err != nil { diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index b495a3e..6a1936c 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -182,6 +182,13 @@ func (r *Manager) validateAgentFeatures(vip string, data jsonmsg.ConfigureResult vip, version.FeatureHTTP, cc.configRequestArgs.HTTP.Listen) } } + if cc.configRequestArgs.SOCKS.Listen != "" { + if _, ok := fm[version.FeatureSOCKS]; !ok { + // not a critical error + logrus.Warnf("%s lacks feature %q, SOCKS listen (%q) is ignored", + vip, version.FeatureSOCKS, cc.configRequestArgs.SOCKS.Listen) + } + } if cc.configRequestArgs.Loopback.Disable { if _, ok := fm[version.FeatureDisableLoopback]; !ok { return errors.Errorf("manifest has Loopback.Disable, but %s lacks feature %q, aborting for security purpose", diff --git a/pkg/manager/manifest/manifest.go b/pkg/manager/manifest/manifest.go index 9e70f83..52e72de 100644 --- a/pkg/manager/manifest/manifest.go +++ b/pkg/manager/manifest/manifest.go @@ -54,6 +54,9 @@ type Host struct { // HTTP can be specified since NoRouter v0.4.0 HTTP *HTTP `yaml:"http,omitempty"` + // SOCKS can be specified since NoRouter v0.4.0 + SOCKS *SOCKS `yaml:"socks,omitempty"` + // Loopback can be specified since NoRouter v0.4.0 Loopback *Loopback `yaml:"loopback,omitempty"` } @@ -66,6 +69,16 @@ type HTTP struct { Listen string `yaml:"listen,omitempty"` } +// SOCKS can be specified since NoRouter v0.4.0 +type SOCKS struct { + // Listen specifies an address of SOCKS proxy to be listened by NoRouter agent processes. + // The address is typically a local address, e.g. "127.0.0.1:18081". + // When the address is not specified, SOCKS proxy is disabled. + // + // Supported protocol versions: SOCKS4, SOCKS4a, and SOCKS5 + Listen string `yaml:"listen,omitempty"` +} + // Loopback can be specified since NoRouter v0.4.0 type Loopback struct { // Disable disables listening on multi-loopback addresses such as 127.0.42.100, 127.0.42.101... diff --git a/pkg/manager/manifest/parsed/parsed.go b/pkg/manager/manifest/parsed/parsed.go index c43b5c3..5f394ea 100644 --- a/pkg/manager/manifest/parsed/parsed.go +++ b/pkg/manager/manifest/parsed/parsed.go @@ -38,17 +38,22 @@ type Host struct { VIP net.IP Ports []*jsonmsg.Forward HTTP HTTP + SOCKS SOCKS Loopback Loopback } -type Loopback struct { - Disable bool -} - type HTTP struct { Listen string } +type SOCKS struct { + Listen string +} + +type Loopback struct { + Disable bool +} + func New(raw *manifest.Manifest) (*ParsedManifest, error) { if ht := raw.HostTemplate; ht != nil { if ht.VIP != "" { @@ -105,6 +110,9 @@ func New(raw *manifest.Manifest) (*ParsedManifest, error) { if raw.HostTemplate.HTTP != nil { h.HTTP.Listen = raw.HostTemplate.HTTP.Listen } + if raw.HostTemplate.SOCKS != nil { + h.SOCKS.Listen = raw.HostTemplate.SOCKS.Listen + } if raw.HostTemplate.Loopback != nil { h.Loopback.Disable = raw.HostTemplate.Loopback.Disable } @@ -112,6 +120,9 @@ func New(raw *manifest.Manifest) (*ParsedManifest, error) { if rh.HTTP != nil { h.HTTP.Listen = rh.HTTP.Listen } + if rh.SOCKS != nil { + h.SOCKS.Listen = rh.SOCKS.Listen + } if rh.Loopback != nil { h.Loopback.Disable = rh.Loopback.Disable } diff --git a/pkg/stream/jsonmsg/configure.go b/pkg/stream/jsonmsg/configure.go index 5e13ac9..19dec2d 100644 --- a/pkg/stream/jsonmsg/configure.go +++ b/pkg/stream/jsonmsg/configure.go @@ -34,6 +34,7 @@ type ConfigureRequestArgs struct { // Fields added in v0.4.0 HostnameMap map[string]net.IP `json:"hostnameMap,omitempty"` // hostname -> ip HTTP HTTP `json:"http,omitempty"` + SOCKS SOCKS `json:"socks,omitempty"` Loopback Loopback `json:"loopback,omitempty"` } @@ -60,6 +61,10 @@ type HTTP struct { Listen string `json:"listen,omitempty"` } +type SOCKS struct { + Listen string `json:"listen,omitempty"` +} + type Loopback struct { Disable bool `json:"disable,omitempty"` } diff --git a/pkg/version/features.go b/pkg/version/features.go index 7803652..713c6f7 100644 --- a/pkg/version/features.go +++ b/pkg/version/features.go @@ -25,8 +25,9 @@ const ( // Features introduced in v0.4.0: FeatureHTTP = "http" // Listening on HTTP for proxy FeatureDisableLoopback = "disable-loopback" // Disabling loopback + FeatureSOCKS = "socks" // Listening a SOCKS proxy (SOCKS4, SOCKS4a, and SOCKS5) // Features introduced in vX.Y.Z: // ... ) -var Features = []Feature{FeatureLoopback, FeatureTCP, FeatureHTTP, FeatureDisableLoopback} +var Features = []Feature{FeatureLoopback, FeatureTCP, FeatureHTTP, FeatureDisableLoopback, FeatureSOCKS}