mirror of
https://gogs.blitter.com/RLabs/xs
synced 2024-08-14 10:26:42 +00:00
The Great Renaming: hkexsh -> xs (Xperimental Shell)
Signed-off-by: Russ Magee <rmagee@gmail.com>
This commit is contained in:
parent
423410bb40
commit
b19687c80b
41 changed files with 1101 additions and 813 deletions
17
xsnet/Makefile
Normal file
17
xsnet/Makefile
Normal 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
152
xsnet/chan.go
Normal 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
113
xsnet/consts.go
Normal 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
129
xsnet/kcp.go
Normal 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
1344
xsnet/net.go
Normal file
File diff suppressed because it is too large
Load diff
418
xsnet/tun.go
Normal file
418
xsnet/tun.go
Normal 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??")
|
||||
}()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue