commit a82fe6f074de5f42ad57fdd1b97f7284d7af901b Author: Zack Scholl Date: Tue Oct 17 09:21:47 2017 -0600 First commit, forked from wormhole diff --git a/README.md b/README.md new file mode 100644 index 0000000..3c63582 --- /dev/null +++ b/README.md @@ -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 \ No newline at end of file diff --git a/client.go b/client.go new file mode 100644 index 0000000..5bf3620 --- /dev/null +++ b/client.go @@ -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) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..b26f7d7 --- /dev/null +++ b/main.go @@ -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) +} diff --git a/rendevouz.go b/rendevouz.go new file mode 100644 index 0000000..e9f17cb --- /dev/null +++ b/rendevouz.go @@ -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()) +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..35004a0 --- /dev/null +++ b/server.go @@ -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 +}