First commit, forked from wormhole

This commit is contained in:
Zack Scholl 2017-10-17 09:21:47 -06:00
commit a82fe6f074
5 changed files with 410 additions and 0 deletions

55
README.md Normal file
View File

@ -0,0 +1,55 @@
# personalportal
*File transfer over parallel TCP, running a rendevouz server (but you don't need to do any port-forwarding!.*
This program pays homage to [magic-wormhole](https://github.com/warner/magic-wormhole) except it doesn't have the rendevous server, or the transit relay, or the password-authenticated key exchange. Its not really anything like it, except that its file transfer over TCP. Here you can transfer a file using multiple TCP ports simultaneously.
## Normal use
### Server computer
Be sure to open up TCP ports 27001-27009 on your port forwarding. Also, get your public address:
```
$ curl icanhazip.com
X.Y.W.Z
```
Then get and run *wormhole*:
```
$ go get github.com/schollz/wormhole
$ wormhole -file SOMEFILE
```
*personalportal* automatically knows to run as a server when the `-file` flag is set.
### Client computer
```
$ go get github.com/schollz/wormhole
$ wormhole -server X.Y.W.Z
```
*personalportal* automatically knows to run as a client when the `-server` flag is set.
## Building for use without flags
For people that don't have or don't want to build from source and don't want to use the command line, you can build it for them to have the flags set automatically! Build the wormhole binary so that it always behaves as a client to a specified server, so that someone just needs to click on it.
```
cd $GOPATH/src/github.com/schollz/wormhole
go build -ldflags "-s -w -X main.serverAddress=X.Y.W.Z" -o client.exe
```
Likewise you could do the same for the server:
```
cd $GOPATH/src/github.com/schollz/wormhole
go build -ldflags "-s -w -X main.fileName=testfile" -o server.exe
```
# License
MIT

91
client.go Normal file
View File

@ -0,0 +1,91 @@
package main
import (
"fmt"
"io"
"net"
"os"
"os/exec"
"strconv"
"strings"
"sync"
"time"
"github.com/gosuri/uiprogress"
log "github.com/sirupsen/logrus"
)
func runClient() {
uiprogress.Start()
var wg sync.WaitGroup
wg.Add(numberConnections)
bars := make([]*uiprogress.Bar, numberConnections)
for id := 0; id < numberConnections; id++ {
go func(id int) {
defer wg.Done()
port := strconv.Itoa(27001 + id)
connection, err := net.Dial("tcp", "localhost:"+port)
if err != nil {
panic(err)
}
defer connection.Close()
bufferFileName := make([]byte, 64)
bufferFileSize := make([]byte, 10)
connection.Read(bufferFileSize)
fileSize, _ := strconv.ParseInt(strings.Trim(string(bufferFileSize), ":"), 10, 64)
bars[id] = uiprogress.AddBar(int(fileSize+1028) / 1024).AppendCompleted().PrependElapsed()
connection.Read(bufferFileName)
fileName = strings.Trim(string(bufferFileName), ":")
os.Remove(fileName + "." + strconv.Itoa(id))
newFile, err := os.Create(fileName + "." + strconv.Itoa(id))
if err != nil {
panic(err)
}
defer newFile.Close()
var receivedBytes int64
for {
if (fileSize - receivedBytes) < BUFFERSIZE {
io.CopyN(newFile, connection, (fileSize - receivedBytes))
// Empty the remaining bytes that we don't need from the network buffer
connection.Read(make([]byte, (receivedBytes+BUFFERSIZE)-fileSize))
break
}
io.CopyN(newFile, connection, BUFFERSIZE)
//Increment the counter
receivedBytes += BUFFERSIZE
bars[id].Incr()
}
}(id)
}
wg.Wait()
// cat the file
os.Remove(fileName)
finished, err := os.Create(fileName)
defer finished.Close()
if err != nil {
log.Fatal(err)
}
for id := 0; id < numberConnections; id++ {
fh, err := os.Open(fileName + "." + strconv.Itoa(id))
if err != nil {
log.Fatal(err)
}
_, err = io.Copy(finished, fh)
if err != nil {
log.Fatal(err)
}
fh.Close()
os.Remove(fileName + "." + strconv.Itoa(id))
}
cmd := exec.Command("cmd", "/c", "cls")
cmd.Stdout = os.Stdout
cmd.Run()
fmt.Println("\n\n\nDownloaded " + fileName + "!")
time.Sleep(1 * time.Second)
}

73
main.go Normal file
View File

@ -0,0 +1,73 @@
package main
import (
"flag"
"fmt"
log "github.com/sirupsen/logrus"
)
const BUFFERSIZE = 1024
const numberConnections = 8
// Build flags
var server, file string
// Global varaibles
var serverAddress, fileName string
func main() {
flag.StringVar(&serverAddress, "server", "", "(run as client) server address to connect to")
flag.StringVar(&fileName, "file", "", "(run as server) file to serve")
flag.Parse()
// Check build flags too, which take precedent
if server != "" {
serverAddress = server
}
if file != "" {
fileName = file
}
fmt.Println(`
* ,MMM8&&&. *
MMMM88&&&&& .
MMMM88&&&&&&&
* MMM88&&&&&&&&
MMM88&&&&&&&&
'MMM88&&&&&&'
'MMM8&&&' *
|\___/|
) ( . '
=\ /=
)===( *
/ \
| |
/ \
\ /
_/\_/\_/\__ _/_/\_/\_/\_/\_/\_/\_/\_/\_/\_
| | | |( ( | | | | | | | | | |
| | | | ) ) | | | | | | | | | |
| | | |(_( | | | | | | | | | |
| | | | | | | | | | | | | | |
| | | | | | | | | | | | | | |
`)
if len(fileName) != 0 {
runServer()
} else if len(serverAddress) != 0 {
runClient()
} else {
fmt.Println("You must specify either -file (for running as a server) or -server (for running as a client)")
}
}
func init() {
// Log as JSON instead of the default ASCII formatter.
// log.SetFormatter(&log.JSONFormatter{})
log.SetFormatter(&log.TextFormatter{})
// Output to stdout instead of the default stderr
// Can be any io.Writer, see below for File example
// log.SetOutput(os.Stdout)
// Only log the warning severity or above.
log.SetLevel(log.DebugLevel)
}

57
rendevouz.go Normal file
View File

@ -0,0 +1,57 @@
package main
import (
"flag"
"io"
"log"
"net"
)
var from *string
var to *string
func init() {
from = flag.String("from", "0.0.0.0:443", "The address and port that wormhole should listen on. Connections enter here.")
to = flag.String("to", "127.0.0.1:80", "Specifies the address and port that wormhole should redirect TCP connections to. Connections exit here.")
flag.Parse()
}
func main() {
// Listen on the specified TCP port on all interfaces.
l, err := net.Listen("tcp", *from)
if err != nil {
log.Fatal(err)
}
defer l.Close()
for {
// Wait for a connection.
c, err := l.Accept()
if err != nil {
log.Fatal(err)
}
// handle the connection in a goroutine
go wormhole(c)
}
}
// wormhole opens a wormhole from the client connection
// to user the specified destination
func wormhole(c net.Conn) {
defer c.Close()
log.Println("Opening wormhole from", c.RemoteAddr())
// connect to the destination tcp port
destConn, err := net.Dial("tcp", *to)
if err != nil {
log.Fatal("Error connecting to destination port")
}
defer destConn.Close()
log.Println("Wormhole open from", c.RemoteAddr())
go func() { io.Copy(c, destConn) }()
io.Copy(destConn, c)
log.Println("Stopping wormhole from", c.RemoteAddr())
}

134
server.go Normal file
View File

@ -0,0 +1,134 @@
package main
import (
"fmt"
"io"
"math"
"net"
"os"
"strconv"
"sync"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
func runServer() {
logger := log.WithFields(log.Fields{
"function": "main",
})
logger.Info("Initializing")
var wg sync.WaitGroup
wg.Add(numberConnections)
for id := 0; id < numberConnections; id++ {
go listenerThread(id, &wg)
}
wg.Wait()
}
func listenerThread(id int, wg *sync.WaitGroup) {
logger := log.WithFields(log.Fields{
"function": "listenerThread@" + serverAddress + ":" + strconv.Itoa(27000+id),
})
defer wg.Done()
err := listener(id)
if err != nil {
logger.Error(err)
}
}
func listener(id int) (err error) {
port := strconv.Itoa(27001 + id)
logger := log.WithFields(log.Fields{
"function": "listener@" + serverAddress + ":" + port,
})
server, err := net.Listen("tcp", serverAddress+":"+port)
if err != nil {
return errors.Wrap(err, "Error listening on "+serverAddress+":"+port)
}
defer server.Close()
logger.Info("waiting for connections")
//Spawn a new goroutine whenever a client connects
for {
connection, err := server.Accept()
if err != nil {
return errors.Wrap(err, "problem accepting connection")
}
logger.Info("Client connected")
go sendFileToClient(id, connection)
}
}
//This function is to 'fill'
func fillString(retunString string, toLength int) string {
for {
lengthString := len(retunString)
if lengthString < toLength {
retunString = retunString + ":"
continue
}
break
}
return retunString
}
func sendFileToClient(id int, connection net.Conn) {
logger := log.WithFields(log.Fields{
"function": "sendFileToClient #" + strconv.Itoa(id),
})
defer connection.Close()
//Open the file that needs to be send to the client
file, err := os.Open(fileName)
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
//Get the filename and filesize
fileInfo, err := file.Stat()
if err != nil {
fmt.Println(err)
return
}
numChunks := math.Ceil(float64(fileInfo.Size()) / float64(BUFFERSIZE))
chunksPerWorker := int(math.Ceil(numChunks / float64(numberConnections)))
bytesPerConnection := int64(chunksPerWorker * BUFFERSIZE)
if id+1 == numberConnections {
bytesPerConnection = fileInfo.Size() - (numberConnections-1)*bytesPerConnection
}
fileSize := fillString(strconv.FormatInt(int64(bytesPerConnection), 10), 10)
fileName := fillString(fileInfo.Name(), 64)
if id == 0 || id == numberConnections-1 {
logger.Infof("numChunks: %v", numChunks)
logger.Infof("chunksPerWorker: %v", chunksPerWorker)
logger.Infof("bytesPerConnection: %v", bytesPerConnection)
logger.Infof("fileName: %v", fileInfo.Name())
}
logger.Info("sending")
connection.Write([]byte(fileSize))
connection.Write([]byte(fileName))
sendBuffer := make([]byte, BUFFERSIZE)
chunkI := 0
for {
_, err = file.Read(sendBuffer)
if err == io.EOF {
//End of file reached, break out of for loop
logger.Info("EOF")
break
}
if (chunkI >= chunksPerWorker*id && chunkI < chunksPerWorker*id+chunksPerWorker) || (id == numberConnections-1 && chunkI >= chunksPerWorker*id) {
connection.Write(sendBuffer)
}
chunkI++
}
fmt.Println("File has been sent, closing connection!")
return
}