The Great Renaming: hkexsh -> xs (Xperimental Shell)

Signed-off-by: Russ Magee <rmagee@gmail.com>
This commit is contained in:
Russ Magee 2019-10-29 20:34:09 -07:00
parent 423410bb40
commit b19687c80b
41 changed files with 1101 additions and 813 deletions

17
xsnet/Makefile Normal file
View file

@ -0,0 +1,17 @@
.PHONY: info clean lib
all: lib
clean:
go clean .
lib: info
go install .
ifneq ($(MSYSTEM),)
info:
@echo "Building for Windows (MSYS)"
else
info:
@echo "Building for Linux"
endif

152
xsnet/chan.go Normal file
View file

@ -0,0 +1,152 @@
package xsnet
// Copyright (c) 2017-2019 Russell Magee
// Licensed under the terms of the MIT license (see LICENSE.mit in this
// distribution)
//
// golang implementation by Russ Magee (rmagee_at_gmail.com)
/* Support functions to set up encryption once an HKEx Conn has been
established with FA exchange and support channel operations
(echo, file-copy, remote-cmd, ...) */
import (
"crypto"
"crypto/aes"
"crypto/cipher"
"encoding/hex"
"errors"
"fmt"
"hash"
"log"
"blitter.com/go/cryptmt"
"blitter.com/go/wanderer"
"golang.org/x/crypto/blowfish"
"golang.org/x/crypto/twofish"
// hash algos must be manually imported thusly:
// (Would be nice if the golang pkg docs were more clear
// on this...)
_ "crypto/sha256"
_ "crypto/sha512"
)
// Expand keymat, if necessary, to a minimum of 2x(blocksize).
// Keymat is used for initial key and the IV, hence the 2x.
// This is occasionally necessary for smaller modes of KEX algorithms
// (eg., KEX_HERRADURA256); perhaps an indication these should be
// avoided in favour of larger modes.
//
// This is used for block ciphers; stream ciphers should do their
// own key expansion.
func expandKeyMat(keymat []byte, blocksize int) []byte {
if len(keymat) < 2*blocksize {
halg := crypto.SHA256
mc := halg.New()
if !halg.Available() {
log.Fatal("hash not available!")
}
_, _ = mc.Write(keymat)
var xpand []byte
xpand = mc.Sum(xpand)
keymat = append(keymat, xpand...)
log.Println("[NOTE: keymat short - applying key expansion using SHA256]")
}
return keymat
}
/* Support functionality to set up encryption after a channel has
been negotiated via xsnet.go
*/
func (hc Conn) getStream(keymat []byte) (rc cipher.Stream, mc hash.Hash, err error) {
var key []byte
var block cipher.Block
var iv []byte
var ivlen int
copts := hc.cipheropts & 0xFF
// TODO: each cipher alg case should ensure len(keymat.Bytes())
// is >= 2*cipher.BlockSize (enough for both key and iv)
switch copts {
case CAlgAES256:
keymat = expandKeyMat(keymat, aes.BlockSize)
key = keymat[0:aes.BlockSize]
block, err = aes.NewCipher(key)
ivlen = aes.BlockSize
iv = keymat[aes.BlockSize : aes.BlockSize+ivlen]
rc = cipher.NewOFB(block, iv)
log.Printf("[cipher AES_256 (%d)]\n", copts)
case CAlgTwofish128:
keymat = expandKeyMat(keymat, twofish.BlockSize)
key = keymat[0:twofish.BlockSize]
block, err = twofish.NewCipher(key)
ivlen = twofish.BlockSize
iv = keymat[twofish.BlockSize : twofish.BlockSize+ivlen]
rc = cipher.NewOFB(block, iv)
log.Printf("[cipher TWOFISH_128 (%d)]\n", copts)
case CAlgBlowfish64:
keymat = expandKeyMat(keymat, blowfish.BlockSize)
key = keymat[0:blowfish.BlockSize]
block, err = blowfish.NewCipher(key)
ivlen = blowfish.BlockSize
// N.b. Bounds enforcement of differing cipher algorithms
// ------------------------------------------------------
// cipher/aes and x/cipher/twofish appear to allow one to
// pass an iv larger than the blockSize harmlessly to
// cipher.NewOFB(); x/cipher/blowfish implementation will
// segfault here if len(iv) is not exactly blowfish.BlockSize.
//
// I assume the other two check bounds and only
// copy what's needed whereas blowfish does no such check.
iv = keymat[blowfish.BlockSize : blowfish.BlockSize+ivlen]
rc = cipher.NewOFB(block, iv)
log.Printf("[cipher BLOWFISH_64 (%d)]\n", copts)
case CAlgCryptMT1:
rc = cryptmt.NewCipher(keymat)
log.Printf("[cipher CRYPTMT1 (%d)]\n", copts)
case CAlgWanderer:
rc = wanderer.NewCodec(nil, nil, keymat, 3, 3)
log.Printf("[cipher WANDERER (%d)]\n", copts)
default:
log.Printf("[invalid cipher (%d)]\n", copts)
fmt.Printf("DOOFUS SET A VALID CIPHER ALG (%d)\n", copts)
err = errors.New("hkexchan: INVALID CIPHER ALG")
//os.Exit(1)
}
hopts := (hc.cipheropts >> 8) & 0xFF
switch hopts {
case HmacSHA256:
log.Printf("[hash HmacSHA256 (%d)]\n", hopts)
halg := crypto.SHA256
mc = halg.New()
if !halg.Available() {
log.Fatal("hash not available!")
}
case HmacSHA512:
log.Printf("[hash HmacSHA512 (%d)]\n", hopts)
halg := crypto.SHA512
mc = halg.New()
if !halg.Available() {
log.Fatal("hash not available!")
}
default:
log.Printf("[invalid hmac (%d)]\n", hopts)
fmt.Printf("DOOFUS SET A VALID HMAC ALG (%d)\n", hopts)
err = errors.New("hkexchan: INVALID HMAC ALG")
return
//os.Exit(1)
}
if err != nil {
// Feed the IV into the hmac: all traffic in the connection must
// feed its data into the hmac afterwards, so both ends can xor
// that with the stream to detect corruption.
_, _ = mc.Write(iv)
var currentHash []byte
currentHash = mc.Sum(currentHash)
log.Printf("Channel init hmac(iv):%s\n", hex.EncodeToString(currentHash))
}
return
}

