Connection keepalive/disconnect

This commit is contained in:
Russ Magee 2023-11-05 14:58:24 -08:00
parent 74be6173b6
commit 580053adbd
4 changed files with 135 additions and 29 deletions

View File

@ -1059,13 +1059,16 @@ func main() { //nolint: funlen, gocyclo
fmt.Fprintln(os.Stderr, rejectUserMsg()) fmt.Fprintln(os.Stderr, rejectUserMsg())
rec.SetStatus(GeneralProtocolErr) rec.SetStatus(GeneralProtocolErr)
} else { } else {
// === Set up connection keepalive to server
conn.StartupKeepAlive() // goroutine, returns immediately
defer conn.ShutdownKeepAlive()
// === Set up chaffing to server // === Set up chaffing to server
conn.SetupChaff(chaffFreqMin, chaffFreqMax, chaffBytesMax) // enable client->server chaffing conn.SetupChaff(chaffFreqMin, chaffFreqMax, chaffBytesMax) // enable client->server chaffing
if chaffEnabled { if chaffEnabled {
// #gv:s/label=\"main\$2\"/label=\"deferCloseChaff\"/ // #gv:s/label=\"main\$2\"/label=\"deferCloseChaff\"/
// TODO:.gv:main:2:deferCloseChaff // TODO:.gv:main:2:deferCloseChaff
conn.EnableChaff() // goroutine, returns immediately conn.StartupChaff() // goroutine, returns immediately
defer conn.DisableChaff()
defer conn.ShutdownChaff() defer conn.ShutdownChaff()
} }

View File

@ -17,11 +17,14 @@ import (
"fmt" "fmt"
"io" "io"
"log" "log"
"net/http"
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
"os/user" "os/user"
"path" "path"
"runtime"
"runtime/pprof"
"strings" "strings"
"sync" "sync"
"syscall" "syscall"
@ -44,6 +47,9 @@ var (
// Log - syslog output (with no -d) // Log - syslog output (with no -d)
Log *logger.Writer Log *logger.Writer
cpuprofile string
memprofile string
) )
const ( const (
@ -115,10 +121,13 @@ func runClientToServerCopyAs(who, ttype string, conn *xsnet.Conn, fpath string,
c.Stdout = os.Stdout c.Stdout = os.Stdout
c.Stderr = os.Stderr c.Stderr = os.Stderr
// === Set up connection keepalive to client
conn.StartupKeepAlive() // goroutine, returns immediately
defer conn.ShutdownKeepAlive()
if chaffing { if chaffing {
conn.EnableChaff() conn.StartupChaff()
} }
defer conn.DisableChaff()
defer conn.ShutdownChaff() defer conn.ShutdownChaff()
// Start the command (no pty) // Start the command (no pty)
@ -212,11 +221,14 @@ func runServerToClientCopyAs(who, ttype string, conn *xsnet.Conn, srcPath string
c.Stderr = stdErrBuffer c.Stderr = stdErrBuffer
//c.Stderr = nil //c.Stderr = nil
// === Set up connection keepalive to client
conn.StartupKeepAlive() // goroutine, returns immediately
defer conn.ShutdownKeepAlive()
if chaffing { if chaffing {
conn.EnableChaff() conn.StartupChaff()
} }
//defer conn.Close() //defer conn.Close()
defer conn.DisableChaff()
defer conn.ShutdownChaff() defer conn.ShutdownChaff()
// Start the command (no pty) // Start the command (no pty)
@ -352,12 +364,15 @@ func runShellAs(who, hname, ttype, cmd string, interactive bool, //nolint:funlen
} }
}() }()
// === Set up connection keepalive to client
conn.StartupKeepAlive() // goroutine, returns immediately
defer conn.ShutdownKeepAlive()
if chaffing { if chaffing {
conn.EnableChaff() conn.StartupChaff()
} }
// #gv:s/label=\"runShellAs\$4\"/label=\"deferChaffShutdown\"/ // #gv:s/label=\"runShellAs\$4\"/label=\"deferChaffShutdown\"/
defer func() { defer func() {
conn.DisableChaff()
conn.ShutdownChaff() conn.ShutdownChaff()
}() }()
@ -545,6 +560,9 @@ func main() { //nolint:funlen,gocyclo
H_SHA256 H_SHA256
H_SHA512`) H_SHA512`)
flag.StringVar(&cpuprofile, "cpuprofile", "", "write cpu profile to <`file`>")
flag.StringVar(&memprofile, "memprofile", "", "write memory profile to <`file`>")
flag.Parse() flag.Parse()
if vopt { if vopt {
@ -559,6 +577,24 @@ func main() { //nolint:funlen,gocyclo
} }
} }
// === Profiling instrumentation
if cpuprofile != "" {
f, err := os.Create(cpuprofile)
if err != nil {
log.Fatal("could not create CPU profile: ", err)
}
defer f.Close()
fmt.Println("StartCPUProfile()")
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("could not start CPU profile: ", err) //nolint:gocritic
} else {
defer pprof.StopCPUProfile()
}
go func() { http.ListenAndServe("localhost:6060", nil) }() //nolint:errcheck,gosec
}
// Enforce some sane min/max vals on chaff flags // Enforce some sane min/max vals on chaff flags
if chaffFreqMin < 2 { //nolint:gomnd if chaffFreqMin < 2 { //nolint:gomnd
chaffFreqMin = 2 chaffFreqMin = 2
@ -611,6 +647,9 @@ func main() { //nolint:funlen,gocyclo
syscall.Kill(0, syscall.SIGINT) //nolint:errcheck syscall.Kill(0, syscall.SIGINT) //nolint:errcheck
case "hangup": case "hangup":
logger.LogNotice(fmt.Sprintf("[Got signal: %s - nop]", sig)) //nolint:errcheck logger.LogNotice(fmt.Sprintf("[Got signal: %s - nop]", sig)) //nolint:errcheck
if cpuprofile != "" || memprofile != "" {
dumpProf()
}
default: default:
logger.LogNotice(fmt.Sprintf("[Got signal: %s - ignored]", sig)) //nolint:errcheck logger.LogNotice(fmt.Sprintf("[Got signal: %s - ignored]", sig)) //nolint:errcheck
} }
@ -632,6 +671,7 @@ func main() { //nolint:funlen,gocyclo
// Wait for a connection. // Wait for a connection.
// Then check if client-proposed algs are allowed // Then check if client-proposed algs are allowed
conn, err := l.Accept() conn, err := l.Accept()
//logger.LogDebug(fmt.Sprintf("l.Accept()\n"))
if err != nil { if err != nil {
log.Printf("Accept() got error(%v), hanging up.\n", err) log.Printf("Accept() got error(%v), hanging up.\n", err)
} else { } else {
@ -864,3 +904,23 @@ func main() { //nolint:funlen,gocyclo
} //endfor } //endfor
//logger.LogNotice(fmt.Sprintln("[Exiting]")) //nolint:errcheck //logger.LogNotice(fmt.Sprintln("[Exiting]")) //nolint:errcheck
} }
func dumpProf() {
if cpuprofile != "" {
pprof.StopCPUProfile()
}
if memprofile != "" {
f, err := os.Create(memprofile)
if err != nil {
log.Fatal("could not create memory profile: ", err)
}
defer f.Close()
runtime.GC() // get up-to-date statistics
if err := pprof.WriteHeapProfile(f); err != nil {
log.Fatal("could not write memory profile: ", err) //nolint:gocritic
}
}
//os.Exit(status)
}

View File

@ -52,6 +52,7 @@ const (
CSEKEXAlgDenied // server rejected proposed KEX alg CSEKEXAlgDenied // server rejected proposed KEX alg
CSECipherAlgDenied // server rejected proposed Cipher alg CSECipherAlgDenied // server rejected proposed Cipher alg
CSEHMACAlgDenied // server rejected proposed HMAC alg CSEHMACAlgDenied // server rejected proposed HMAC alg
CSEConnDead // connection keepalives expired
) )
// Extended (>255 UNIX exit status) codes // Extended (>255 UNIX exit status) codes
@ -78,6 +79,7 @@ const (
CSOTunKeepAlive // client tunnel heartbeat CSOTunKeepAlive // client tunnel heartbeat
CSOTunDisconn // server -> client: tunnel rport disconnected CSOTunDisconn // server -> client: tunnel rport disconnected
CSOTunHangup // client -> server: tunnel lport hung up CSOTunHangup // client -> server: tunnel lport hung up
CSOKeepAlive // bidir keepalive packet to monitor main connection
) )
// TunEndpoint.tunCtl control values - used to control workers for client // TunEndpoint.tunCtl control values - used to control workers for client

