Skip to content

Commit

Permalink
Merge branch 'release/0.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
nwtgck committed Oct 12, 2020
2 parents 9426ac2 + bd80f8c commit f5fb916
Show file tree
Hide file tree
Showing 10 changed files with 435 additions and 105 deletions.
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,18 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)

## [Unreleased]

## [0.2.0] - 2020-10-12
### Changed
* Change server-host as "server" subcommand, not root command
* Allow one rest argument to specify path

### Added
* Create "client" subcommand
* Create --progress flag to show upload/download progress (default: true)

## 0.1.0 - 2020-10-01
### Added
* Initial release

[Unreleased]: https://github.com/nwtgck/go-piping-tunnel/compare/v0.1.0...HEAD
[Unreleased]: https://github.com/nwtgck/go-piping-tunnel/compare/v0.2.0...HEAD
[0.2.0]: https://github.com/nwtgck/go-piping-tunnel/compare/v0.1.0...v0.2.0
51 changes: 49 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# piping-tunnel
[![CircleCI](https://circleci.com/gh/nwtgck/go-piping-tunnel.svg?style=shield)](https://app.circleci.com/pipelines/github/nwtgck/go-piping-tunnel)

Tunnel CLI over Piping Server
Tunneling over HTTP with [Piping Server](https://github.com/nwtgck/piping-server)

## Help

Expand All @@ -10,15 +10,62 @@ Tunnel over Piping Server
Usage:
piping-tunnel [flags]
piping-tunnel [command]
Available Commands:
client Run client-host
help Help about any command
server Run server-host
Flags:
--dns-server string DNS server (e.g. 1.1.1.1:53)
-h, --help help for piping-tunnel
-k, --insecure Allow insecure server connections when using SSL
-p, --port int TCP port of server host
--progress Show progress (default true)
-s, --server string Piping Server URL (default "https://ppng.io")
-v, --version show version
Use "piping-tunnel [command] --help" for more information about a command.
```

The following help is for server-host.
```
Run server-host
Usage:
piping-tunnel server [flags]
Flags:
-h, --help help for server
-p, --port int TCP port of server host
Global Flags:
--dns-server string DNS server (e.g. 1.1.1.1:53)
-k, --insecure Allow insecure server connections when using SSL
--progress Show progress (default true)
-s, --server string Piping Server URL (default "https://ppng.io")
```

The following help is for client-host.
```
Run client-host
Usage:
piping-tunnel client [flags]
Flags:
-h, --help help for client
-p, --port int TCP port of client host
Global Flags:
--dns-server string DNS server (e.g. 1.1.1.1:53)
-k, --insecure Allow insecure server connections when using SSL
--progress Show progress (default true)
-s, --server string Piping Server URL (default "https://ppng.io")
```

## References
- (Japanese) <https://qiita.com/Cryolite/items/ed8fa237dd8eab54ef2f>

## Related work
- [portwarp](https://github.com/essa/portwarp)
110 changes: 110 additions & 0 deletions cmd/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package cmd

import (
"fmt"
"github.com/nwtgck/go-piping-tunnel/io_progress"
"github.com/nwtgck/go-piping-tunnel/util"
"github.com/spf13/cobra"
"io"
"net"
"net/http"
"os"
"strings"
"time"
)

var clientHostPort int

func init() {
RootCmd.AddCommand(clientCmd)
clientCmd.Flags().IntVarP(&clientHostPort, "port", "p", 0, "TCP port of client host")
}

var clientCmd = &cobra.Command{
Use: "client",
Short: "Run client-host",
RunE: func(cmd *cobra.Command, args []string) error {
clientToServerPath, serverToClientPath, err := generatePaths(args)
if err != nil {
return err
}
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", clientHostPort))
if err != nil {
return err
}

clientToServerUrl, err := util.UrlJoin(serverUrl, clientToServerPath)
if err != nil {
return err
}
serverToClientUrl, err := util.UrlJoin(serverUrl, serverToClientPath)
if err != nil {
return err
}
// (from: https://stackoverflow.com/a/43425461)
clientHostPort = ln.Addr().(*net.TCPAddr).Port
fmt.Printf("[INFO] Client host listening on %d ...\n", clientHostPort)
fmt.Println("[INFO] Hint: Server host (socat + curl)")
fmt.Printf(
" socat 'EXEC:curl -NsS %s!!EXEC:curl -NsST - %s' TCP:127.0.0.1:<YOUR PORT>\n",
strings.Replace(clientToServerUrl, ":", "\\:", -1),
strings.Replace(serverToClientUrl, ":", "\\:", -1),
)
fmt.Println("[INFO] Hint: Server host (piping-tunnel)")
fmt.Printf(
" piping-tunnel -s %s server -p <YOUR PORT> %s %s\n",
serverUrl,
clientToServerPath,
serverToClientPath,
)
conn, err := ln.Accept()
if err != nil {
return err
}
fmt.Println("[INFO] accepted")
// Refuse another new connection
ln.Close()
httpClient := util.CreateHttpClient(insecure)
if dnsServer != "" {
// Set DNS resolver
httpClient.Transport.(*http.Transport).DialContext = util.CreateDialContext(dnsServer)
}
var progress *io_progress.IOProgress = nil
if showProgress {
p := io_progress.NewIOProgress(conn, os.Stderr, func(progress *io_progress.IOProgress) string {
return fmt.Sprintf(
"↑ %s (%s/s) | ↓ %s (%s/s)",
util.HumanizeBytes(float64(progress.CurrReadBytes)),
util.HumanizeBytes(float64(progress.CurrReadBytes)/time.Since(progress.StartTime).Seconds()),
util.HumanizeBytes(float64(progress.CurrWriteBytes)),
util.HumanizeBytes(float64(progress.CurrWriteBytes)/time.Since(progress.StartTime).Seconds()),
)
})
progress = &p
}
var reader io.Reader = conn
if progress != nil {
reader = progress
}
_, err = httpClient.Post(clientToServerUrl, "application/octet-stream", reader)
if err != nil {
return err
}
res, err := httpClient.Get(serverToClientUrl)
if err != nil {
return err
}
var writer io.Writer = conn
if progress != nil {
writer = io.MultiWriter(conn, progress)
}
_, err = io.Copy(writer, res.Body)
fmt.Println()
if err != nil {
return err
}
fmt.Println("[INFO] Finished")

return nil
},
}
107 changes: 7 additions & 100 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,42 +1,34 @@
package cmd

import (
"context"
"crypto/tls"
"fmt"
"github.com/nwtgck/go-piping-tunnel/version"
"github.com/spf13/cobra"
"io"
"net"
"net/http"
"net/url"
"os"
"path"
"time"
)

const (
ServerUrlEnvName = "PIPING_SERVER_URL"
)

var serverUrl string
var tcpPort int
var insecure bool
var dnsServer string
var showsVersion bool
var showProgress bool

func init() {
cobra.OnInitialize()
RootCmd.AddCommand(serverCmd)
defaultServer, ok := os.LookupEnv(ServerUrlEnvName)
if !ok {
defaultServer = "https://ppng.io"
}
RootCmd.Flags().StringVarP(&serverUrl, "server", "s", defaultServer, "Piping Server URL")
RootCmd.Flags().IntVarP(&tcpPort, "port", "p", 0, "TCP port of server host")
RootCmd.MarkFlagRequired("port")
RootCmd.PersistentFlags().StringVarP(&serverUrl, "server", "s", defaultServer, "Piping Server URL")
RootCmd.PersistentFlags().StringVar(&dnsServer, "dns-server", "", "DNS server (e.g. 1.1.1.1:53)")
// NOTE: --insecure, -k is inspired by curl
RootCmd.Flags().BoolVarP(&insecure, "insecure", "k", false, "Allow insecure server connections when using SSL")
RootCmd.Flags().StringVar(&dnsServer, "dns-server", "", "DNS server (e.g. 1.1.1.1:53)")
RootCmd.PersistentFlags().BoolVarP(&insecure, "insecure", "k", false, "Allow insecure server connections when using SSL")
RootCmd.PersistentFlags().BoolVarP(&showProgress, "progress", "", true, "Show progress")
RootCmd.Flags().BoolVarP(&showsVersion, "version", "v", false, "show version")
}

Expand All @@ -49,91 +41,6 @@ var RootCmd = &cobra.Command{
fmt.Println(version.Version)
return nil
}
if len(args) != 2 {
return fmt.Errorf("Path 1 and path 2 are required\n")
}

path1 := args[0]
path2 := args[1]

conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", tcpPort))
if err != nil {
panic(err)
}
defer conn.Close()

httpClient := getHttpClient(insecure)
if dnsServer != "" {
// Set DNS resolver
httpClient.Transport.(*http.Transport).DialContext = dialContext(dnsServer)
}

url2, err := urlJoin(serverUrl, path2)
if err != nil {
panic(err)
}
_, err = httpClient.Post(url2, "application/octet-stream", conn)
if err != nil {
panic(err)
}
fmt.Println("after POST")

url1, err := urlJoin(serverUrl, path1)
if err != nil {
panic(err)
}
res, err := httpClient.Get(url1)
if err != nil {
panic(err)
}
_, err = io.Copy(conn, res.Body)
if err != nil {
panic(err)
}
fmt.Println("after GET")

return nil
return cmd.Help()
},
}

// (base: https://stackoverflow.com/a/34668130/2885946)
func urlJoin(s string, p string) (string, error) {
u, err := url.Parse(s)
if err != nil {
return "", err
}
u.Path = path.Join(u.Path, p)
return u.String(), nil
}

// Generate HTTP client
func getHttpClient(insecure bool) *http.Client {
// Set insecure or not
tr := &http.Transport{
TLSClientConfig: &tls.Config{ InsecureSkipVerify: insecure },
}
return &http.Client{Transport: tr}
}


// Set default resolver for HTTP client
func dialContext(dnsServer string) func(ctx context.Context, network, address string) (net.Conn, error) {
resolver := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{
Timeout: time.Millisecond * time.Duration(10000),
}
return d.DialContext(ctx, "udp", dnsServer)
},
}

// Resolver for HTTP
return func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{
Timeout: time.Millisecond * time.Duration(10000),
Resolver: resolver,
}
return d.DialContext(ctx, network, address)
}
}
Loading

0 comments on commit f5fb916

Please sign in to comment.