Add standalone, pure Go port forwarding

This commit is contained in:
Gary Talent 2017-01-27 14:49:58 -06:00
parent 12ec5e4f93
commit 913f70a2af
6 changed files with 81 additions and 137 deletions

54
net.go Normal file
View File

@ -0,0 +1,54 @@
/*
Copyright 2016-2017 gtalent2@gmail.com
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package main
import (
"io"
"log"
"net"
)
const (
CONN_ACTIVE = iota
CONN_DISCONNECTED
)
type ConnStatus struct {
Status int
Err error
}
func portForward(wanConn *net.TCPConn, lanIp, port string, connStatus chan ConnStatus) {
done := make(chan error)
log.Print("Proxy: Connecting to ", lanIp+":"+port)
lanConn, err := net.Dial("tcp", lanIp+":"+port)
if err != nil {
log.Print("Proxy: LAN dial error:", err)
return
}
go forwardConn(wanConn, lanConn, done)
go forwardConn(lanConn, wanConn, done)
for i := 0; i < 2; i++ {
err = <-done
if err != nil {
log.Print("Proxy:", err)
}
}
wanConn.Close()
lanConn.Close()
connStatus <- ConnStatus{Status: CONN_DISCONNECTED, Err: err}
}
func forwardConn(writer, reader net.Conn, done chan error) {
_, err := io.Copy(writer, reader)
done <- err
}

View File

@ -1,51 +0,0 @@
/*
Copyright 2016-2017 gtalent2@gmail.com
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package main
import (
"bytes"
"log"
"os/exec"
"strconv"
)
func addPortForward(ruleName, ip, port string) {
pfrule := "\"rdr pass on $dospin_ext_if proto { tcp, udp } from any to any port {" + port + "} -> " + ip + "\""
in, err := exec.Command("pfctl", "-a", "\"dospin_"+ruleName+"\"", "-f", "-").StdinPipe()
defer in.Close()
if err != nil {
log.Println("Port Forwarding:", err)
}
_, err = in.Write([]byte(pfrule))
if err != nil {
log.Println("Port Forwarding:", err)
}
}
func rmPortForward(ruleName string) {
_, err := exec.Command("pfctl", "-a", "\"dospin_"+ruleName+"\"", "-F", "rules").Output()
if err != nil {
log.Println("Port Forwarding:", err)
}
}
func portUsageCount(ports ...int) int {
cmd := "sockstat"
args := []string{"-4c"}
for _, v := range ports {
args = append(args, "-p")
args = append(args, strconv.Itoa(v))
}
out, err := exec.Command(cmd, args...).Output()
if err != nil {
log.Println("Port Usage Check: Could not run \""+cmd+"\":", err)
}
return bytes.Count(out, []byte{'\n'}) - 1
}

View File

@ -1,32 +0,0 @@
/*
Copyright 2016-2017 gtalent2@gmail.com
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package main
import (
"log"
"os/exec"
)
func addPortForward(ruleName, localIp, remoteIp, port string) {
log.Println("Setting up port", port, "to", remoteIp)
cmdOut, err := exec.Command("iptables", "-t", "nat", "-A", "PREROUTING", "-p", "tcp", "--dport", port, "-j", "DNAT", "--to-destination", remoteIp+":"+port).Output()
if err != nil {
log.Println("iptables error:", err)
}
log.Println(cmdOut)
cmdOut, err = exec.Command("iptables", "-t", "nat", "-A", "POSTROUTING", "-p", "tcp", "-d", localIp, "--dport", port, "-j", "SNAT", "--to-source", remoteIp).Output()
if err != nil {
log.Println("iptables error:", err)
}
log.Println("iptables", "-t", "nat", "-A", "POSTROUTING", "-p", "tcp", "-d", localIp, "--dport", port, "-j", "SNAT", "--to-source", remoteIp)
}
func rmPortForward(ruleName string) {
log.Print("Port forwarding not currently implemented for Linux/iptables")
}

View File

@ -1,23 +0,0 @@
/*
Copyright 2016-2017 gtalent2@gmail.com
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package main
import (
"net"
"strconv"
"testing"
)
func TestPortCount(t *testing.T) {
port := 49214
// listen on some port that nothing should be using for the sake of the test
go func() {
addr, _ := net.ResolveTCPAddr("tcp", "0.0.0.0:"+strconv.Itoa(port))
net.ListenTCP("tcp", addr)
}()
}

View File

@ -11,6 +11,7 @@ import (
"log" "log"
"net" "net"
"strconv" "strconv"
"time"
) )
const ( const (
@ -31,12 +32,14 @@ type ServerHandler interface {
} }
type ServerManager struct { type ServerManager struct {
name string name string
ports []int ports []int
in chan serverManagerEvent in chan serverManagerEvent
done chan interface{} done chan interface{}
usageScore int // spin down server when this reaches 0 connStatus chan ConnStatus
server ServerHandler lastKeepAliveTime time.Time
usageScore int // spin down server when this reaches 0
server ServerHandler
} }
func NewServerManager(name string, server ServerHandler, settings Settings) *ServerManager { func NewServerManager(name string, server ServerHandler, settings Settings) *ServerManager {
@ -58,11 +61,22 @@ func NewServerManager(name string, server ServerHandler, settings Settings) *Ser
func (me *ServerManager) Serve() { func (me *ServerManager) Serve() {
// TODO: see if server is currently up, and setup port forwarding if so // TODO: see if server is currently up, and setup port forwarding if so
fiveMin := time.Duration(5) * time.Minute
ticker := time.NewTicker(fiveMin)
// event loop // event loop
for running := true; running; { for running := true; running; {
select { select {
case status := <-me.connStatus:
if status.Status == CONN_ACTIVE {
me.lastKeepAliveTime = time.Now()
}
case action := <-me.in: case action := <-me.in:
running = me.serveAction(action) running = me.serveAction(action)
case <-ticker.C:
if time.Since(me.lastKeepAliveTime) > fiveMin {
me.Spindown()
}
} }
} }
@ -80,7 +94,7 @@ func (me *ServerManager) Spinup(c *net.TCPConn) {
/* /*
Sends the serve loop a spindown message. Sends the serve loop a spindown message.
*/ */
func (me *ServerManager) Spindown(c *net.TCPConn) { func (me *ServerManager) Spindown() {
me.in <- serverManagerEvent{eventType: SERVERMANAGER_SPINDOWN} me.in <- serverManagerEvent{eventType: SERVERMANAGER_SPINDOWN}
} }
@ -95,18 +109,6 @@ func (me *ServerManager) Done() {
<-me.done <-me.done
} }
func (me *ServerManager) addPortForwards(localIp, remoteIp string) {
log.Println("Ports:", me.ports)
for _, p := range me.ports {
port := strconv.Itoa(p)
addPortForward(me.name, localIp, remoteIp, port)
}
}
func (me *ServerManager) rmPortForwards() {
rmPortForward(me.name)
}
func (me *ServerManager) setupListener(port int) { func (me *ServerManager) setupListener(port int) {
portStr := strconv.Itoa(port) portStr := strconv.Itoa(port)
addr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:"+portStr) addr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:"+portStr)
@ -125,14 +127,9 @@ func (me *ServerManager) setupListener(port int) {
conn, err := l.AcceptTCP() conn, err := l.AcceptTCP()
if err != nil { if err != nil {
log.Print("Could not accept TCP connection:", err) log.Print("Could not accept TCP connection:", err)
} else { } else { // connection accepted
// connection accepted
// spinup machine // spinup machine
me.Spinup(conn) me.Spinup(conn)
// close existing connection, not doing anything with it
conn.Close()
} }
} }
} }
@ -146,16 +143,15 @@ func (me *ServerManager) serveAction(event serverManagerEvent) bool {
targetIp, err := me.server.Spinup(me.name) targetIp, err := me.server.Spinup(me.name)
if err == nil { if err == nil {
log.Println("ServerManager: Got IP for", me.name+":", targetIp) log.Println("ServerManager: Got IP for", me.name+":", targetIp)
localIp := event.tcpConn.LocalAddr().String() wanAddr := event.tcpConn.LocalAddr().String()
me.addPortForwards(localIp, targetIp) _, port, _ := net.SplitHostPort(wanAddr)
go portForward(event.tcpConn, targetIp, port, me.connStatus)
} else { } else {
log.Println("ServerManager: Could not spin up "+me.name+":", err) log.Println("ServerManager: Could not spin up "+me.name+":", err)
} }
case SERVERMANAGER_SPINDOWN: case SERVERMANAGER_SPINDOWN:
err := me.server.Spindown(me.name) err := me.server.Spindown(me.name)
if err == nil { if err != nil {
me.rmPortForwards()
} else {
log.Println("ServerManager: Could not spin down "+me.name+":", err) log.Println("ServerManager: Could not spin down "+me.name+":", err)
} }
case SERVERMANAGER_STOP: case SERVERMANAGER_STOP:

View File

@ -1,5 +1,5 @@
/* /*
Copyright 2016 gtalent2@gmail.com Copyright 2016-2017 gtalent2@gmail.com
This Source Code Form is subject to the terms of the Mozilla Public This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this License, v. 2.0. If a copy of the MPL was not distributed with this