View File

@ -64,7 +64,6 @@ type (
// see: https://en.wikipedia.org/wiki/chaff_(countermeasure) // see: https://en.wikipedia.org/wiki/chaff_(countermeasure)
ChaffConfig struct { ChaffConfig struct {
shutdown bool //set to inform chaffHelper to shut down shutdown bool //set to inform chaffHelper to shut down
enabled bool
msecsMin uint //msecs min interval msecsMin uint //msecs min interval
msecsMax uint //msecs max interval msecsMax uint //msecs max interval
szMax uint // max size in bytes szMax uint // max size in bytes
@ -87,6 +86,7 @@ type (
Rows uint16 Rows uint16
Cols uint16 Cols uint16
keepalive uint // if this reaches zero, conn is considered dead
chaff ChaffConfig chaff ChaffConfig
tuns *map[uint16](*TunEndpoint) tuns *map[uint16](*TunEndpoint)
@ -971,7 +971,7 @@ func Dial(protocol string, ipport string, extensions ...string) (hc Conn, err er
// Close a hkex.Conn // Close a hkex.Conn
func (hc *Conn) Close() (err error) { func (hc *Conn) Close() (err error) {
hc.DisableChaff() hc.ShutdownChaff()
s := make([]byte, 4) s := make([]byte, 4)
binary.BigEndian.PutUint32(s, uint32(*hc.closeStat)) binary.BigEndian.PutUint32(s, uint32(*hc.closeStat))
log.Printf("** Writing closeStat %d at Close()\n", *hc.closeStat) log.Printf("** Writing closeStat %d at Close()\n", *hc.closeStat)
@ -1337,6 +1337,9 @@ func (hc Conn) Read(b []byte) (n int, err error) {
case CSOChaff: case CSOChaff:
// Throw away pkt if it's chaff (ie., caller to Read() won't see this data) // Throw away pkt if it's chaff (ie., caller to Read() won't see this data)
log.Printf("[Chaff pkt, discarded (len %d)]\n", decryptN) log.Printf("[Chaff pkt, discarded (len %d)]\n", decryptN)
case CSOKeepAlive:
//log.Printf("[KeepAlive pkt, discarded (len %d)]\n", decryptN)
hc.ResetKeepAlive()
case CSOTermSize: case CSOTermSize:
fmt.Sscanf(string(payloadBytes), "%d %d", &hc.Rows, &hc.Cols) fmt.Sscanf(string(payloadBytes), "%d %d", &hc.Rows, &hc.Cols)
log.Printf("[TermSize pkt: rows %v cols %v]\n", hc.Rows, hc.Cols) log.Printf("[TermSize pkt: rows %v cols %v]\n", hc.Rows, hc.Cols)
@ -1568,18 +1571,12 @@ func (hc *Conn) WritePacket(b []byte, ctrlStatOp byte) (n int, err error) {
return retN, err return retN, err
} }
func (hc *Conn) EnableChaff() { func (hc *Conn) StartupChaff() {
hc.chaff.shutdown = false hc.chaff.shutdown = false
hc.chaff.enabled = true
log.Println("Chaffing ENABLED") log.Println("Chaffing ENABLED")
hc.chaffHelper() hc.chaffHelper()
} }
func (hc *Conn) DisableChaff() {
hc.chaff.enabled = false
log.Println("Chaffing DISABLED")
}
func (hc *Conn) ShutdownChaff() { func (hc *Conn) ShutdownChaff() {
hc.chaff.shutdown = true hc.chaff.shutdown = true
log.Println("Chaffing SHUTDOWN") log.Println("Chaffing SHUTDOWN")
@ -1594,9 +1591,10 @@ func (hc *Conn) SetupChaff(msecsMin uint, msecsMax uint, szMax uint) {
// Helper routine to spawn a chaffing goroutine for each Conn // Helper routine to spawn a chaffing goroutine for each Conn
func (hc *Conn) chaffHelper() { func (hc *Conn) chaffHelper() {
go func() { go func() {
for {
var nextDuration int var nextDuration int
if hc.chaff.enabled { for {
//logger.LogDebug(fmt.Sprintf("[chaffHelper Loop]\n"))
if !hc.chaff.shutdown {
var bufTmp []byte var bufTmp []byte
bufTmp = make([]byte, rand.Intn(int(hc.chaff.szMax))) bufTmp = make([]byte, rand.Intn(int(hc.chaff.szMax)))
min := int(hc.chaff.msecsMin) min := int(hc.chaff.msecsMin)
@ -1604,17 +1602,60 @@ func (hc *Conn) chaffHelper() {
_, _ = rand.Read(bufTmp) _, _ = rand.Read(bufTmp)
_, err := hc.WritePacket(bufTmp, CSOChaff) _, err := hc.WritePacket(bufTmp, CSOChaff)
if err != nil { if err != nil {
log.Println("[ *** error - chaffHelper quitting *** ]") log.Println("[ *** error - chaffHelper shutting down *** ]")
hc.chaff.enabled = false hc.chaff.shutdown = true
break break
} }
} else {
log.Println("[ *** chaffHelper shutting down *** ]")
break
} }
time.Sleep(time.Duration(nextDuration) * time.Millisecond) time.Sleep(time.Duration(nextDuration) * time.Millisecond)
if hc.chaff.shutdown { }
log.Println("*** chaffHelper shutting down") }()
break }
}
func (hc *Conn) StartupKeepAlive() {
hc.ResetKeepAlive()
log.Println("KeepAlive ENABLED")
hc.keepaliveHelper()
}
func (hc *Conn) ShutdownKeepAlive() {
log.Println("Conn SHUTDOWN")
hc.SetStatus(CSEConnDead)
hc.Close()
}
func (hc *Conn) ResetKeepAlive() {
hc.keepalive = 3
log.Println("KeepAlive RESET")
}
// Helper routine to spawn a keepalive goroutine for each Conn
func (hc *Conn) keepaliveHelper() {
go func() {
for {
nextDuration := 10000
bufTmp := []byte{0x55, 0xaa}
_, err := hc.WritePacket(bufTmp, CSOKeepAlive)
//logger.LogDebug(fmt.Sprintf("[keepalive]\n"))
if err != nil {
logger.LogDebug(fmt.Sprintf("[ *** error - keepaliveHelper quitting *** ]\n"))
break
}
time.Sleep(time.Duration(nextDuration) * time.Millisecond)
hc.keepalive -= 1
if rand.Intn(32) == 0 {
hc.keepalive = 0
}
if hc.keepalive == 0 {
logger.LogDebug(fmt.Sprintf("*** keepaliveHelper shutting down\n"))
hc.ShutdownKeepAlive()
break
}
} }
}() }()
} }