113
xsnet/consts.go Normal file
View file

@ -0,0 +1,113 @@
// consts.go - consts for xsnet
// Copyright (c) 2017-2019 Russell Magee
// Licensed under the terms of the MIT license (see LICENSE.mit in this
// distribution)
//
// golang implementation by Russ Magee (rmagee_at_gmail.com)
package xsnet
// KEX algorithm values
//
// Specified (in string form) as the extensions parameter
// to xsnet.Dial()
// Alg is sent in a uint8 so there are up to 256 possible
const (
KEX_HERRADURA256 = iota // this MUST be first for default if omitted in ctor
KEX_HERRADURA512
KEX_HERRADURA1024
KEX_HERRADURA2048
KEX_resvd4
KEX_resvd5
KEX_resvd6
KEX_resvd7
KEX_KYBER512
KEX_KYBER768
KEX_KYBER1024
KEX_resvd11
KEX_NEWHOPE
KEX_NEWHOPE_SIMPLE // 'NewHopeLP-Simple' - https://eprint.iacr.org/2016/1157
KEX_resvd14
KEX_resvd15
)
// Sent from client to server in order to specify which
// algo shall be used (see xsnet.KEX_HERRADURA256, ...)
type KEXAlg uint8
// Extended exit status codes - indicate comm/pty issues
// rather than remote end normal UNIX exit codes
const (
CSENone = 1024 + iota
CSETruncCSO // No CSOExitStatus in payload
CSEStillOpen // Channel closed unexpectedly
CSEExecFail // cmd.Start() (exec) failed
CSEPtyExecFail // pty.Start() (exec w/pty) failed
CSEPtyGetNameFail // failed to obtain pty name
)
// Extended (>255 UNIX exit status) codes
// This indicate channel-related or internal errors
type CSExtendedCode uint32
// Channel Status/Op bytes - packet types
const (
// Main connection/session control
CSONone = iota // No error, normal packet
CSOHmacInvalid // HMAC mismatch detected on remote end
CSOTermSize // set term size (rows:cols)
CSOExitStatus // Remote cmd exit status
CSOChaff // Dummy packet, do not pass beyond decryption
// Tunnel setup/control/status
CSOTunSetup // client -> server tunnel setup request (dstport)
CSOTunSetupAck // server -> client tunnel setup ack
CSOTunRefused // server -> client: tunnel rport connection refused
CSOTunData // packet contains tunnel data [rport:data]
CSOTunKeepAlive // client tunnel heartbeat
CSOTunDisconn // server -> client: tunnel rport disconnected
CSOTunHangup // client -> server: tunnel lport hung up
)
// TunEndpoint.tunCtl control values - used to control workers for client
// or server tunnels depending on the code
const (
TunCtl_Client_Listen = 'a'
// [CSOTunAccept]
// status: server has ack'd tun setup request
// action: client should accept (after re-listening, if required) on lport
TunCtl_Server_Dial = 'd' // server has dialled OK, client side can accept() conns
// [CSOTunAccept]
// status: client wants to open tunnel to rport
// action:server side should dial() rport on client's behalf
)
// Channel status Op byte type (see CSONone, ... and CSENone, ...)
type CSOType uint32
//TODO: this should be small (max unfragmented packet size?)
const MAX_PAYLOAD_LEN = 4*1024*1024*1024 - 1
// Session symmetric crypto algs
const (
CAlgAES256 = iota
CAlgTwofish128 // golang.org/x/crypto/twofish
CAlgBlowfish64 // golang.org/x/crypto/blowfish
CAlgCryptMT1 //cryptmt using mtwist64
CAlgWanderer // inhouse experimental crypto alg
CAlgNoneDisallowed
)
// Available ciphers for hkex.Conn
type CSCipherAlg uint32
// Session packet auth HMAC algs
const (
HmacSHA256 = iota
HmacSHA512
HmacNoneDisallowed
)
// Available HMACs for hkex.Conn
type CSHmacAlg uint32

