核心思路:在客户端的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
)