diff --git a/.gitignore b/.gitignore index 0d5cdd8..5dfcfd1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ gtunnel* *~ *bak +*swp diff --git a/README.md b/README.md index 89f7a06..ac1eb4c 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,22 @@ gtunnel ====== -A stunnel like util write by golang +A stunnel-like util write by golang -There are two work mode. +work mode +======== -###Client mode +- listen plain, forward through plain +- listen plain, forward through TLS +- listen TLS, forward through plain +- listen TLS, forward through TLS -In this mode, listen for incoming plain connections and forward to server to use SSL/TLS connections -###Server mode - -In this mode, listen for incoming SSL/TLS connections and forward to backend use plain connections - -###Build +usage +==== go get github.com/fangdingjun/gtunnel - cd $GOPATH/src/github.com/fangdingjun/gtunnel - go build - -###Usage - -server mode - - ./gtunnel --server --cert server.crt --key server.key --port 8001 --remote 127.0.0.1:80 - -listen for SSL/TLS connections on port 8001 and forward to 127.0.0.1:80 - -client mode - - ./gtunnel --client --port 8002 --remote www.example.com:8081 - -listen for plain connections on port 8002 and forward to www.example.com:8081 to use SSL/TLS connections - -use `./gtunnel -h` see more options + cp $GOPATH/src/github.com/fangdingjun/config_example.yaml config.yaml + vim config.yaml + $GOPATH/bin/gtunnel -c config.yaml diff --git a/conf_test.go b/conf_test.go new file mode 100644 index 0000000..109d7af --- /dev/null +++ b/conf_test.go @@ -0,0 +1,14 @@ +package main + +import ( + "fmt" + "testing" +) + +func TestConf(t *testing.T) { + c, err := loadConfig("config_example.yaml") + if err != nil { + t.Fatal(err) + } + fmt.Printf("%#v\n", c) +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..84fba9d --- /dev/null +++ b/config.go @@ -0,0 +1,44 @@ +package main + +import ( + //"fmt" + "github.com/go-yaml/yaml" + "io/ioutil" +) + +type server struct { + Listen listen + Backend backend +} + +type conf []server + +type listen struct { + Host string + Port int + Cert string + Key string +} + +type backend struct { + Host string + Port int + Hostname string + TLS bool + Insecure bool +} + +func loadConfig(fn string) (*conf, error) { + data, err := ioutil.ReadFile(fn) + if err != nil { + return nil, err + } + + c := new(conf) + err = yaml.Unmarshal(data, c) + if err != nil { + return nil, err + } + + return c, nil +} diff --git a/config_example.yaml b/config_example.yaml new file mode 100644 index 0000000..5819d8b --- /dev/null +++ b/config_example.yaml @@ -0,0 +1,57 @@ +# gtunnel can do the follow things +# a. listen plain, forward through TLS +# b. listen plain, forward through plain +# c. listen TLS, forward through plain +# d. listen TLS, forward through TLS +# +# when cert and key specified it will listen on TLS +# + +- + # listen plain and forward through TLS + listen: + host: 0.0.0.0 + port: 4120 + + backend: + host: 1.2.3.4 + port: 443 + + # tls sni + hostname: example.com + + # use TLS + tls: true + + # verify server certificate or not + # when set true, will not verify the server's certificate(danger) + insecure: false + +- + # listen tls and forward through plain + listen: + host: 0.0.0.0 + port: 443 + cert: www.crt + key: www.key + + backend: + host: 127.0.0.1 + port: 4120 + tls: false + +# more port forwards + +#- +# # listen TLS, forward through TLS +# listen: +# host: 0.0.0.0 +# port: 3122 +# cert: file.crt +# key: file.key +# +# backend: +# host: www.example.com +# port: 443 +# hostname: example.com +# tls: true diff --git a/local.go b/local.go deleted file mode 100644 index 7a4e64e..0000000 --- a/local.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "crypto/tls" - "fmt" - "io" - "log" - "net" -) - -//var server string = "www.ratafee.nl:443" - -func local_main() { - l, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) - if err != nil { - log.Fatal(err) - } - log.Printf("Listen on %s...", l.Addr()) - defer l.Close() - - for { - c, err := l.Accept() - if err != nil { - log.Println(err) - continue - } - go handle_local(c) - } -} - -func handle_local(c net.Conn) { - //defer c.Close() - log.Printf("accept connection from %s", c.RemoteAddr()) - log.Printf("connect to %s...", remote) - s, err := tls.Dial("tcp", remote, nil) - if err != nil { - log.Printf("connect to %s failed: %s", remote, err.Error()) - c.Close() - return - } - ch := make(chan int) - //defer s.Close() - go func() { - count, _ := io.Copy(s, c) - log.Printf("write %d bytes to %s", count, s.RemoteAddr()) - s.Close() - ch <- 1 - }() - co, _ := io.Copy(c, s) - log.Printf("write %d bytes to %s", co, c.RemoteAddr()) - c.Close() - <-ch -} diff --git a/main.go b/main.go index 836834b..d3b7a6d 100644 --- a/main.go +++ b/main.go @@ -1,39 +1,111 @@ package main import ( + "crypto/tls" "flag" - "github.com/fangdingjun/iniflags" + "fmt" + "io" "log" + "net" ) -var remote string - func main() { - var server, client bool - flag.StringVar(&remote, "remote", "", "remote server") - flag.IntVar(&port, "port", 8080, "the listen port") - flag.BoolVar(&server, "server", false, "tls server mode") - flag.BoolVar(&client, "client", false, "tls client mode") - flag.StringVar(&cert, "cert", "", "the certificate file") - flag.StringVar(&key, "key", "", "the private key") - iniflags.Parse() - - if remote == "" { - log.Fatal("please use --remote to special the server") + var configfile string + + flag.StringVar(&configfile, "c", "config.yaml", "config file") + flag.Parse() + + cfg, err := loadConfig(configfile) + if err != nil { + log.Fatal(err) } - if server { - if cert == "" || key == "" { - log.Fatal("in server mode, you must special the certificate and private key") + initServer(cfg) + + select {} +} + +func initServer(cfg *conf) { + for _, srv := range *cfg { + go initListener(srv) + } +} + +func initListener(srv server) { + var l net.Listener + var err error + + host := net.JoinHostPort(srv.Listen.Host, fmt.Sprintf("%d", srv.Listen.Port)) + + if srv.Listen.Cert != "" && srv.Listen.Key != "" { + cert, err := tls.LoadX509KeyPair(srv.Listen.Cert, srv.Listen.Key) + if err != nil { + log.Fatal(err) } - server_main() - return + config := &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + + l, err = tls.Listen("tcp", host, config) + } else { + l, err = net.Listen("tcp", host) + } + + if err != nil { + log.Fatal(err) + } + + for { + conn, err := l.Accept() + if err != nil { + log.Println(err) + break + } + go handleConn(conn, srv.Backend) } +} + +func handleConn(conn net.Conn, b backend) { + var c net.Conn + var err error - if client { - local_main() + host := net.JoinHostPort(b.Host, fmt.Sprintf("%d", b.Port)) + if b.TLS { + hostname := b.Host + if b.Hostname != "" { + hostname = b.Hostname + } + config := &tls.Config{ + ServerName: hostname, + InsecureSkipVerify: b.Insecure, + } + c, err = tls.Dial("tcp", host, config) + } else { + c, err = net.Dial("tcp", host) + } + + if err != nil { + log.Println(err) return } - log.Fatal("please use --server or --client to special a work mode") + pipeAndClose(conn, c) +} + +func pipeAndClose(c1, c2 net.Conn) { + defer c1.Close() + defer c2.Close() + + ch := make(chan struct{}, 2) + go func() { + io.Copy(c1, c2) + ch <- struct{}{} + }() + + go func() { + io.Copy(c2, c1) + ch <- struct{}{} + }() + + <-ch } diff --git a/server.go b/server.go deleted file mode 100644 index df2b59a..0000000 --- a/server.go +++ /dev/null @@ -1,60 +0,0 @@ -package main - -import ( - "crypto/tls" - "fmt" - "io" - "log" - "net" -) - -//var backend = "127.0.0.1:8080" -var cert = "server.crt" -var key = "server.key" -var port = 9000 - -func server_main() { - certificate, err := tls.LoadX509KeyPair(cert, key) - if err != nil { - log.Fatal(err) - } - l, err := tls.Listen("tcp", fmt.Sprintf(":%d", port), &tls.Config{ - Certificates: []tls.Certificate{certificate}, - }) - if err != nil { - log.Fatal(err) - } - defer l.Close() - - for { - c, err := l.Accept() - if err != nil { - log.Println(err) - continue - } - go handle_server(c) - } -} - -func handle_server(c net.Conn) { - log.Printf("accept connection from %s", c.RemoteAddr()) - s, err := net.Dial("tcp", remote) - if err != nil { - log.Println(err) - c.Close() - return - } - - ch := make(chan int) - go func() { - count, _ := io.Copy(s, c) - s.Close() - log.Printf("write %d bytes to %s", count, s.RemoteAddr()) - ch <- 1 - }() - - co, _ := io.Copy(c, s) - c.Close() - log.Printf("write %d bytes to %s", co, c.RemoteAddr()) - <-ch -}