-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial version with plain TCP stream
- Loading branch information
sasdf
committed
Apr 30, 2021
1 parent
c7a3ec2
commit ed7ee42
Showing
5 changed files
with
264 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,3 +13,5 @@ | |
|
||
# Dependency directories (remove the comment below to include it) | ||
# vendor/ | ||
|
||
build/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
#!/bin/env make | ||
|
||
|
||
PROGRAM = nyan | ||
|
||
ARCH = linux_386 linux_amd64 linux_arm linux_arm64 | ||
ARCH += darwin_amd64 darwin_arm64 | ||
ARCH += windows_386.exe windows_amd64.exe | ||
|
||
TARGETS := $(foreach arch,$(ARCH),$(addprefix $(PROGRAM)_,$(arch))) | ||
|
||
all: $(TARGETS) | ||
|
||
|
||
get_os = $(word 2,$(subst _, ,$(word 1,$(subst ., ,$@)))) | ||
get_arch = $(word 3,$(subst _, ,$(word 1,$(subst ., ,$@)))) | ||
|
||
$(TARGETS): %: | ||
@mkdir -p build | ||
CGO_ENABLED=0 GOOS=$(get_os) GOARCH=$(get_arch) go build -ldflags="-s -w" -o build/$@ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,48 @@ | ||
# nyan | ||
Yet another netcat for fast file transfer | ||
|
||
When I need to transfer a file in safe environment (e.g. LAN / VMs), | ||
I just want to use a simple command without cumbersome authentication, GUI, server etc. | ||
`ncat` usually works very well in this case. | ||
However, lastest ncat is super slow on Windows (~32KB/s), and that's why `nyan` was born. | ||
To my surprise, such a naive implementation works very well, even on linux. | ||
|
||
## Features | ||
* Plain TCP stream | ||
* you don't need to use `nyan` at both end | ||
* Progress indicator: Percentage, Size, ETA, Speed | ||
|
||
## Speed | ||
Testing commands: | ||
```sh | ||
Linux ncat: | ||
$ ncat -lvp 1234 --send-only < /dev/zero | ||
$ ncat 127.0.0.1 1234 --recv-only | pv -perb > /dev/null | ||
|
||
Linux nyan: | ||
$ nyan send /dev/zero 1234 | ||
$ nyan recv /dev/null 1234 127.0.0.1 | ||
|
||
Windows: | ||
$ zeros | ncat -lvp 1234 --send-only | ||
$ ncat 127.0.0.1 1234 --recv-only > NUL | ||
|
||
Windows nyan: | ||
$ zeros | nyan send - 1234 | ||
$ nyan recv NUL 1234 127.0.0.1 | ||
``` | ||
|
||
Testing environment: | ||
* Arch Linux host | ||
* Windows 10 20H2 KVM guest with virtio netif connected to local bridge | ||
* Arch Linux KVM guest with virtio netif connected to local bridge | ||
* [Ncat 5.59BETA1](https://nmap.org/ncat/) on Windows performs better than latest version. | ||
|
||
| A | B | nyan (A->B) | ncat (A->B) | nyan (B->A) | ncat (B->A) | | ||
|:----------:|:-----------:|:-----------:|:-----------:|:-----------:|:-----------:| | ||
| Linux Host | localhost | 6.2 GB/s | 2.6 GB/s | - | - | | ||
| Linux VM | localhost | 6.2 GB/s | 1.9 GB/s | - | - | | ||
| Windows VM | localhost | 2.9 GB/s | 0.6 GB/s | - | - | | ||
| Linux Host | Linux VM | 0.8 GB/s | 0.7 GB/s | 5.0 GB/s | 2.2 GB/s | | ||
| Linux Host | Windows VM | 3.2 GB/s | 1.2 GB/s | 2.6 GB/s | 0.3 GB/s | | ||
| Linux VM | Windows VM | 2.5 GB/s | 0.8 GB/s | 0.6 GB/s | 0.3 GB/s | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module github.com/sasdf/nyan | ||
|
||
go 1.16 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"math" | ||
"time" | ||
"io" | ||
"strings" | ||
"net" | ||
_ "hash/crc32" | ||
) | ||
|
||
|
||
const bufSize = 1<<20 | ||
const minPrintInterval = 0.1 | ||
|
||
const usageStr = ` | ||
Usage: nyan <command> [args...] | ||
Receive file: | ||
nyan recv <file> <port> [host] | ||
Receive data using TCP from host:port and save to <file>. | ||
If host argument is omitted, it will listen on port <port>. | ||
If <file> is "-", data will be written to stdout. | ||
Send file: | ||
nyan send <file> <port> [host] | ||
Send <file> to using TCP to host:port. | ||
If host argument is omitted, it will listen on port <port>. | ||
If <file> is "-", data will be read from stdin. | ||
` | ||
|
||
type Pipe interface { | ||
Read(b []byte) (n int, err error) | ||
Write(b []byte) (n int, err error) | ||
Close() error | ||
} | ||
|
||
func fatal(err error) { | ||
fmt.Fprintln(os.Stderr, "[!] Fatal error:", err) | ||
os.Exit(1) | ||
} | ||
|
||
func usage() { | ||
fmt.Fprintln(os.Stderr, strings.TrimSpace(usageStr)) | ||
os.Exit(1) | ||
} | ||
|
||
func connect(port string, host string) net.Conn { | ||
addr := net.JoinHostPort(host, port) | ||
|
||
if host == "" { | ||
ln, err := net.Listen("tcp", addr) | ||
if err != nil { fatal(err) } | ||
defer ln.Close() | ||
|
||
fmt.Fprintln(os.Stderr, "[*] Listening on", addr) | ||
conn, err := ln.Accept() | ||
if err != nil { fatal(err) } | ||
fmt.Fprintln(os.Stderr, "[+] Connection from", conn.RemoteAddr()) | ||
return conn | ||
} else { | ||
conn, err := net.Dial("tcp", addr) | ||
if err != nil { fatal(err) } | ||
fmt.Fprintln(os.Stderr, "[+] Connected to", conn.RemoteAddr()) | ||
return conn | ||
} | ||
|
||
} | ||
|
||
func open(path string, write bool) (file *os.File, err error) { | ||
if path == "-" && write { return os.Stdout, nil } | ||
if path == "-" { return os.Stdin, nil } | ||
if write { return os.Create(path) } | ||
return os.Open(path) | ||
} | ||
|
||
func fmtsi(x float64) string { | ||
for _, u := range " KMGTP" { | ||
if x < 1000 { return fmt.Sprintf("%6.2f%c", x, u) } | ||
x /= 1024 | ||
} | ||
return fmt.Sprintf("%6.2f%s", x, "E") | ||
} | ||
|
||
func fmttime(x_ float64) string { | ||
x := int64(math.Round(x_)) | ||
return fmt.Sprintf("%02d:%02d:%02d", x/3600, x/60%60, x%60) | ||
} | ||
|
||
var lastPrint *time.Time = nil | ||
func progress(cur_ int64, total_ int64, dur_ time.Duration, force bool) { | ||
now := time.Now() | ||
if !force && lastPrint != nil && now.Sub(*lastPrint).Seconds() < minPrintInterval { | ||
return | ||
} | ||
lastPrint = &now | ||
|
||
cur, total, dur := float64(cur_), float64(total_), dur_.Seconds() | ||
if dur < 0.1 { dur = 0.1 } | ||
|
||
speed := cur / dur | ||
|
||
if total_ == 0 { | ||
fmt.Fprintf( | ||
os.Stderr, | ||
"\r[o] %sB [%s] (%sB/s)", | ||
fmtsi(cur), | ||
fmttime(dur), | ||
fmtsi(speed), | ||
) | ||
} else { | ||
ratio := cur / total | ||
eta := dur / ratio | ||
fmt.Fprintf( | ||
os.Stderr, | ||
"\r[o] %6.2f%% - %sB / %sB [%s / %s] (%sB/s)", | ||
ratio * 100.0, | ||
fmtsi(cur), fmtsi(total), | ||
fmttime(dur), fmttime(eta), | ||
fmtsi(speed), | ||
) | ||
} | ||
} | ||
|
||
func pipe(src Pipe, dst Pipe, size int64) { | ||
var buf [bufSize]byte | ||
var cur int64 = 0 | ||
// crc32State := crc32.NewIEEE() | ||
start := time.Now() | ||
for { | ||
nbytes, err := src.Read(buf[:]) | ||
if err == io.EOF { break } | ||
if err != nil { fmt.Fprintf(os.Stderr, "\n"); fatal(err) } | ||
_, err = dst.Write(buf[:nbytes]) | ||
if err != nil { fmt.Fprintf(os.Stderr, "\n"); fatal(err) } | ||
// _, err = crc32State.Write(buf[:nbytes]) | ||
// if err != nil { fmt.Fprintf(os.Stderr, "\n"); fatal(err) } | ||
|
||
cur += int64(nbytes) | ||
progress(cur, size, time.Now().Sub(start), false) | ||
} | ||
fmt.Fprintf(os.Stderr, "\n") | ||
// fmt.Fprintf(os.Stderr, "[+] CRC32: %08x\n", crc32State.Sum32()) | ||
} | ||
|
||
func main() { | ||
if len(os.Args) < 4 { | ||
usage() | ||
} | ||
|
||
cmd := os.Args[1] | ||
path := os.Args[2] | ||
port := os.Args[3] | ||
host := ""; if len(os.Args) > 4 { host = os.Args[4] } | ||
|
||
var file *os.File | ||
var err error | ||
var size int64 = 0 | ||
|
||
if cmd == "recv" { | ||
file, err = open(path, true) | ||
} else if cmd == "send" { | ||
file, err = open(path, false) | ||
} else { | ||
fmt.Fprintf(os.Stderr, "Unknown command: %s\n", cmd) | ||
usage() | ||
} | ||
if err != nil { fatal(err) } | ||
defer file.Close() | ||
|
||
info, err := file.Stat() | ||
if err != nil { fatal(err) } | ||
size = info.Size() | ||
|
||
if size > 0 { | ||
fmt.Fprintf(os.Stderr, "[+] File Size: %sB\n", fmtsi(float64(size))) | ||
} | ||
|
||
conn := connect(port, host) | ||
defer conn.Close() | ||
|
||
if cmd == "send" { | ||
pipe(file, conn, size) | ||
} else if cmd == "recv" { | ||
pipe(conn, file, size) | ||
} else { | ||
fatal(fmt.Errorf("WTF: Unknown command: %s", cmd)) | ||
} | ||
} |