Skip to content

Commit

Permalink
Add QUIC support for GT.
Browse files Browse the repository at this point in the history
  • Loading branch information
DrakenLibra committed Sep 26, 2023
1 parent a57e397 commit 5e36287
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 27 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,18 +329,19 @@ options:
#### Internal QUIC Penetration

- Requirements: There is an intranet server and a public network server, and id1.example.com resolves to the address of the public network server. Hopefully by accessing id1.example.com:8080
To access the web page served by port 80 on the intranet server. At the same time, QUIC is used to build a transport connection between the client and the server.
To access the web page served by port 80 on the intranet server. Use QUIC to build a transport connection between the client and the server. QUIC uses TLS 1.3 for transport encryption. When the user also gives certFile
and keyFile, use them for encrypted communication. Otherwise, keys and certificates are automatically generated using the ECDSA encryption algorithm.

- Server (Public network server)
- Server (public network server)

```shell
./release/linux-amd64-server -addr 8080 -quicAddr 10080 -id id1 -secret secret1
./release/linux-amd64-server -addr 8080 -quicAddr 443 -certFile /root/openssl_crt/tls.crt -keyFile /root/openssl_crt/tls.key -id id1 -secret secret1
```

- Client (Internal network server)
- Client (internal network server), because a self-signed certificate is used, the `-remoteCertInsecure` option is used. This option is prohibited from being used in other cases (man-in-the-middle attacks cause encrypted content to be decrypted

```shell
./release/linux-amd64-client -local http://127.0.0.1:80 -remote quic://id1.example.com:10080 -id id1 -secret secret1
./release/linux-amd64-client -local http://127.0.0.1:80 -remote quic://id1.example.com:443 -remoteCertInsecure -id id1 -secret secret1
```

#### Client Start Multiple Services Simultaneously
Expand Down
9 changes: 5 additions & 4 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,18 +311,19 @@ options:
#### QUIC 内网穿透

- 需求:有一台内网服务器和一台公网服务器,id1.example.com 解析到公网服务器的地址。希望通过访问 id1.example.com:8080
来访问内网服务器上 80 端口服务的网页。同时用 QUIC 为客户端与服务端之间构建传输连接。
来访问内网服务器上 80 端口服务的网页。使用 QUIC 为客户端与服务端之间构建传输连接,QUIC 使用 TLS 1.3 进行传输加密。当用户同时给出certFile
和keyFile时,使用他们进行加密通信。否则,会使用 ECDSA 加密算法自动生成密钥和证书。

- 服务端(公网服务器)

```shell
./release/linux-amd64-server -addr 8080 -quicAddr 10080 -id id1 -secret secret1
./release/linux-amd64-server -addr 8080 -quicAddr 443 -certFile /root/openssl_crt/tls.crt -keyFile /root/openssl_crt/tls.key -id id1 -secret secret1
```

- 客户端(内网服务器)
- 客户端(内网服务器),因为使用了自签名证书,所以使用了 `-remoteCertInsecure` 选项,其它情况禁止使用此选项(中间人攻击导致加密内容被解密

```shell
./release/linux-amd64-client -local http://127.0.0.1:80 -remote quic://id1.example.com:10080 -id id1 -secret secret1
./release/linux-amd64-client -local http://127.0.0.1:80 -remote quic://id1.example.com:443 -remoteCertInsecure -id id1 -secret secret1
```

#### 客户端同时开启多个服务
Expand Down
24 changes: 22 additions & 2 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,29 @@ func (d *dialer) init(c *Client, remote string, stun string) (err error) {
d.dialFn = d.dial
case "quic":
if len(u.Port()) < 1 {
u.Host = net.JoinHostPort(u.Host, "10080")
u.Host = net.JoinHostPort(u.Host, "443")
}
tlsConfig := &tls.Config{}
if len(c.Config().RemoteCert) > 0 {
var cf []byte
cf, err = os.ReadFile(c.Config().RemoteCert)
if err != nil {
err = fmt.Errorf("failed to read remote cert file (-remoteCert option) '%s', cause %s", c.Config().RemoteCert, err.Error())
return
}
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM(cf)
if !ok {
err = fmt.Errorf("failed to parse remote cert file (-remoteCert option) '%s'", c.Config().RemoteCert)
return
}
tlsConfig.RootCAs = roots
}
if c.Config().RemoteCertInsecure {
tlsConfig.InsecureSkipVerify = true
}
d.host = u.Host
d.tlsConfig = tlsConfig
d.dialFn = d.quicDial
default:
err = fmt.Errorf("remote url (-remote option) '%s' is invalid", remote)
Expand Down Expand Up @@ -291,7 +311,7 @@ func (d *dialer) tlsDial() (conn net.Conn, err error) {
}

func (d *dialer) quicDial() (conn net.Conn, err error) {
return connection.QuicDial(d.host)
return connection.QuicDial(d.host, d.tlsConfig)
}

// Start runs the client agent.
Expand Down
36 changes: 24 additions & 12 deletions conn/quicConn.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package conn

import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
Expand All @@ -24,22 +25,29 @@ type QuicListener struct {
var _ net.Conn = &QuicConnection{}
var _ net.Listener = &QuicListener{}

func QuicDial(addr string) (net.Conn, error) {
tlsConf := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"quic-echo-example"},
func QuicDial(addr string, config *tls.Config) (net.Conn, error) {
config.NextProtos = []string{"gt-quic"}
conn, err := quic.DialAddr(context.Background(), addr, config, &quic.Config{})
if err != nil {
panic(err)
}
conn, _ := quic.DialAddr(context.Background(), addr, tlsConf, &quic.Config{})
stream, err := conn.OpenStreamSync(context.Background())
if err != nil {
panic(err)
}
nc := &QuicConnection{
Connection: conn,
Stream: stream,
}
return nc, err
}

func QuicListen(addr string) (net.Listener, error) {
listener, err := quic.ListenAddr(addr, GenerateTLSConfig(), nil)
func QuicListen(addr string, config *tls.Config) (net.Listener, error) {
config.NextProtos = []string{"gt-quic"}
listener, err := quic.ListenAddr(addr, config, &quic.Config{})
if err != nil {
panic(err)
}
ln := &QuicListener{
Listener: *listener,
}
Expand All @@ -57,16 +65,20 @@ func (ln *QuicListener) Accept() (net.Conn, error) {
}

func GenerateTLSConfig() *tls.Config {
key, err := rsa.GenerateKey(rand.Reader, 1024)
ecdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
panic(err)
}
template := x509.Certificate{SerialNumber: big.NewInt(1)}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &ecdsaKey.PublicKey, ecdsaKey)
if err != nil {
panic(err)
}
keyBytes, err := x509.MarshalECPrivateKey(ecdsaKey)
if err != nil {
panic(err)
}
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "ECDSA PRIVATE KEY", Bytes: keyBytes})
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})

tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
Expand All @@ -75,6 +87,6 @@ func GenerateTLSConfig() *tls.Config {
}
return &tls.Config{
Certificates: []tls.Certificate{tlsCert},
NextProtos: []string{"quic-echo-example"},
NextProtos: []string{"gt-quic"},
}
}
15 changes: 13 additions & 2 deletions server/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,19 @@ func (c *conn) handle(handleFunc func() bool) {

c.Logger = c.Logger.With().Time("tunnel", time.Now()).Logger()

remoteAddr := c.RemoteAddr().String()
remoteIP := strings.Split(remoteAddr, ":")[0]
remoteTcpAddr, okTcp := c.RemoteAddr().(*net.TCPAddr)
var remoteIP string
if okTcp {
remoteIP = remoteTcpAddr.IP.String()
} else {
remoteUdpAddr, okUdp := c.RemoteAddr().(*net.UDPAddr)
if okUdp {
remoteIP = remoteUdpAddr.IP.String()
} else {
c.Logger.Warn().Msg("conn is not tcp/udp conn")
return
}
}

c.server.reconnectRWMutex.RLock()
reconnectTimes := c.server.reconnect[remoteIP]
Expand Down
13 changes: 11 additions & 2 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,21 @@ func (s *Server) listen() (err error) {
}

func (s *Server) quicListen() (err error) {
s.quicListener, err = connection.QuicListen(s.config.QuicAddr)
var tlsConfig *tls.Config
if len(s.config.CertFile) > 0 && len(s.config.KeyFile) > 0 {
tlsConfig, err = newTLSConfig(s.config.CertFile, s.config.KeyFile, s.config.TLSMinVersion)
} else {
tlsConfig = connection.GenerateTLSConfig()
}
if err != nil {
return
}
s.quicListener, err = connection.QuicListen(s.config.QuicAddr, tlsConfig)
if err != nil {
err = fmt.Errorf("can not listen on addr '%s', cause %s, please check option 'addr'", s.config.QuicAddr, err.Error())
return
}
s.Logger.Info().Str("addr", s.quicListener.Addr().String()).Msg("Listening")
s.Logger.Info().Str("QuicAddr", s.quicListener.Addr().String()).Msg("Listening")
go s.acceptLoop(s.quicListener, func(c *conn) {
c.handle(c.handleHTTP)
})
Expand Down

0 comments on commit 5e36287

Please sign in to comment.