129
xsnet/kcp.go Normal file
View file

@ -0,0 +1,129 @@
package xsnet
import (
"crypto/sha1"
"errors"
"fmt"
"net"
"blitter.com/go/xs/logger"
kcp "github.com/xtaci/kcp-go"
"golang.org/x/crypto/pbkdf2"
)
const (
KCP_NONE = iota
KCP_AES
KCP_BLOWFISH
KCP_CAST5
KCP_SM4
KCP_SALSA20
KCP_SIMPLEXOR
KCP_TEA
KCP_3DES
KCP_TWOFISH
KCP_XTEA
)
// for github.com/xtaci/kcp-go BlockCrypt alg selection
type KCPAlg uint8
var (
kcpKeyBytes []byte = []byte("SET THIS") // symmetric crypto key for KCP (github.com/xtaci/kcp-go) if used
kcpSaltBytes []byte = []byte("ALSO SET THIS")
)
func getKCPalgnum(extensions []string) (k KCPAlg) {
k = KCP_AES // default
var s string
for _, s = range extensions {
switch s {
case "KCP_NONE":
k = KCP_NONE
break //out of for
case "KCP_AES":
k = KCP_AES
break //out of for
case "KCP_BLOWFISH":
k = KCP_BLOWFISH
break //out of for
case "KCP_CAST5":
k = KCP_CAST5
break //out of for
case "KCP_SM4":
k = KCP_SM4
break //out of for
case "KCP_SALSA20":
k = KCP_SALSA20
break //out of for
case "KCP_SIMPLEXOR":
k = KCP_SIMPLEXOR
break //out of for
case "KCP_TEA":
k = KCP_TEA
break //out of for
case "KCP_3DES":
k = KCP_3DES
break //out of for
case "KCP_TWOFISH":
k = KCP_TWOFISH
break //out of for
case "KCP_XTEA":
k = KCP_XTEA
break //out of for
}
}
logger.LogDebug(fmt.Sprintf("[KCP BlockCrypt '%s' activated]", s))
return
}
func SetKCPKeyAndSalt(key []byte, salt []byte) {
kcpKeyBytes = key
kcpSaltBytes = salt
}
func _newKCPBlockCrypt(key []byte, extensions []string) (b kcp.BlockCrypt, e error) {
switch getKCPalgnum(extensions) {
case KCP_NONE:
return kcp.NewNoneBlockCrypt(key)
case KCP_AES:
return kcp.NewAESBlockCrypt(key)
case KCP_BLOWFISH:
return kcp.NewBlowfishBlockCrypt(key)
case KCP_CAST5:
return kcp.NewCast5BlockCrypt(key)
case KCP_SM4:
return kcp.NewSM4BlockCrypt(key)
case KCP_SALSA20:
return kcp.NewSalsa20BlockCrypt(key)
case KCP_SIMPLEXOR:
return kcp.NewSimpleXORBlockCrypt(key)
case KCP_TEA:
return kcp.NewTEABlockCrypt(key)
case KCP_3DES:
return kcp.NewTripleDESBlockCrypt(key)
case KCP_TWOFISH:
return kcp.NewTwofishBlockCrypt(key)
case KCP_XTEA:
return kcp.NewXTEABlockCrypt(key)
}
return nil, errors.New("Invalid KCP BlockCrypto specified")
}
func kcpDial(ipport string, extensions []string) (c net.Conn, err error) {
kcpKey := pbkdf2.Key(kcpKeyBytes, kcpSaltBytes, 1024, 32, sha1.New)
block, be := _newKCPBlockCrypt([]byte(kcpKey), extensions)
_ = be
return kcp.DialWithOptions(ipport, block, 10, 3)
}
func kcpListen(ipport string, extensions []string) (l net.Listener, err error) {
kcpKey := pbkdf2.Key(kcpKeyBytes, kcpSaltBytes, 1024, 32, sha1.New)
block, be := _newKCPBlockCrypt([]byte(kcpKey), extensions)
_ = be
return kcp.ListenWithOptions(ipport, block, 10, 3)
}
func (hl *HKExListener) AcceptKCP() (c net.Conn, e error) {
return hl.l.(*kcp.Listener).AcceptKCP()
}

