【Go语言】quic-go实现0-RTT传输

核心思路:在客户端的tls文件中缓存第一次连接留下来的会话票据,在第二次连接中就可以实现0-RTT。为此,重要的是实现tls.Config.ClientSessionCache这个接口的具体结构体


文件目录


tlscfg.go代码:这个模块主要用于实现客户端和服务器的tls配置

package tlscfg

import (
	"crypto"
	"crypto/ed25519"
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"log"
	"math/big"
	"net"
	"time"
)

const alpn string = "zerortt"

// 产生自签名的根证书和私钥
func GenerateCA() (*x509.Certificate, crypto.PrivateKey, error) {
	certTempl := &x509.Certificate{
		SerialNumber:          big.NewInt(2019),
		Subject:               pkix.Name{},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().Add(24 * time.Hour),
		IsCA:                  true,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		BasicConstraintsValid: true,
	}
	pub, priv, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		return nil, nil, err
	}
	caBytes, err := x509.CreateCertificate(rand.Reader, certTempl, certTempl, pub, priv)
	if err != nil {
		return nil, nil, err
	}
	ca, err := x509.ParseCertificate(caBytes)
	if err != nil {
		return nil, nil, err
	}
	return ca, priv, nil
}

// 产生一个服务器(叶)证书和私钥
func GenerateLeafCert(ca *x509.Certificate, caPriv crypto.PrivateKey) (*x509.Certificate, crypto.PrivateKey, error) {
	certTempl := &x509.Certificate{
		SerialNumber: big.NewInt(1),
		DNSNames:     []string{"localhost"},
		IPAddresses:  []net.IP{net.IPv4(127, 0, 0, 1)},
		NotBefore:    time.Now(),
		NotAfter:     time.Now().Add(24 * time.Hour),
		ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
		KeyUsage:     x509.KeyUsageDigitalSignature,
	}
	pub, priv, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		return nil, nil, err
	}
	certBytes, err := x509.CreateCertificate(rand.Reader, certTempl, ca, pub, caPriv)
	if err != nil {
		return nil, nil, err
	}
	cert, err := x509.ParseCertificate(certBytes)
	if err != nil {
		return nil, nil, err
	}
	return cert, priv, nil
}

// 产生服务器tls config
// 首字母必须大写才能够被main,go import识别
func GetTLSServerConfig() *tls.Config {
	ca, caPrivateKey, err := GenerateCA()
	if err != nil {
		log.Fatal(err)
	}
	leafCert, leafPrivateKey, err := GenerateLeafCert(ca, caPrivateKey)
	if err != nil {
		log.Fatal(err)
	}
	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{{
			Certificate: [][]byte{leafCert.Raw},
			PrivateKey:  leafPrivateKey,
		}},
		NextProtos:         []string{alpn},
		InsecureSkipVerify: true,
	}
	return tlsConfig
}

// 产生客户端 tls config
func GetTLSClientConfig() *tls.Config {
	ca, _, _ := GenerateCA()
	root := x509.NewCertPool()
	root.AddCert(ca)
	tlsClientConfig := &tls.Config{
		ServerName:         "localhost",
		RootCAs:            root,
		NextProtos:         []string{alpn},
		InsecureSkipVerify: true,
	}

	return tlsClientConfig
}

sessionCache.go代码:主要实现了tls.Config.ClientSenssionCache的具体结构体

package cltSessionCache

import (
	"crypto/tls"
)

// 这段代码实现了一个clientSessionCache类型,这个类型实现了tls.ClientSessionCache的所有接口
type clientSessionCache struct {
	cache tls.ClientSessionCache

	gets chan<- string
	puts chan<- string
}

func NewClientSessionCache(cache tls.ClientSessionCache, gets, puts chan<- string) *clientSessionCache {
	return &clientSessionCache{
		cache: cache,
		gets:  gets,
		puts:  puts,
	}
}

var _ tls.ClientSessionCache = &clientSessionCache{}

func (c *clientSessionCache) Get(sessionKey string) (*tls.ClientSessionState, bool) {

	session, ok := c.cache.Get(sessionKey)
	if c.gets != nil {
		if session != nil {
			//fmt.Printf("Client:The Session Ticket is in my Cache!\n")
			//fmt.Printf("Info: %#v\n", session)
		}
		//fmt.Printf("Ticket: %v\n",c.ticket)
		c.gets <- sessionKey
	}
	return session, ok
}

