Compare commits

...

5 Commits

Author SHA1 Message Date
wenyifan
d276407530 update 2022-09-06 14:28:39 +08:00
wenyifan
0a7d7dd290 update 2022-09-06 14:13:48 +08:00
wenyifan
f426b7b2cc update 2022-09-05 16:08:24 +08:00
wenyifan
c4c6f1c4ff 无多路复用版本 2022-09-05 14:21:33 +08:00
wenyifan
1c49851d13 无多路复用版本 2022-09-04 18:08:16 +08:00
7 changed files with 204 additions and 124 deletions

View File

@ -20,12 +20,13 @@
```
## 功能特性
- TLS连接多路复用,减少TLS握手次数
- ~~TLS连接多路复用,减少TLS握手次数~~(存在特征已经移除)
## 安全性特别说明
- 包装的TCP流量没有加密,如有需求请加密后再转发
- 多路复用特性使用了smux的框架,有协议特征,有更进一步需求需修改源码二次加密
- TLS后续流量没有进行Application Data封装,深层次的协议分析可以发现此特征
- ~~多路复用特性使用了smux的框架,有协议特征,有更进一步需求需修改源码二次加密~~(已经移除多路复用,如有需求请在外层处理)
- ~~TLS后续流量没有进行Application Data封装,深层次的协议分析可以发现此特征~~(已经封装,已与标准的TLS协议一致)
## 特别说明
- 感谢v2ex网友ihciah的思路灵感.

1
go.mod
View File

@ -4,7 +4,6 @@ go 1.18
require (
github.com/spf13/cobra v1.5.0
github.com/xtaci/smux v1.5.16
)
require (

View File

@ -1,9 +1,11 @@
package shadow
import (
"crypto/tls"
"fmt"
"io"
"net"
"time"
)
type Client struct {
@ -22,8 +24,6 @@ func NewClient(listenAddress string, serverAddress string, fakeAddressSNI string
}
func (c *Client) Start() {
bridge := NewTLSBridge(c.ServerAddress, c.FakeAddressSNI)
listen, err := net.Listen("tcp", c.ListenAddress)
if err != nil {
fmt.Printf("[Client] Start client error: %v\n", err)
@ -37,15 +37,61 @@ func (c *Client) Start() {
fmt.Printf("[Client] accept error: %v\n", err)
continue
}
stream := bridge.GetStream()
if stream == nil {
conn.Close()
fmt.Println("[Client] connect to server error")
continue
}
fmt.Printf("[Client] New TCP connection: %v <-> %v \n", conn.LocalAddr().String(), conn.RemoteAddr().String())
go io.Copy(conn, stream)
go io.Copy(stream, conn)
go handlerClient(conn, c.ServerAddress, c.FakeAddressSNI)
}
}
func handlerClient(conn net.Conn, serverAddress string, fakeAddressSNI string) {
dial, err := tls.DialWithDialer(&net.Dialer{
Timeout: time.Second * 5,
}, "tcp", serverAddress, &tls.Config{
ServerName: fakeAddressSNI,
})
if err != nil {
fmt.Printf("[Client] Dial server error: %v\n", err)
return
}
err = dial.Handshake()
if err != nil {
fmt.Printf("[Client] Handshake error: %v\n", err)
return
}
dial.NetConn().SetDeadline(time.Now())
dial.NetConn().SetDeadline(time.Time{})
p := &PackAppData{Conn: dial.NetConn()}
defer p.Close()
defer conn.Close()
exitCh := make(chan int, 1)
go MyCopy(conn, p, exitCh)
go MyCopy(p, conn, exitCh)
<-exitCh
}
func MyCopy(src io.ReadWriteCloser, dst io.ReadWriteCloser, ch chan int) {
buf := make([]byte, 32*1024)
for {
nr, er := src.Read(buf)
if er != nil {
if er == io.EOF {
break
} else {
fmt.Printf("Read err: %v\n", er)
break
}
} else {
nw, ew := dst.Write(buf[0:nr])
if ew != nil {
fmt.Printf("Write error:%v\n", ew)
break
}
if nr != nw {
fmt.Printf("Write less then buffered \n")
break
}
}
}
ch <- 1
}

View File

@ -23,15 +23,14 @@ func TestName(t *testing.T) {
}
func TestName2(t *testing.T) {
dial, err := tls.DialWithDialer(&net.Dialer{
Timeout: time.Second * 5,
}, "tcp", "evan.run:443", &tls.Config{
ServerName: "evan.run",
})
err = dial.Handshake()
b := []byte("ABC")
encrypt, err := AesEncrypt(b, []byte("1234567812345678"))
if err != nil {
fmt.Println(err)
}
time.Sleep(time.Minute)
decrypt, err := AesDecrypt(encrypt, []byte("1234567812345678"))
if err != nil {
fmt.Println(err)
}
fmt.Println(string(decrypt))
}

70
shadow/packer.go Normal file
View File

@ -0,0 +1,70 @@
package shadow
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
)
var (
AppDataHeader = []byte{0x17, 0x3, 0x3}
HeaderLength = len(AppDataHeader)
)
type PackAppData struct {
Conn net.Conn
}
func (m PackAppData) Read(p []byte) (n int, err error) {
buf := make([]byte, 32*1024+HeaderLength+2)
headRead, err := io.ReadAtLeast(m.Conn, buf[0:HeaderLength+2], HeaderLength+2)
if err != nil {
fmt.Printf("Read header error: %v\n", err)
return 0, err
}
if headRead < HeaderLength+2 {
return 0, errors.New("Read header failed")
}
if bytes.Equal(buf[0:HeaderLength], AppDataHeader) {
payLoadLength := int(binary.BigEndian.Uint16(buf[HeaderLength : HeaderLength+2]))
sum := 0
for sum < payLoadLength {
r, e := m.Conn.Read(buf[HeaderLength+2+sum : HeaderLength+2+payLoadLength])
if e != nil {
if e == io.EOF {
break
} else {
return 0, e
}
}
copy(p[sum:], buf[HeaderLength+2+sum:HeaderLength+2+sum+r])
sum += r
}
return sum, err
} else {
fmt.Printf("Invalid header")
return 0, errors.New("invalid header")
}
}
func (m PackAppData) Write(p []byte) (n int, err error) {
lenNum := make([]byte, 2)
binary.BigEndian.PutUint16(lenNum, uint16(len(p)))
packetBuf := bytes.NewBuffer(AppDataHeader)
packetBuf.Write(lenNum)
packetBuf.Write(p)
write, err := m.Conn.Write(packetBuf.Bytes())
write = write - HeaderLength - 2
return write, err
}
func (m PackAppData) Close() error {
return m.Conn.Close()
}

View File

@ -2,7 +2,6 @@ package shadow
import (
"fmt"
"github.com/xtaci/smux"
"io"
"net"
"time"
@ -61,21 +60,76 @@ func handler(conn net.Conn, targetAddress string, fakeAddress string) {
conn.SetDeadline(time.Now())
conn.SetDeadline(time.Time{})
//Process real tcp connection
session, err := smux.Server(conn, nil)
realConnection, err := net.Dial("tcp", targetAddress)
if err != nil {
fmt.Printf("[Server] smux error: %v\n", err)
fmt.Printf("[Server] Dial target error : %v\n", err)
return
}
for {
stream, err := session.AcceptStream()
if err != nil {
fmt.Printf("[Server] AcceptStream error: %v\n", err)
break
}
go handlerMux(stream, targetAddress)
if err != nil {
return
}
p := &PackAppData{Conn: conn}
defer p.Close()
defer realConnection.Close()
exit := make(chan int, 1)
go MyCopy(p, realConnection, exit)
go MyCopy(realConnection, p, exit)
<-exit
//go func() {
// buf := make([]byte, 64*1024)
// for {
// nr, er := realConnection.Read(buf)
// if er != nil {
// if er == io.EOF {
// continue
// } else {
// fmt.Println("read err:", er)
// break
// }
// } else {
// lenNum := make([]byte, 2)
// binary.BigEndian.PutUint16(lenNum, uint16(nr))
//
// packetBuf := bytes.NewBuffer(AppDataHeader)
// packetBuf.Write(lenNum)
// packetBuf.Write(buf[0:nr])
//
// _, ew := conn.Write(packetBuf.Bytes())
// if ew != nil {
// fmt.Printf("err2:%v\n", ew)
// break
// }
// }
// }
//}()
//
//go func() {
// result := bytes.NewBuffer(nil)
// var buf [65542]byte // 由于 标识数据包长度 的只有两个字节 故数据包最大为 2^16+4(魔数)+2(长度标识)
// for {
// n, er := conn.Read(buf[0:])
// result.Write(buf[0:n])
// if er != nil {
// if er == io.EOF {
// continue
// } else {
// fmt.Println("read err:", er)
// break
// }
// } else {
// scanner := bufio.NewScanner(result)
// scanner.Split(packetSlitFunc)
// for scanner.Scan() {
// realConnection.Write(scanner.Bytes()[HeaderLength+2:])
// }
// }
// result.Reset()
// }
//}()
}
func processHandshake(src net.Conn, dst net.Conn, waitCh chan int) {
@ -123,17 +177,3 @@ func processHandshake(src net.Conn, dst net.Conn, waitCh chan int) {
}
waitCh <- 1
}
func handlerMux(conn *smux.Stream, targetAddress string) {
realConnection, err := net.Dial("tcp", targetAddress)
if err != nil {
fmt.Printf("[Server] Dial target error : %v\n", err)
return
}
if err != nil {
return
}
go io.Copy(realConnection, conn)
go io.Copy(conn, realConnection)
}

View File

@ -1,75 +0,0 @@
package shadow
import (
"crypto/tls"
"github.com/xtaci/smux"
"net"
"sync"
"time"
)
type TLSBridge struct {
session *smux.Session
locker sync.Mutex
serverAddress string
fakeAddressSNI string
}
func NewTLSBridge(serverAddress string, fakeAddressSNI string) *TLSBridge {
t := &TLSBridge{
session: nil,
locker: sync.Mutex{},
serverAddress: serverAddress,
fakeAddressSNI: fakeAddressSNI,
}
return t
}
func (t *TLSBridge) dial() error {
if t.session != nil {
t.session.Close()
}
dial, err := tls.DialWithDialer(&net.Dialer{
Timeout: time.Second * 5,
}, "tcp", t.serverAddress, &tls.Config{
ServerName: t.fakeAddressSNI,
})
if err != nil {
return err
}
err = dial.Handshake()
if err != nil {
return err
}
dial.NetConn().SetDeadline(time.Now())
dial.NetConn().SetDeadline(time.Time{})
time.Sleep(time.Millisecond * 100)
session, err := smux.Client(dial.NetConn(), nil)
if err != nil {
return err
}
//force openStream to prevent first connection problem
session.OpenStream()
t.session = session
return nil
}
func (t *TLSBridge) GetStream() *smux.Stream {
t.locker.Lock()
defer t.locker.Unlock()
if t.session == nil {
err := t.dial()
if err != nil {
return nil
}
}
openStream, err := t.session.OpenStream()
if err != nil {
t.session.Close()
t.session = nil
}
return openStream
}