1344
xsnet/net.go Normal file

File diff suppressed because it is too large Load diff

418
xsnet/tun.go Normal file
View file

@ -0,0 +1,418 @@
// hkextun.go - Tunnel setup using an established xsnet.Conn
// Copyright (c) 2017-2019 Russell Magee
// Licensed under the terms of the MIT license (see LICENSE.mit in this
// distribution)
//
// golang implementation by Russ Magee (rmagee_at_gmail.com)
package xsnet
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"net"
"strings"
"sync"
"time"
"blitter.com/go/xs/logger"
)
type (
// Tunnels
// --
// 1. client is given (lport, remhost, rport) by local user
// 2. client sends [CSOTunReq:rport] to server
// client=> [CSOTunReq:rport] =>remhost
//
// remhost starts worker to receive/send data using rport
// remhost replies to client with rport to acknowledge tun is ready
// client<= [CSOTunAck:rport] <=remhost
// ... or if rhost rport refuses connection, sends
// [CSOTunRefused:rport]
//
// client starts worker to receive/send data using lport
// ... client disconnects: sends remhost [CSOTunClose:rport]
// ... or server disconnects: sends client [CSOTunClose:lport]
// server at any time sends [CSOTunRefused:rport] if daemon died
// --
// TunEndpoint [securePort:peer:dataPort]
TunEndpoint struct {
Rport uint16 // Names are from client's perspective
Lport uint16 // ... ie., RPort is on server, LPort is on client
Peer string //net.Addr
Died bool // set by client upon receipt of a CSOTunDisconn
KeepAlive uint32 // must be reset by client to keep server dial() alive
Ctl chan rune //See TunCtl_* consts
Data chan []byte
}
)
func (hc *Conn) CollapseAllTunnels(client bool) {
for k, t := range *hc.tuns {
var tunDst bytes.Buffer
binary.Write(&tunDst, binary.BigEndian, t.Lport)
binary.Write(&tunDst, binary.BigEndian, t.Rport)
if client {
hc.WritePacket(tunDst.Bytes(), CSOTunHangup)
} else {
hc.WritePacket(tunDst.Bytes(), CSOTunDisconn)
}
delete(*hc.tuns, k)
}
}
func (hc *Conn) InitTunEndpoint(lp uint16, p string /* net.Addr */, rp uint16) {
hc.Lock()
defer hc.Unlock()
if (*hc.tuns) == nil {
(*hc.tuns) = make(map[uint16]*TunEndpoint)
}
if (*hc.tuns)[rp] == nil {
var addrs []net.Addr
if p == "" {
addrs, _ = net.InterfaceAddrs()
p = addrs[0].String()
}
(*hc.tuns)[rp] = &TunEndpoint{ /*Status: CSOTunSetup,*/ Peer: p,
Lport: lp, Rport: rp, Data: make(chan []byte, 1),
Ctl: make(chan rune, 1)}
logger.LogDebug(fmt.Sprintf("InitTunEndpoint [%d:%s:%d]", lp, p, rp))
} else {
logger.LogDebug(fmt.Sprintf("InitTunEndpoint [reusing] %v", (*hc.tuns)[rp]))
if (*hc.tuns)[rp].Data == nil {
// When re-using a tunnel it will have its
// data channel removed on closure. Re-create it
(*hc.tuns)[rp].Data = make(chan []byte, 1)
}
(*hc.tuns)[rp].KeepAlive = 0
(*hc.tuns)[rp].Died = false
}
return
}
func (hc *Conn) StartClientTunnel(lport, rport uint16) {
hc.InitTunEndpoint(lport, "", rport)
go func() {
var wg sync.WaitGroup
for cmd := range (*hc.tuns)[rport].Ctl {
if cmd == 'a' {
l, e := net.Listen("tcp4", fmt.Sprintf(":%d", lport))
if e != nil {
logger.LogDebug(fmt.Sprintf("[ClientTun] Could not get lport %d! (%s)", lport, e))
} else {
logger.LogDebug(fmt.Sprintf("[ClientTun] Listening for client tunnel port %d", lport))
for {
c, e := l.Accept() // blocks until new conn
// If tunnel is being re-used, re-init it
if (*hc.tuns)[rport] == nil {
hc.InitTunEndpoint(lport, "", rport)
}
// ask server to dial() its side, rport
var tunDst bytes.Buffer
binary.Write(&tunDst, binary.BigEndian, lport)
binary.Write(&tunDst, binary.BigEndian, rport)
hc.WritePacket(tunDst.Bytes(), CSOTunSetup)
if e != nil {
logger.LogDebug(fmt.Sprintf("[ClientTun] Accept() got error(%v), hanging up.", e))
} else {
logger.LogDebug(fmt.Sprintf("[ClientTun] Accepted tunnel client %v", (*hc.tuns)[rport]))
// outside client -> tunnel lport
wg.Add(1)
go func() {
defer func() {
if c.Close() != nil {
logger.LogDebug("[ClientTun] worker A: conn c already closed")
} else {
logger.LogDebug("[ClientTun] worker A: closed conn c")
}
wg.Done()
}()
logger.LogDebug("[ClientTun] worker A: starting")
var tunDst bytes.Buffer
binary.Write(&tunDst, binary.BigEndian, lport)
binary.Write(&tunDst, binary.BigEndian, rport)
for {
rBuf := make([]byte, 1024)
//Read data from c, encrypt/write via hc to client(lport)
c.SetReadDeadline(time.Now().Add(200 * time.Millisecond))
n, e := c.Read(rBuf)
if e != nil {
if e == io.EOF {
logger.LogDebug(fmt.Sprintf("[ClientTun] worker A: lport Disconnected: shutting down tunnel %v", (*hc.tuns)[rport]))
// if Died was already set, server-side already is gone.
if hc.TunIsAlive(rport) {
hc.WritePacket(tunDst.Bytes(), CSOTunHangup)
}
hc.ShutdownTun(rport) // FIXME: race-C
break
} else if strings.Contains(e.Error(), "i/o timeout") {
if !hc.TunIsAlive(rport) {
logger.LogDebug(fmt.Sprintf("[ClientTun] worker A: timeout: Server side died, hanging up %v", (*hc.tuns)[rport]))
hc.ShutdownTun(rport)
break
}
} else {
logger.LogDebug(fmt.Sprintf("[ClientTun] worker A: Read error from lport of tun %v\n%s", (*hc.tuns)[rport], e))
if hc.TunIsAlive(rport) {
hc.WritePacket(tunDst.Bytes(), CSOTunHangup)
}
hc.ShutdownTun(rport)
break
}
}
if n > 0 {
rBuf = append(tunDst.Bytes(), rBuf[:n]...)
_, de := hc.WritePacket(rBuf[:n+4], CSOTunData)
if de != nil {
logger.LogDebug(fmt.Sprintf("[ClientTun] worker A: Error writing to tunnel %v, %s]\n", (*hc.tuns)[rport], de))
break
}
}
}
logger.LogDebug("[ClientTun] worker A: exiting")
}()
// tunnel lport -> outside client (c)
wg.Add(1)
go func() {
defer func() {
if c.Close() != nil {
logger.LogDebug("[ClientTun] worker B: conn c already closed")
} else {
logger.LogDebug("[ClientTun] worker B: closed conn c")
}
wg.Done()
}()
logger.LogDebug("[ClientTun] worker B: starting")
for {
bytes, ok := <-(*hc.tuns)[rport].Data // FIXME: race-C w/ShutdownTun calls
if ok {
c.SetWriteDeadline(time.Now().Add(200 * time.Millisecond))
_, e := c.Write(bytes)
if e != nil {
logger.LogDebug(fmt.Sprintf("[ClientTun] worker B: lport conn closed"))
break
}
} else {
logger.LogDebug(fmt.Sprintf("[ClientTun] worker B: Channel was closed?"))
break
}
}
logger.LogDebug("[ClientTun] worker B: exiting")
}()
} // end Accept() worker block
wg.Wait()
// When both workers have exited due to a disconnect or other
// condition, it's safe to remove the tunnel descriptor.
logger.LogDebug("[ClientTun] workers exited")
hc.ShutdownTun(rport)
} // end for-accept
} // end Listen() block
}
} // end t.Ctl for
}()
}
func (hc *Conn) AgeTunnel(endp uint16) uint32 {
hc.Lock()
defer hc.Unlock()
(*hc.tuns)[endp].KeepAlive += 1
return (*hc.tuns)[endp].KeepAlive
}
func (hc *Conn) ResetTunnelAge(endp uint16) {
hc.Lock()
defer hc.Unlock()
(*hc.tuns)[endp].KeepAlive = 0
}
func (hc *Conn) TunIsNil(endp uint16) bool {
hc.Lock()
defer hc.Unlock()
return (*hc.tuns)[endp] == nil
}
func (hc *Conn) TunIsAlive(endp uint16) bool {
hc.Lock()
defer hc.Unlock()
return !(*hc.tuns)[endp].Died
}
func (hc *Conn) MarkTunDead(endp uint16) {
hc.Lock()
defer hc.Unlock()
(*hc.tuns)[endp].Died = true
}
func (hc *Conn) ShutdownTun(endp uint16) {
hc.Lock()
defer hc.Unlock()
if (*hc.tuns)[endp] != nil {
(*hc.tuns)[endp].Died = true
if (*hc.tuns)[endp].Data != nil {
close((*hc.tuns)[endp].Data)
(*hc.tuns)[endp].Data = nil
}
}
delete((*hc.tuns), endp)
}
func (hc *Conn) StartServerTunnel(lport, rport uint16) {
hc.InitTunEndpoint(lport, "", rport)
var err error
go func() {
var wg sync.WaitGroup
//
// worker to age server tunnel and kill it if keepalives
// stop from client
//
wg.Add(1)
go func() {
defer wg.Done()
for {
time.Sleep(100 * time.Millisecond)
if hc.TunIsNil(rport) {
logger.LogDebug("[ServerTun] worker A: Client endpoint removed.")
break
}
age := hc.AgeTunnel(rport)
if age > 25 {
hc.MarkTunDead(rport)
logger.LogDebug("[ServerTun] worker A: Client died, hanging up.")
break
}
}
}()
for cmd := range (*hc.tuns)[rport].Ctl {
var c net.Conn
logger.LogDebug(fmt.Sprintf("[ServerTun] got Ctl '%c'.", cmd))
if cmd == 'd' {
// if re-using tunnel, re-init it
if hc.TunIsNil(rport) {
hc.InitTunEndpoint(lport, "", rport)
}
logger.LogDebug("[ServerTun] dialling...")
c, err = net.Dial("tcp4", fmt.Sprintf(":%d", rport))
if err != nil {
logger.LogDebug(fmt.Sprintf("[ServerTun] Dial() error for tun %v: %s", (*hc.tuns)[rport], err))
var resp bytes.Buffer
binary.Write(&resp, binary.BigEndian /*lport*/, uint16(0))
binary.Write(&resp, binary.BigEndian, rport)
hc.WritePacket(resp.Bytes(), CSOTunRefused)
} else {
logger.LogDebug(fmt.Sprintf("[ServerTun] Tunnel Opened - %v", (*hc.tuns)[rport]))
var resp bytes.Buffer
binary.Write(&resp, binary.BigEndian, lport)
binary.Write(&resp, binary.BigEndian, rport)
logger.LogDebug(fmt.Sprintf("[ServerTun] Writing CSOTunSetupAck %v", (*hc.tuns)[rport]))
hc.WritePacket(resp.Bytes(), CSOTunSetupAck)
//
// worker to read data from the rport (to encrypt & send to client)
//
wg.Add(1)
go func() {
defer func() {
logger.LogDebug("[ServerTun] worker A: deferred hangup")
if c.Close() != nil {
logger.LogDebug("[ServerTun] workerA: conn c already closed")
}
wg.Done()
}()
logger.LogDebug("[ServerTun] worker A: starting")
var tunDst bytes.Buffer
binary.Write(&tunDst, binary.BigEndian, (*hc.tuns)[rport].Lport)
binary.Write(&tunDst, binary.BigEndian, (*hc.tuns)[rport].Rport)
for {
rBuf := make([]byte, 1024)
// Read data from c, encrypt/write via hc to client(lport)
c.SetReadDeadline(time.Now().Add(200 * time.Millisecond))
n, e := c.Read(rBuf)
if e != nil {
if e == io.EOF {
logger.LogDebug(fmt.Sprintf("[ServerTun] worker A: rport Disconnected: shutting down tunnel %v", (*hc.tuns)[rport]))
if hc.TunIsAlive(rport) {
hc.WritePacket(tunDst.Bytes(), CSOTunDisconn)
}
hc.ShutdownTun(rport) // FIXME: race-A
break
} else if strings.Contains(e.Error(), "i/o timeout") {
if !hc.TunIsAlive(rport) {
logger.LogDebug(fmt.Sprintf("[ServerTun] worker A: timeout: Server side died, hanging up %v", (*hc.tuns)[rport]))
hc.ShutdownTun(rport) // FIXME: race-B
break
}
} else {
logger.LogDebug(fmt.Sprintf("[ServerTun] worker A: Read error from rport of tun %v: %s", (*hc.tuns)[rport], e))
if hc.TunIsAlive(rport) {
hc.WritePacket(tunDst.Bytes(), CSOTunDisconn)
}
hc.ShutdownTun(rport) // FIXME: race-C
break
}
}
if n > 0 {
rBuf = append(tunDst.Bytes(), rBuf[:n]...)
hc.WritePacket(rBuf[:n+4], CSOTunData)
}
}
logger.LogDebug("[ServerTun] worker A: exiting")
}()
// worker to read data from client (already decrypted) & fwd to rport
wg.Add(1)
go func() {
defer func() {
logger.LogDebug("[ServerTun] worker B: deferred hangup")
if c.Close() != nil {
logger.LogDebug("[ServerTun] worker B: conn c already closed")
}
wg.Done()
}()
logger.LogDebug("[ServerTun] worker B: starting")
for {
rData, ok := <-(*hc.tuns)[rport].Data // FIXME: race-A, race-B, race-C (w/ShutdownTun() calls)
if ok {
c.SetWriteDeadline(time.Now().Add(200 * time.Millisecond))
_, e := c.Write(rData)
if e != nil {
logger.LogDebug(fmt.Sprintf("[ServerTun] worker B: ERROR writing to rport conn"))
break
}
} else {
logger.LogDebug(fmt.Sprintf("[ServerTun] worker B: Channel was closed?"))
break
}
}
logger.LogDebug("[ServerTun] worker B: exiting")
}()
wg.Wait()
} // end if Dialled successfully
delete((*hc.tuns), rport)
}
} // t.Ctl read loop
logger.LogDebug("[ServerTun] Tunnel exiting t.Ctl read loop - channel closed??")
}()
}