func (c *clientSessionCache) Put(sessionKey string, cs *tls.ClientSessionState) {
	//fmt.Printf("Client Get a Session Ticket %s!\n", sessionKey)
	//fmt.Printf("Info: %#v\n", cs)
	c.cache.Put(sessionKey, cs)
	if c.puts != nil {
		c.puts <- sessionKey
	}

}

zerorttExpir.go代码:进行三次连接,后面两次连接实现了0-RTT

package main

import (
	"context"
	"crypto/tls"
	"fmt"
	"io"
	"log"
	"zerorttExpir/cltSessionCache"
	"zerorttExpir/tlscfg"

	quic "github.com/quic-go/quic-go"
)

// 服务器监听的端口号
const port string = "4242"

func getQuicConfig(conf *quic.Config) *quic.Config {
	if conf == nil {
		conf = &quic.Config{Allow0RTT: true}
	} else {
		conf = conf.Clone()
		conf.Allow0RTT = true
	}
	return conf
}

// 产生指定大小的比特数据,用于模拟数据传输
func GeneratePRData(l int) []byte {
	res := make([]byte, l)
	seed := uint64(1)
	for i := 0; i < l; i++ {
		seed = seed * 48271 % 2147483647
		res[i] = byte(seed)
	}
	return res
}

func Transfer0RttData(serverTLSConf *tls.Config, serverConf *quic.Config, clientTLSConf *tls.Config, clientConf *quic.Config) {

	fmt.Printf("Start Server Listening on Port %s \n", port)
	//服务器监听指定端口
	ln, err := quic.ListenAddrEarly(
		"localhost:"+port,
		serverTLSConf,
		serverConf,
	)

	if err != nil {
		log.Fatal(err)
	}
	defer ln.Close()

	//启动一个goroutine等待客户端发来的stream数据
	go func() {
		conn1, err := ln.Accept(context.Background())
		if err != nil {
			log.Fatal(err)
		}
		str, err := conn1.AcceptStream(context.Background())
		if err != nil {
			log.Fatal(err)
		}
		_, _ = io.ReadAll(str)
	}()

	//客户端连接服务器
	fmt.Printf("Start Client Connect to %s!\n", "localhost:"+port)

	//在client的TLS配置中加入Session Ticket缓存,用于保存0-RTT 的Session Ticket
	puts := make(chan string, 100)
	cache := clientTLSConf.ClientSessionCache
	if cache == nil {
		cache = tls.NewLRUClientSessionCache(100)
	}
	clientTLSConf.ClientSessionCache = cltSessionCache.NewClientSessionCache(cache, make(chan string, 100), puts)

	//连接服务器
	var conn quic.EarlyConnection
	conn, err = quic.DialAddrEarly(
		context.Background(),
		fmt.Sprintf("localhost:%s", port),
		clientTLSConf,
		getQuicConfig(nil),
	)

	if err != nil {
		log.Fatal(err)
	}

	//等待握手完成
	<-conn.HandshakeComplete()
	fmt.Println("Handshake Complete! 0-RTT used: ", conn.ConnectionState().Used0RTT)

	//这里必须进行实际数据的传输,否则Client将不会保存Session Ticket。会话票据会在握手完成后传输给客户端
	str, _ := conn.OpenStream()
	testdata := GeneratePRData(500 * 1024)
	_, err = str.Write(testdata)

	if err != nil {
		log.Fatal(err)
	}

	conn.CloseWithError(0, "")
	ln.Close()
}
func main() {

	serverTLSConf := tlscfg.GetTLSServerConfig()
	clientTLSConf := tlscfg.GetTLSClientConfig()
	serverQuicConf := getQuicConfig(nil)
	clientQuicConf := getQuicConfig(nil)

	for _, l := range []int{0, 1, 2} {
		fmt.Printf("%d Connection!\n", l)
		Transfer0RttData(serverTLSConf, serverQuicConf, clientTLSConf, clientQuicConf)
	}

}

go.mod文件

module zerorttExpir

go 1.22.7

require github.com/quic-go/quic-go v0.47.0

require (
	github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
	github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
	github.com/onsi/ginkgo/v2 v2.9.5 // indirect
	go.uber.org/mock v0.4.0 // indirect
	golang.org/x/crypto v0.26.0 // indirect
	golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
	golang.org/x/mod v0.17.0 // indirect
	golang.org/x/net v0.28.0 // indirect
	golang.org/x/sys v0.23.0 // indirect
	golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值