mirror of
https://github.com/refraction-networking/uquic.git
synced 2025-04-02 19:57:35 +03:00
new: uquic
This commit is contained in:
parent
95f3eaaa66
commit
ea40752ca3
25 changed files with 1420 additions and 686 deletions
109
client.go
109
client.go
|
@ -38,8 +38,6 @@ type client struct {
|
||||||
tracer logging.ConnectionTracer
|
tracer logging.ConnectionTracer
|
||||||
tracingID uint64
|
tracingID uint64
|
||||||
logger utils.Logger
|
logger utils.Logger
|
||||||
|
|
||||||
chs *tls.ClientHelloSpec // [UQUIC]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// make it possible to mock connection ID for initial generation in the tests
|
// make it possible to mock connection ID for initial generation in the tests
|
||||||
|
@ -167,41 +165,6 @@ func dial(
|
||||||
return c.conn, nil
|
return c.conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dialWithCHS(
|
|
||||||
ctx context.Context,
|
|
||||||
conn sendConn,
|
|
||||||
connIDGenerator ConnectionIDGenerator,
|
|
||||||
packetHandlers packetHandlerManager,
|
|
||||||
tlsConf *tls.Config,
|
|
||||||
config *Config,
|
|
||||||
onClose func(),
|
|
||||||
use0RTT bool,
|
|
||||||
chs *tls.ClientHelloSpec,
|
|
||||||
) (quicConn, error) {
|
|
||||||
c, err := newClient(conn, connIDGenerator, config, tlsConf, onClose, use0RTT)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.packetHandlers = packetHandlers
|
|
||||||
|
|
||||||
c.tracingID = nextConnTracingID()
|
|
||||||
if c.config.Tracer != nil {
|
|
||||||
c.tracer = c.config.Tracer(context.WithValue(ctx, ConnectionTracingKey, c.tracingID), protocol.PerspectiveClient, c.destConnID)
|
|
||||||
}
|
|
||||||
if c.tracer != nil {
|
|
||||||
c.tracer.StartedConnection(c.sendConn.LocalAddr(), c.sendConn.RemoteAddr(), c.srcConnID, c.destConnID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// [UQUIC]
|
|
||||||
c.chs = chs
|
|
||||||
// [/UQUIC]
|
|
||||||
|
|
||||||
if err := c.dial(ctx); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return c.conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newClient(sendConn sendConn, connIDGenerator ConnectionIDGenerator, config *Config, tlsConf *tls.Config, onClose func(), use0RTT bool) (*client, error) {
|
func newClient(sendConn sendConn, connIDGenerator ConnectionIDGenerator, config *Config, tlsConf *tls.Config, onClose func(), use0RTT bool) (*client, error) {
|
||||||
if tlsConf == nil {
|
if tlsConf == nil {
|
||||||
tlsConf = &tls.Config{}
|
tlsConf = &tls.Config{}
|
||||||
|
@ -209,25 +172,12 @@ func newClient(sendConn sendConn, connIDGenerator ConnectionIDGenerator, config
|
||||||
tlsConf = tlsConf.Clone()
|
tlsConf = tlsConf.Clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
// // [UQUIC]
|
|
||||||
// if config.SrcConnIDLength != 0 {
|
|
||||||
// connIDLen := config.SrcConnIDLength
|
|
||||||
// connIDGenerator = &protocol.DefaultConnectionIDGenerator{ConnLen: connIDLen}
|
|
||||||
// }
|
|
||||||
|
|
||||||
srcConnID, err := connIDGenerator.GenerateConnectionID()
|
srcConnID, err := connIDGenerator.GenerateConnectionID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var destConnID protocol.ConnectionID
|
destConnID, err := generateConnectionIDForInitial()
|
||||||
// [UQUIC]
|
|
||||||
if config.DestConnIDLength > 0 {
|
|
||||||
destConnID, err = generateConnectionIDForInitialWithLength(config.DestConnIDLength)
|
|
||||||
} else {
|
|
||||||
destConnID, err = generateConnectionIDForInitial()
|
|
||||||
}
|
|
||||||
// [/UQUIC]
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -243,8 +193,6 @@ func newClient(sendConn sendConn, connIDGenerator ConnectionIDGenerator, config
|
||||||
version: config.Versions[0],
|
version: config.Versions[0],
|
||||||
handshakeChan: make(chan struct{}),
|
handshakeChan: make(chan struct{}),
|
||||||
logger: utils.DefaultLogger.WithPrefix("client"),
|
logger: utils.DefaultLogger.WithPrefix("client"),
|
||||||
|
|
||||||
initialPacketNumber: protocol.PacketNumber(config.InitPacketNumber), // [UQUIC]
|
|
||||||
}
|
}
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
@ -252,45 +200,22 @@ func newClient(sendConn sendConn, connIDGenerator ConnectionIDGenerator, config
|
||||||
func (c *client) dial(ctx context.Context) error {
|
func (c *client) dial(ctx context.Context) error {
|
||||||
c.logger.Infof("Starting new connection to %s (%s -> %s), source connection ID %s, destination connection ID %s, version %s", c.tlsConf.ServerName, c.sendConn.LocalAddr(), c.sendConn.RemoteAddr(), c.srcConnID, c.destConnID, c.version)
|
c.logger.Infof("Starting new connection to %s (%s -> %s), source connection ID %s, destination connection ID %s, version %s", c.tlsConf.ServerName, c.sendConn.LocalAddr(), c.sendConn.RemoteAddr(), c.srcConnID, c.destConnID, c.version)
|
||||||
|
|
||||||
// [UQUIC]
|
c.conn = newClientConnection(
|
||||||
if c.chs == nil {
|
c.sendConn,
|
||||||
c.conn = newClientConnection(
|
c.packetHandlers,
|
||||||
c.sendConn,
|
c.destConnID,
|
||||||
c.packetHandlers,
|
c.srcConnID,
|
||||||
c.destConnID,
|
c.connIDGenerator,
|
||||||
c.srcConnID,
|
c.config,
|
||||||
c.connIDGenerator,
|
c.tlsConf,
|
||||||
c.config,
|
c.initialPacketNumber,
|
||||||
c.tlsConf,
|
c.use0RTT,
|
||||||
c.initialPacketNumber,
|
c.hasNegotiatedVersion,
|
||||||
c.use0RTT,
|
c.tracer,
|
||||||
c.hasNegotiatedVersion,
|
c.tracingID,
|
||||||
c.tracer,
|
c.logger,
|
||||||
c.tracingID,
|
c.version,
|
||||||
c.logger,
|
)
|
||||||
c.version,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// [UQUIC]: use custom version of the connection
|
|
||||||
c.conn = newUClientConnection(
|
|
||||||
c.sendConn,
|
|
||||||
c.packetHandlers,
|
|
||||||
c.destConnID,
|
|
||||||
c.srcConnID,
|
|
||||||
c.connIDGenerator,
|
|
||||||
c.config,
|
|
||||||
c.tlsConf,
|
|
||||||
c.initialPacketNumber,
|
|
||||||
c.use0RTT,
|
|
||||||
c.hasNegotiatedVersion,
|
|
||||||
c.tracer,
|
|
||||||
c.tracingID,
|
|
||||||
c.logger,
|
|
||||||
c.version,
|
|
||||||
c.chs,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// [/UQUIC]
|
|
||||||
|
|
||||||
c.packetHandlers.Add(c.srcConnID, c.conn)
|
c.packetHandlers.Add(c.srcConnID, c.conn)
|
||||||
|
|
||||||
|
|
|
@ -131,12 +131,6 @@ func populateConfig(config *Config) *Config {
|
||||||
DisableVersionNegotiationPackets: config.DisableVersionNegotiationPackets,
|
DisableVersionNegotiationPackets: config.DisableVersionNegotiationPackets,
|
||||||
Allow0RTT: config.Allow0RTT,
|
Allow0RTT: config.Allow0RTT,
|
||||||
Tracer: config.Tracer,
|
Tracer: config.Tracer,
|
||||||
|
|
||||||
// [UQUIC]
|
|
||||||
SrcConnIDLength: config.SrcConnIDLength,
|
|
||||||
DestConnIDLength: config.DestConnIDLength,
|
|
||||||
InitPacketNumber: config.InitPacketNumber,
|
|
||||||
InitPacketNumberLength: config.InitPacketNumberLength,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,10 +62,12 @@ func (h *connIDManager) Add(f *wire.NewConnectionIDFrame) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [UQUIC]
|
||||||
connIDLimit := h.connectionIDLimit
|
connIDLimit := h.connectionIDLimit
|
||||||
if connIDLimit == 0 {
|
if connIDLimit == 0 {
|
||||||
connIDLimit = protocol.MaxActiveConnectionIDs
|
connIDLimit = protocol.MaxActiveConnectionIDs
|
||||||
}
|
}
|
||||||
|
// [/UQUIC]
|
||||||
|
|
||||||
if uint64(h.queue.Len()) >= connIDLimit {
|
if uint64(h.queue.Len()) >= connIDLimit {
|
||||||
return &qerr.TransportError{ErrorCode: qerr.ConnectionIDLimitError}
|
return &qerr.TransportError{ErrorCode: qerr.ConnectionIDLimitError}
|
||||||
|
@ -191,11 +193,6 @@ func (h *connIDManager) SetStatelessResetToken(token protocol.StatelessResetToke
|
||||||
h.addStatelessResetToken(token)
|
h.addStatelessResetToken(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
// [UQUIC]
|
|
||||||
func (h *connIDManager) SetConnectionIDLimit(limit uint64) {
|
|
||||||
h.connectionIDLimit = limit
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *connIDManager) SentPacket() {
|
func (h *connIDManager) SentPacket() {
|
||||||
h.packetsSinceLastChange++
|
h.packetsSinceLastChange++
|
||||||
}
|
}
|
||||||
|
|
152
connection.go
152
connection.go
|
@ -390,9 +390,6 @@ var newClientConnection = func(
|
||||||
s.tracer,
|
s.tracer,
|
||||||
s.logger,
|
s.logger,
|
||||||
)
|
)
|
||||||
if conf.InitPacketNumberLength != 0 {
|
|
||||||
ackhandler.SetInitialPacketNumberLength(s.sentPacketHandler, conf.InitPacketNumberLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mtuDiscoverer = newMTUDiscoverer(s.rttStats, getMaxPacketSize(s.conn.RemoteAddr()), s.sentPacketHandler.SetMaxDatagramSize)
|
s.mtuDiscoverer = newMTUDiscoverer(s.rttStats, getMaxPacketSize(s.conn.RemoteAddr()), s.sentPacketHandler.SetMaxDatagramSize)
|
||||||
oneRTTStream := newCryptoStream()
|
oneRTTStream := newCryptoStream()
|
||||||
|
@ -453,151 +450,6 @@ var newClientConnection = func(
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// [UQUIC]
|
|
||||||
var newUClientConnection = func(
|
|
||||||
conn sendConn,
|
|
||||||
runner connRunner,
|
|
||||||
destConnID protocol.ConnectionID,
|
|
||||||
srcConnID protocol.ConnectionID,
|
|
||||||
connIDGenerator ConnectionIDGenerator,
|
|
||||||
conf *Config,
|
|
||||||
tlsConf *tls.Config,
|
|
||||||
initialPacketNumber protocol.PacketNumber,
|
|
||||||
enable0RTT bool,
|
|
||||||
hasNegotiatedVersion bool,
|
|
||||||
tracer logging.ConnectionTracer,
|
|
||||||
tracingID uint64,
|
|
||||||
logger utils.Logger,
|
|
||||||
v protocol.VersionNumber,
|
|
||||||
chs *tls.ClientHelloSpec,
|
|
||||||
) quicConn {
|
|
||||||
s := &connection{
|
|
||||||
conn: conn,
|
|
||||||
config: conf,
|
|
||||||
origDestConnID: destConnID,
|
|
||||||
handshakeDestConnID: destConnID,
|
|
||||||
srcConnIDLen: srcConnID.Len(),
|
|
||||||
perspective: protocol.PerspectiveClient,
|
|
||||||
logID: destConnID.String(),
|
|
||||||
logger: logger,
|
|
||||||
tracer: tracer,
|
|
||||||
versionNegotiated: hasNegotiatedVersion,
|
|
||||||
version: v,
|
|
||||||
}
|
|
||||||
s.connIDManager = newConnIDManager(
|
|
||||||
destConnID,
|
|
||||||
func(token protocol.StatelessResetToken) { runner.AddResetToken(token, s) },
|
|
||||||
runner.RemoveResetToken,
|
|
||||||
s.queueControlFrame,
|
|
||||||
)
|
|
||||||
|
|
||||||
s.connIDGenerator = newConnIDGenerator(
|
|
||||||
srcConnID,
|
|
||||||
nil,
|
|
||||||
func(connID protocol.ConnectionID) { runner.Add(connID, s) },
|
|
||||||
runner.GetStatelessResetToken,
|
|
||||||
runner.Remove,
|
|
||||||
runner.Retire,
|
|
||||||
runner.ReplaceWithClosed,
|
|
||||||
s.queueControlFrame,
|
|
||||||
connIDGenerator,
|
|
||||||
)
|
|
||||||
s.preSetup()
|
|
||||||
s.ctx, s.ctxCancel = context.WithCancelCause(context.WithValue(context.Background(), ConnectionTracingKey, tracingID))
|
|
||||||
s.sentPacketHandler, s.receivedPacketHandler = ackhandler.NewAckHandler(
|
|
||||||
initialPacketNumber,
|
|
||||||
getMaxPacketSize(s.conn.RemoteAddr()),
|
|
||||||
s.rttStats,
|
|
||||||
false, /* has no effect */
|
|
||||||
s.perspective,
|
|
||||||
s.tracer,
|
|
||||||
s.logger,
|
|
||||||
)
|
|
||||||
if conf.InitPacketNumberLength != 0 {
|
|
||||||
ackhandler.SetInitialPacketNumberLength(s.sentPacketHandler, conf.InitPacketNumberLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mtuDiscoverer = newMTUDiscoverer(s.rttStats, getMaxPacketSize(s.conn.RemoteAddr()), s.sentPacketHandler.SetMaxDatagramSize)
|
|
||||||
oneRTTStream := newCryptoStream()
|
|
||||||
|
|
||||||
var params *wire.TransportParameters
|
|
||||||
// params := &wire.TransportParameters{
|
|
||||||
// InitialMaxStreamDataBidiRemote: protocol.ByteCount(s.config.InitialStreamReceiveWindow),
|
|
||||||
// InitialMaxStreamDataBidiLocal: protocol.ByteCount(s.config.InitialStreamReceiveWindow),
|
|
||||||
// InitialMaxStreamDataUni: protocol.ByteCount(s.config.InitialStreamReceiveWindow),
|
|
||||||
// InitialMaxData: protocol.ByteCount(s.config.InitialConnectionReceiveWindow),
|
|
||||||
// MaxIdleTimeout: s.config.MaxIdleTimeout,
|
|
||||||
// MaxBidiStreamNum: protocol.StreamNum(s.config.MaxIncomingStreams),
|
|
||||||
// MaxUniStreamNum: protocol.StreamNum(s.config.MaxIncomingUniStreams),
|
|
||||||
// MaxAckDelay: protocol.MaxAckDelayInclGranularity,
|
|
||||||
// AckDelayExponent: protocol.AckDelayExponent,
|
|
||||||
// DisableActiveMigration: true,
|
|
||||||
// // For interoperability with quic-go versions before May 2023, this value must be set to a value
|
|
||||||
// // different from protocol.DefaultActiveConnectionIDLimit.
|
|
||||||
// // If set to the default value, it will be omitted from the transport parameters, which will make
|
|
||||||
// // old quic-go versions interpret it as 0, instead of the default value of 2.
|
|
||||||
// // See https://github.com/quic-go/quic-go/pull/3806.
|
|
||||||
// ActiveConnectionIDLimit: protocol.MaxActiveConnectionIDs,
|
|
||||||
// InitialSourceConnectionID: srcConnID,
|
|
||||||
// }
|
|
||||||
// if s.config.EnableDatagrams {
|
|
||||||
// params.MaxDatagramFrameSize = protocol.MaxDatagramFrameSize
|
|
||||||
// } else {
|
|
||||||
// params.MaxDatagramFrameSize = protocol.InvalidByteCount
|
|
||||||
// }
|
|
||||||
|
|
||||||
// [UQUIC] iterate over all Extensions to set the TransportParameters
|
|
||||||
var tpSet bool
|
|
||||||
FOR_EACH_TLS_EXTENSION:
|
|
||||||
for _, ext := range chs.Extensions {
|
|
||||||
switch ext := ext.(type) {
|
|
||||||
case *tls.QUICTransportParametersExtension:
|
|
||||||
params = &wire.TransportParameters{
|
|
||||||
InitialSourceConnectionID: srcConnID,
|
|
||||||
}
|
|
||||||
params.PopulateFromUQUIC(ext.TransportParameters)
|
|
||||||
s.connIDManager.SetConnectionIDLimit(params.ActiveConnectionIDLimit)
|
|
||||||
tpSet = true
|
|
||||||
break FOR_EACH_TLS_EXTENSION
|
|
||||||
default:
|
|
||||||
continue FOR_EACH_TLS_EXTENSION
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !tpSet {
|
|
||||||
panic("applied ClientHelloSpec must contain a QUICTransportParametersExtension to proceed")
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.tracer != nil {
|
|
||||||
s.tracer.SentTransportParameters(params)
|
|
||||||
}
|
|
||||||
cs := handshake.NewUCryptoSetupClient(
|
|
||||||
destConnID,
|
|
||||||
params,
|
|
||||||
tlsConf,
|
|
||||||
enable0RTT,
|
|
||||||
s.rttStats,
|
|
||||||
tracer,
|
|
||||||
logger,
|
|
||||||
s.version,
|
|
||||||
chs,
|
|
||||||
)
|
|
||||||
s.cryptoStreamHandler = cs
|
|
||||||
s.cryptoStreamManager = newCryptoStreamManager(cs, s.initialStream, s.handshakeStream, oneRTTStream)
|
|
||||||
s.unpacker = newPacketUnpacker(cs, s.srcConnIDLen)
|
|
||||||
s.packer = newPacketPacker(srcConnID, s.connIDManager.Get, s.initialStream, s.handshakeStream, s.sentPacketHandler, s.retransmissionQueue, cs, s.framer, s.receivedPacketHandler, s.datagramQueue, s.perspective)
|
|
||||||
if len(tlsConf.ServerName) > 0 {
|
|
||||||
s.tokenStoreKey = tlsConf.ServerName
|
|
||||||
} else {
|
|
||||||
s.tokenStoreKey = conn.RemoteAddr().String()
|
|
||||||
}
|
|
||||||
if s.config.TokenStore != nil {
|
|
||||||
if token := s.config.TokenStore.Pop(s.tokenStoreKey); token != nil {
|
|
||||||
s.packer.SetToken(token.data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *connection) preSetup() {
|
func (s *connection) preSetup() {
|
||||||
s.initialStream = newCryptoStream()
|
s.initialStream = newCryptoStream()
|
||||||
s.handshakeStream = newCryptoStream()
|
s.handshakeStream = newCryptoStream()
|
||||||
|
@ -2204,9 +2056,7 @@ func (s *connection) sendPackedCoalescedPacket(packet *coalescedPacket, now time
|
||||||
}
|
}
|
||||||
s.connIDManager.SentPacket()
|
s.connIDManager.SentPacket()
|
||||||
s.sendQueue.Send(packet.buffer, packet.buffer.Len())
|
s.sendQueue.Send(packet.buffer, packet.buffer.Len())
|
||||||
// [UQUIC]
|
|
||||||
// fmt.Printf("sendPackedCoalescedPacket:Sending %d bytes\n", packet.buffer.Len())
|
|
||||||
// fmt.Printf("sendPackedCoalescedPacket: %v\n", packet.buffer.Data)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,83 @@ import (
|
||||||
"github.com/quic-go/quic-go/http3"
|
"github.com/quic-go/quic-go/http3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getCHS() *tls.ClientHelloSpec {
|
func main() {
|
||||||
|
keyLogWriter, err := os.Create("./keylog.txt")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConf := &tls.Config{
|
||||||
|
ServerName: "quic.tlsfingerprint.io",
|
||||||
|
// ServerName: "www.cloudflare.com",
|
||||||
|
// MinVersion: tls.VersionTLS13,
|
||||||
|
KeyLogWriter: keyLogWriter,
|
||||||
|
// NextProtos: []string{"h3"},
|
||||||
|
}
|
||||||
|
|
||||||
|
quicConf := &quic.Config{}
|
||||||
|
|
||||||
|
roundTripper := &http3.RoundTripper{
|
||||||
|
TLSClientConfig: tlsConf,
|
||||||
|
QuicConfig: quicConf,
|
||||||
|
}
|
||||||
|
uRoundTripper := http3.GetURoundTripper(
|
||||||
|
roundTripper,
|
||||||
|
// getFFQUICSpec(),
|
||||||
|
getCRQUICSpec(),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
defer uRoundTripper.Close()
|
||||||
|
|
||||||
|
hclient := &http.Client{
|
||||||
|
Transport: uRoundTripper,
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := "https://quic.tlsfingerprint.io/qfp/?beautify=true"
|
||||||
|
// addr := "https://www.cloudflare.com"
|
||||||
|
|
||||||
|
rsp, err := hclient.Get(addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Got response for %s: %#v", addr, rsp)
|
||||||
|
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
_, err = io.Copy(body, rsp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Response Body: %s", body.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFFQUICSpec() *quic.QUICSpec {
|
||||||
|
return &quic.QUICSpec{
|
||||||
|
InitialPacketSpec: quic.InitialPacketSpec{
|
||||||
|
SrcConnIDLength: 3,
|
||||||
|
DestConnIDLength: 8,
|
||||||
|
InitPacketNumberLength: 1,
|
||||||
|
InitPacketNumber: 1,
|
||||||
|
ClientTokenLength: 0,
|
||||||
|
FrameOrder: quic.QUICFrames{
|
||||||
|
&quic.QUICFrameCrypto{
|
||||||
|
Offset: 300,
|
||||||
|
Length: 0,
|
||||||
|
},
|
||||||
|
&quic.QUICFramePadding{
|
||||||
|
Length: 125,
|
||||||
|
},
|
||||||
|
&quic.QUICFramePing{},
|
||||||
|
&quic.QUICFrameCrypto{
|
||||||
|
Offset: 0,
|
||||||
|
Length: 300,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ClientHelloSpec: getFFCHS(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFFCHS() *tls.ClientHelloSpec {
|
||||||
return &tls.ClientHelloSpec{
|
return &tls.ClientHelloSpec{
|
||||||
TLSVersMin: tls.VersionTLS13,
|
TLSVersMin: tls.VersionTLS13,
|
||||||
TLSVersMax: tls.VersionTLS13,
|
TLSVersMax: tls.VersionTLS13,
|
||||||
|
@ -135,54 +211,146 @@ func getCHS() *tls.ClientHelloSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func getCRQUICSpec() *quic.QUICSpec {
|
||||||
keyLogWriter, err := os.Create("./keylog.txt")
|
return &quic.QUICSpec{
|
||||||
if err != nil {
|
InitialPacketSpec: quic.InitialPacketSpec{
|
||||||
panic(err)
|
SrcConnIDLength: 0,
|
||||||
|
DestConnIDLength: 8,
|
||||||
|
InitPacketNumberLength: 1,
|
||||||
|
InitPacketNumber: 1,
|
||||||
|
ClientTokenLength: 0,
|
||||||
|
FrameOrder: quic.QUICFrames{
|
||||||
|
&quic.QUICFrameCrypto{
|
||||||
|
Offset: 300,
|
||||||
|
Length: 0,
|
||||||
|
},
|
||||||
|
&quic.QUICFramePadding{
|
||||||
|
Length: 125,
|
||||||
|
},
|
||||||
|
&quic.QUICFramePing{},
|
||||||
|
&quic.QUICFrameCrypto{
|
||||||
|
Offset: 0,
|
||||||
|
Length: 300,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ClientHelloSpec: getCRCHS(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func getCRCHS() *tls.ClientHelloSpec {
|
||||||
|
return &tls.ClientHelloSpec{
|
||||||
|
TLSVersMin: tls.VersionTLS13,
|
||||||
|
TLSVersMax: tls.VersionTLS13,
|
||||||
|
CipherSuites: []uint16{
|
||||||
|
tls.TLS_AES_128_GCM_SHA256,
|
||||||
|
tls.TLS_CHACHA20_POLY1305_SHA256,
|
||||||
|
tls.TLS_AES_256_GCM_SHA384,
|
||||||
|
},
|
||||||
|
CompressionMethods: []uint8{
|
||||||
|
0x0, // no compression
|
||||||
|
},
|
||||||
|
Extensions: []tls.TLSExtension{
|
||||||
|
&tls.SNIExtension{},
|
||||||
|
&tls.ExtendedMasterSecretExtension{},
|
||||||
|
&tls.RenegotiationInfoExtension{
|
||||||
|
Renegotiation: tls.RenegotiateOnceAsClient,
|
||||||
|
},
|
||||||
|
&tls.SupportedCurvesExtension{
|
||||||
|
Curves: []tls.CurveID{
|
||||||
|
tls.CurveX25519,
|
||||||
|
tls.CurveSECP256R1,
|
||||||
|
tls.CurveSECP384R1,
|
||||||
|
tls.CurveSECP521R1,
|
||||||
|
tls.FakeCurveFFDHE2048,
|
||||||
|
tls.FakeCurveFFDHE3072,
|
||||||
|
tls.FakeCurveFFDHE4096,
|
||||||
|
tls.FakeCurveFFDHE6144,
|
||||||
|
tls.FakeCurveFFDHE8192,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&tls.ALPNExtension{
|
||||||
|
AlpnProtocols: []string{
|
||||||
|
"h3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&tls.StatusRequestExtension{},
|
||||||
|
&tls.FakeDelegatedCredentialsExtension{
|
||||||
|
SupportedSignatureAlgorithms: []tls.SignatureScheme{
|
||||||
|
tls.ECDSAWithP256AndSHA256,
|
||||||
|
tls.ECDSAWithP384AndSHA384,
|
||||||
|
tls.ECDSAWithP521AndSHA512,
|
||||||
|
tls.ECDSAWithSHA1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&tls.KeyShareExtension{
|
||||||
|
KeyShares: []tls.KeyShare{
|
||||||
|
{
|
||||||
|
Group: tls.X25519,
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// Group: tls.CurveP256,
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&tls.SupportedVersionsExtension{
|
||||||
|
Versions: []uint16{
|
||||||
|
tls.VersionTLS13,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&tls.SignatureAlgorithmsExtension{
|
||||||
|
SupportedSignatureAlgorithms: []tls.SignatureScheme{
|
||||||
|
tls.ECDSAWithP256AndSHA256,
|
||||||
|
tls.ECDSAWithP384AndSHA384,
|
||||||
|
tls.ECDSAWithP521AndSHA512,
|
||||||
|
tls.ECDSAWithSHA1,
|
||||||
|
tls.PSSWithSHA256,
|
||||||
|
tls.PSSWithSHA384,
|
||||||
|
tls.PSSWithSHA512,
|
||||||
|
tls.PKCS1WithSHA256,
|
||||||
|
tls.PKCS1WithSHA384,
|
||||||
|
tls.PKCS1WithSHA512,
|
||||||
|
tls.PKCS1WithSHA1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&tls.PSKKeyExchangeModesExtension{
|
||||||
|
Modes: []uint8{
|
||||||
|
tls.PskModeDHE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&tls.FakeRecordSizeLimitExtension{
|
||||||
|
Limit: 0x4001,
|
||||||
|
},
|
||||||
|
&tls.QUICTransportParametersExtension{
|
||||||
|
TransportParameters: tls.TransportParameters{
|
||||||
|
&tls.GREASE{
|
||||||
|
IdOverride: 0x35967c5b9c37e023,
|
||||||
|
ValueOverride: []byte{
|
||||||
|
0xfc, 0x97, 0xbb, 0x57, 0xb8, 0x02, 0x19, 0xcd,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tls.InitialMaxStreamsUni(103),
|
||||||
|
tls.InitialSourceConnectionID([]byte{}),
|
||||||
|
tls.InitialMaxStreamsBidi(100),
|
||||||
|
tls.InitialMaxData(15728640),
|
||||||
|
&tls.VersionInformation{
|
||||||
|
ChoosenVersion: tls.VERSION_1,
|
||||||
|
AvailableVersions: []uint32{
|
||||||
|
tls.VERSION_1,
|
||||||
|
tls.VERSION_GREASE,
|
||||||
|
},
|
||||||
|
LegacyID: true,
|
||||||
|
},
|
||||||
|
tls.MaxIdleTimeout(30000),
|
||||||
|
tls.MaxUDPPayloadSize(1472),
|
||||||
|
tls.MaxDatagramFrameSize(65536),
|
||||||
|
tls.InitialMaxStreamDataBidiLocal(6291456),
|
||||||
|
tls.InitialMaxStreamDataUni(6291456),
|
||||||
|
tls.InitialMaxStreamDataBidiRemote(6291456),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&tls.UtlsPaddingExtension{
|
||||||
|
GetPaddingLen: tls.BoringPaddingStyle,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConf := &tls.Config{
|
|
||||||
ServerName: "quic.tlsfingerprint.io",
|
|
||||||
// ServerName: "www.cloudflare.com",
|
|
||||||
// MinVersion: tls.VersionTLS13,
|
|
||||||
KeyLogWriter: keyLogWriter,
|
|
||||||
// NextProtos: []string{"h3"},
|
|
||||||
}
|
|
||||||
|
|
||||||
quicConf := &quic.Config{
|
|
||||||
Versions: []quic.VersionNumber{quic.Version1},
|
|
||||||
// EnableDatagrams: true,
|
|
||||||
SrcConnIDLength: 3, // <4 causes timeout
|
|
||||||
DestConnIDLength: 8,
|
|
||||||
InitPacketNumber: 0,
|
|
||||||
InitPacketNumberLength: quic.PacketNumberLen1, // currently only affects the initial packet number
|
|
||||||
// Versions: []quic.VersionNumber{quic.Version2},
|
|
||||||
}
|
|
||||||
|
|
||||||
roundTripper := &http3.RoundTripper{
|
|
||||||
TLSClientConfig: tlsConf,
|
|
||||||
QuicConfig: quicConf,
|
|
||||||
ClientHelloSpec: getCHS(),
|
|
||||||
}
|
|
||||||
defer roundTripper.Close()
|
|
||||||
|
|
||||||
hclient := &http.Client{
|
|
||||||
Transport: roundTripper,
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := "https://quic.tlsfingerprint.io/qfp/"
|
|
||||||
// addr := "https://www.cloudflare.com"
|
|
||||||
|
|
||||||
rsp, err := hclient.Get(addr)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
fmt.Printf("Got response for %s: %#v", addr, rsp)
|
|
||||||
|
|
||||||
body := &bytes.Buffer{}
|
|
||||||
_, err = io.Copy(body, rsp.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
fmt.Printf("Response Body: %s", body.Bytes())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,9 +88,6 @@ type RoundTripper struct {
|
||||||
newClient func(hostname string, tlsConf *tls.Config, opts *roundTripperOpts, conf *quic.Config, dialer dialFunc) (roundTripCloser, error) // so we can mock it in tests
|
newClient func(hostname string, tlsConf *tls.Config, opts *roundTripperOpts, conf *quic.Config, dialer dialFunc) (roundTripCloser, error) // so we can mock it in tests
|
||||||
clients map[string]*roundTripCloserWithCount
|
clients map[string]*roundTripCloserWithCount
|
||||||
transport *quic.Transport
|
transport *quic.Transport
|
||||||
|
|
||||||
// [UQUIC]
|
|
||||||
ClientHelloSpec *tls.ClientHelloSpec
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundTripOpt are options for the Transport.RoundTripOpt method.
|
// RoundTripOpt are options for the Transport.RoundTripOpt method.
|
||||||
|
@ -194,8 +191,7 @@ func (r *RoundTripper) getClient(hostname string, onlyCached bool) (rtc *roundTr
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
r.transport = &quic.Transport{
|
r.transport = &quic.Transport{
|
||||||
Conn: udpConn,
|
Conn: udpConn,
|
||||||
ClientHelloSpec: r.ClientHelloSpec,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dial = r.makeDialer()
|
dial = r.makeDialer()
|
||||||
|
|
192
http3/u_roundtrip.go
Normal file
192
http3/u_roundtrip.go
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
package http3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/quic-go/quic-go"
|
||||||
|
tls "github.com/refraction-networking/utls"
|
||||||
|
"golang.org/x/net/http/httpguts"
|
||||||
|
)
|
||||||
|
|
||||||
|
type URoundTripper struct {
|
||||||
|
*RoundTripper
|
||||||
|
|
||||||
|
quicSpec *quic.QUICSpec
|
||||||
|
uTransportOverride *quic.UTransport
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetURoundTripper(r *RoundTripper, QUICSpec *quic.QUICSpec, uTransport *quic.UTransport) *URoundTripper {
|
||||||
|
QUICSpec.UpdateConfig(r.QuicConfig)
|
||||||
|
|
||||||
|
return &URoundTripper{
|
||||||
|
RoundTripper: r,
|
||||||
|
quicSpec: QUICSpec,
|
||||||
|
uTransportOverride: uTransport,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundTripOpt is like RoundTrip, but takes options.
|
||||||
|
func (r *URoundTripper) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
|
||||||
|
if req.URL == nil {
|
||||||
|
closeRequestBody(req)
|
||||||
|
return nil, errors.New("http3: nil Request.URL")
|
||||||
|
}
|
||||||
|
if req.URL.Scheme != "https" {
|
||||||
|
closeRequestBody(req)
|
||||||
|
return nil, fmt.Errorf("http3: unsupported protocol scheme: %s", req.URL.Scheme)
|
||||||
|
}
|
||||||
|
if req.URL.Host == "" {
|
||||||
|
closeRequestBody(req)
|
||||||
|
return nil, errors.New("http3: no Host in request URL")
|
||||||
|
}
|
||||||
|
if req.Header == nil {
|
||||||
|
closeRequestBody(req)
|
||||||
|
return nil, errors.New("http3: nil Request.Header")
|
||||||
|
}
|
||||||
|
for k, vv := range req.Header {
|
||||||
|
if !httpguts.ValidHeaderFieldName(k) {
|
||||||
|
return nil, fmt.Errorf("http3: invalid http header field name %q", k)
|
||||||
|
}
|
||||||
|
for _, v := range vv {
|
||||||
|
if !httpguts.ValidHeaderFieldValue(v) {
|
||||||
|
return nil, fmt.Errorf("http3: invalid http header field value %q for key %v", v, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Method != "" && !validMethod(req.Method) {
|
||||||
|
closeRequestBody(req)
|
||||||
|
return nil, fmt.Errorf("http3: invalid method %q", req.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname := authorityAddr("https", hostnameFromRequest(req))
|
||||||
|
cl, isReused, err := r.getClient(hostname, opt.OnlyCachedConn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cl.useCount.Add(-1)
|
||||||
|
rsp, err := cl.RoundTripOpt(req, opt)
|
||||||
|
if err != nil {
|
||||||
|
r.removeClient(hostname)
|
||||||
|
if isReused {
|
||||||
|
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
||||||
|
return r.RoundTripOpt(req, opt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rsp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundTrip does a round trip.
|
||||||
|
func (r *URoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
return r.RoundTripOpt(req, RoundTripOpt{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *URoundTripper) getClient(hostname string, onlyCached bool) (rtc *roundTripCloserWithCount, isReused bool, err error) {
|
||||||
|
r.mutex.Lock()
|
||||||
|
defer r.mutex.Unlock()
|
||||||
|
|
||||||
|
if r.clients == nil {
|
||||||
|
r.clients = make(map[string]*roundTripCloserWithCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, ok := r.clients[hostname]
|
||||||
|
if !ok {
|
||||||
|
if onlyCached {
|
||||||
|
return nil, false, ErrNoCachedConn
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
newCl := newClient
|
||||||
|
if r.newClient != nil {
|
||||||
|
newCl = r.newClient
|
||||||
|
}
|
||||||
|
dial := r.Dial
|
||||||
|
if dial == nil {
|
||||||
|
if r.transport == nil && r.uTransportOverride == nil {
|
||||||
|
udpConn, err := net.ListenUDP("udp", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
r.uTransportOverride = &quic.UTransport{
|
||||||
|
Transport: &quic.Transport{
|
||||||
|
Conn: udpConn,
|
||||||
|
},
|
||||||
|
QUICSpec: r.quicSpec,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dial = r.makeDialer()
|
||||||
|
}
|
||||||
|
c, err := newCl(
|
||||||
|
hostname,
|
||||||
|
r.TLSClientConfig,
|
||||||
|
&roundTripperOpts{
|
||||||
|
EnableDatagram: r.EnableDatagrams,
|
||||||
|
DisableCompression: r.DisableCompression,
|
||||||
|
MaxHeaderBytes: r.MaxResponseHeaderBytes,
|
||||||
|
StreamHijacker: r.StreamHijacker,
|
||||||
|
UniStreamHijacker: r.UniStreamHijacker,
|
||||||
|
},
|
||||||
|
r.QuicConfig,
|
||||||
|
dial,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
client = &roundTripCloserWithCount{roundTripCloser: c}
|
||||||
|
r.clients[hostname] = client
|
||||||
|
} else if client.HandshakeComplete() {
|
||||||
|
isReused = true
|
||||||
|
}
|
||||||
|
client.useCount.Add(1)
|
||||||
|
return client, isReused, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *URoundTripper) Close() error {
|
||||||
|
r.mutex.Lock()
|
||||||
|
defer r.mutex.Unlock()
|
||||||
|
for _, client := range r.clients {
|
||||||
|
if err := client.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.clients = nil
|
||||||
|
if r.transport != nil {
|
||||||
|
if err := r.transport.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := r.transport.Conn.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.transport = nil
|
||||||
|
}
|
||||||
|
if r.uTransportOverride != nil {
|
||||||
|
if err := r.uTransportOverride.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := r.uTransportOverride.Conn.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.uTransportOverride = nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeDialer makes a QUIC dialer using r.udpConn.
|
||||||
|
func (r *URoundTripper) makeDialer() func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
|
return func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
|
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if r.uTransportOverride != nil {
|
||||||
|
return r.uTransportOverride.DialEarly(ctx, udpAddr, tlsCfg, cfg)
|
||||||
|
} else if r.transport == nil {
|
||||||
|
return nil, errors.New("http3: no QUIC transport available")
|
||||||
|
}
|
||||||
|
return r.transport.DialEarly(ctx, udpAddr, tlsCfg, cfg)
|
||||||
|
}
|
||||||
|
}
|
|
@ -333,12 +333,6 @@ type Config struct {
|
||||||
// Enable QUIC datagram support (RFC 9221).
|
// Enable QUIC datagram support (RFC 9221).
|
||||||
EnableDatagrams bool
|
EnableDatagrams bool
|
||||||
Tracer func(context.Context, logging.Perspective, ConnectionID) logging.ConnectionTracer
|
Tracer func(context.Context, logging.Perspective, ConnectionID) logging.ConnectionTracer
|
||||||
|
|
||||||
// TransportParameters override other transport parameters set by the Config.
|
|
||||||
SrcConnIDLength int // [UQUIC]
|
|
||||||
DestConnIDLength int // [UQUIC]
|
|
||||||
InitPacketNumber uint64 // [UQUIC]
|
|
||||||
InitPacketNumberLength PacketNumberLen // [UQUIC]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientHelloInfo struct {
|
type ClientHelloInfo struct {
|
||||||
|
|
|
@ -96,8 +96,6 @@ type sentPacketHandler struct {
|
||||||
|
|
||||||
tracer logging.ConnectionTracer
|
tracer logging.ConnectionTracer
|
||||||
logger utils.Logger
|
logger utils.Logger
|
||||||
|
|
||||||
initialPacketNumberLength protocol.PacketNumberLen // [UQUIC]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -138,12 +136,6 @@ func newSentPacketHandler(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetInitialPacketNumberLength(h SentPacketHandler, pnLen protocol.PacketNumberLen) {
|
|
||||||
if sph, ok := h.(*sentPacketHandler); ok {
|
|
||||||
sph.initialPacketNumberLength = pnLen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *sentPacketHandler) removeFromBytesInFlight(p *packet) {
|
func (h *sentPacketHandler) removeFromBytesInFlight(p *packet) {
|
||||||
if p.includedInBytesInFlight {
|
if p.includedInBytesInFlight {
|
||||||
if p.Length > h.bytesInFlight {
|
if p.Length > h.bytesInFlight {
|
||||||
|
@ -725,11 +717,6 @@ func (h *sentPacketHandler) PeekPacketNumber(encLevel protocol.EncryptionLevel)
|
||||||
pn := pnSpace.pns.Peek()
|
pn := pnSpace.pns.Peek()
|
||||||
// See section 17.1 of RFC 9000.
|
// See section 17.1 of RFC 9000.
|
||||||
|
|
||||||
// [UQUIC] This kinda breaks PN length mimicry.
|
|
||||||
if encLevel == protocol.EncryptionInitial && h.initialPacketNumberLength != 0 {
|
|
||||||
return pn, h.initialPacketNumberLength
|
|
||||||
}
|
|
||||||
|
|
||||||
return pn, protocol.GetPacketNumberLengthForHeader(pn, pnSpace.largestAcked)
|
return pn, protocol.GetPacketNumberLengthForHeader(pn, pnSpace.largestAcked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
23
internal/ackhandler/u_ackhandler.go
Normal file
23
internal/ackhandler/u_ackhandler.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package ackhandler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/quic-go/quic-go/internal/protocol"
|
||||||
|
"github.com/quic-go/quic-go/internal/utils"
|
||||||
|
"github.com/quic-go/quic-go/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
// [UQUIC]
|
||||||
|
func NewUAckHandler(
|
||||||
|
initialPacketNumber protocol.PacketNumber,
|
||||||
|
initialMaxDatagramSize protocol.ByteCount,
|
||||||
|
rttStats *utils.RTTStats,
|
||||||
|
clientAddressValidated bool,
|
||||||
|
pers protocol.Perspective,
|
||||||
|
tracer logging.ConnectionTracer,
|
||||||
|
logger utils.Logger,
|
||||||
|
) (SentPacketHandler, ReceivedPacketHandler) {
|
||||||
|
sph := newSentPacketHandler(initialPacketNumber, initialMaxDatagramSize, rttStats, clientAddressValidated, pers, tracer, logger)
|
||||||
|
return &uSentPacketHandler{
|
||||||
|
sentPacketHandler: sph,
|
||||||
|
}, newReceivedPacketHandler(sph, rttStats, logger)
|
||||||
|
}
|
30
internal/ackhandler/u_sent_packet_handler.go
Normal file
30
internal/ackhandler/u_sent_packet_handler.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package ackhandler
|
||||||
|
|
||||||
|
import "github.com/quic-go/quic-go/internal/protocol"
|
||||||
|
|
||||||
|
type uSentPacketHandler struct {
|
||||||
|
*sentPacketHandler
|
||||||
|
|
||||||
|
initialPacketNumberLength protocol.PacketNumberLen // [UQUIC]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *uSentPacketHandler) PeekPacketNumber(encLevel protocol.EncryptionLevel) (protocol.PacketNumber, protocol.PacketNumberLen) {
|
||||||
|
pnSpace := h.getPacketNumberSpace(encLevel)
|
||||||
|
pn := pnSpace.pns.Peek()
|
||||||
|
// See section 17.1 of RFC 9000.
|
||||||
|
|
||||||
|
// [UQUIC] Otherwise it kinda breaks PN length mimicry.
|
||||||
|
if encLevel == protocol.EncryptionInitial && h.initialPacketNumberLength != 0 {
|
||||||
|
return pn, h.initialPacketNumberLength
|
||||||
|
}
|
||||||
|
// [/UQUIC]
|
||||||
|
|
||||||
|
return pn, protocol.GetPacketNumberLengthForHeader(pn, pnSpace.largestAcked)
|
||||||
|
}
|
||||||
|
|
||||||
|
// [UQUIC]
|
||||||
|
func SetInitialPacketNumberLength(h SentPacketHandler, pnLen protocol.PacketNumberLen) {
|
||||||
|
if sph, ok := h.(*uSentPacketHandler); ok {
|
||||||
|
sph.initialPacketNumberLength = pnLen
|
||||||
|
}
|
||||||
|
}
|
|
@ -102,41 +102,6 @@ func NewCryptoSetupClient(
|
||||||
return cs
|
return cs
|
||||||
}
|
}
|
||||||
|
|
||||||
// [UQUIC]
|
|
||||||
// NewUCryptoSetupClient creates a new crypto setup for the client with UTLS
|
|
||||||
func NewUCryptoSetupClient(
|
|
||||||
connID protocol.ConnectionID,
|
|
||||||
tp *wire.TransportParameters,
|
|
||||||
tlsConf *tls.Config,
|
|
||||||
enable0RTT bool,
|
|
||||||
rttStats *utils.RTTStats,
|
|
||||||
tracer logging.ConnectionTracer,
|
|
||||||
logger utils.Logger,
|
|
||||||
version protocol.VersionNumber,
|
|
||||||
chs *tls.ClientHelloSpec,
|
|
||||||
) CryptoSetup {
|
|
||||||
cs := newCryptoSetup(
|
|
||||||
connID,
|
|
||||||
tp,
|
|
||||||
rttStats,
|
|
||||||
tracer,
|
|
||||||
logger,
|
|
||||||
protocol.PerspectiveClient,
|
|
||||||
version,
|
|
||||||
)
|
|
||||||
|
|
||||||
tlsConf = tlsConf.Clone()
|
|
||||||
tlsConf.MinVersion = tls.VersionTLS13
|
|
||||||
quicConf := &qtls.QUICConfig{TLSConfig: tlsConf}
|
|
||||||
qtls.SetupConfigForClient(quicConf, cs.marshalDataForSessionState, cs.handleDataFromSessionState)
|
|
||||||
cs.tlsConf = tlsConf
|
|
||||||
|
|
||||||
cs.conn = qtls.UQUICClient(quicConf, chs)
|
|
||||||
// cs.conn.SetTransportParameters(cs.ourParams.Marshal(protocol.PerspectiveClient)) // [UQUIC] doesn't require this
|
|
||||||
|
|
||||||
return cs
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCryptoSetupServer creates a new crypto setup for the server
|
// NewCryptoSetupServer creates a new crypto setup for the server
|
||||||
func NewCryptoSetupServer(
|
func NewCryptoSetupServer(
|
||||||
connID protocol.ConnectionID,
|
connID protocol.ConnectionID,
|
||||||
|
@ -281,6 +246,7 @@ func (h *cryptoSetup) handleEvent(ev qtls.QUICEvent) (done bool, err error) {
|
||||||
return false, h.handleTransportParameters(ev.Data)
|
return false, h.handleTransportParameters(ev.Data)
|
||||||
case qtls.QUICTransportParametersRequired:
|
case qtls.QUICTransportParametersRequired:
|
||||||
h.conn.SetTransportParameters(h.ourParams.Marshal(h.perspective))
|
h.conn.SetTransportParameters(h.ourParams.Marshal(h.perspective))
|
||||||
|
// [UQUIC] doesn't expect this and may fail
|
||||||
return false, nil
|
return false, nil
|
||||||
case qtls.QUICRejectedEarlyData:
|
case qtls.QUICRejectedEarlyData:
|
||||||
h.rejected0RTT()
|
h.rejected0RTT()
|
||||||
|
|
45
internal/handshake/u_crypto_setup.go
Normal file
45
internal/handshake/u_crypto_setup.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package handshake
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/quic-go/quic-go/internal/protocol"
|
||||||
|
"github.com/quic-go/quic-go/internal/qtls"
|
||||||
|
"github.com/quic-go/quic-go/internal/utils"
|
||||||
|
"github.com/quic-go/quic-go/internal/wire"
|
||||||
|
"github.com/quic-go/quic-go/logging"
|
||||||
|
tls "github.com/refraction-networking/utls"
|
||||||
|
)
|
||||||
|
|
||||||
|
// [UQUIC]
|
||||||
|
// NewUCryptoSetupClient creates a new crypto setup for the client with UTLS
|
||||||
|
func NewUCryptoSetupClient(
|
||||||
|
connID protocol.ConnectionID,
|
||||||
|
tp *wire.TransportParameters,
|
||||||
|
tlsConf *tls.Config,
|
||||||
|
enable0RTT bool,
|
||||||
|
rttStats *utils.RTTStats,
|
||||||
|
tracer logging.ConnectionTracer,
|
||||||
|
logger utils.Logger,
|
||||||
|
version protocol.VersionNumber,
|
||||||
|
chs *tls.ClientHelloSpec,
|
||||||
|
) CryptoSetup {
|
||||||
|
cs := newCryptoSetup(
|
||||||
|
connID,
|
||||||
|
tp,
|
||||||
|
rttStats,
|
||||||
|
tracer,
|
||||||
|
logger,
|
||||||
|
protocol.PerspectiveClient,
|
||||||
|
version,
|
||||||
|
)
|
||||||
|
|
||||||
|
tlsConf = tlsConf.Clone()
|
||||||
|
tlsConf.MinVersion = tls.VersionTLS13
|
||||||
|
quicConf := &qtls.QUICConfig{TLSConfig: tlsConf}
|
||||||
|
qtls.SetupConfigForClient(quicConf, cs.marshalDataForSessionState, cs.handleDataFromSessionState)
|
||||||
|
cs.tlsConf = tlsConf
|
||||||
|
|
||||||
|
cs.conn = qtls.UQUICClient(quicConf, chs)
|
||||||
|
// cs.conn.SetTransportParameters(cs.ourParams.Marshal(protocol.PerspectiveClient)) // [UQUIC] doesn't require this
|
||||||
|
|
||||||
|
return cs
|
||||||
|
}
|
|
@ -68,11 +68,6 @@ func GenerateConnectionIDForInitial() (ConnectionID, error) {
|
||||||
return GenerateConnectionID(l)
|
return GenerateConnectionID(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
// [UQUIC]
|
|
||||||
func GenerateConnectionIDForInitialWithLen(l int) (ConnectionID, error) {
|
|
||||||
return GenerateConnectionID(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadConnectionID reads a connection ID of length len from the given io.Reader.
|
// ReadConnectionID reads a connection ID of length len from the given io.Reader.
|
||||||
// It returns io.EOF if there are not enough bytes to read.
|
// It returns io.EOF if there are not enough bytes to read.
|
||||||
func ReadConnectionID(r io.Reader, l int) (ConnectionID, error) {
|
func ReadConnectionID(r io.Reader, l int) (ConnectionID, error) {
|
||||||
|
|
16
internal/protocol/u_connection_id.go
Normal file
16
internal/protocol/u_connection_id.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
// [UQUIC]
|
||||||
|
func GenerateConnectionIDForInitialWithLen(l int) (ConnectionID, error) {
|
||||||
|
return GenerateConnectionID(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpEmptyConnectionIDGenerator struct{}
|
||||||
|
|
||||||
|
func (g *ExpEmptyConnectionIDGenerator) GenerateConnectionID() (ConnectionID, error) {
|
||||||
|
return GenerateConnectionID(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *ExpEmptyConnectionIDGenerator) ConnectionIDLen() int {
|
||||||
|
return 0
|
||||||
|
}
|
113
packet_packer.go
113
packet_packer.go
|
@ -332,11 +332,6 @@ func (p *packetPacker) PackCoalescedPacket(onlyAck bool, maxPacketSize protocol.
|
||||||
if initialPayload.length > 0 {
|
if initialPayload.length > 0 {
|
||||||
size += p.longHeaderPacketLength(initialHdr, initialPayload, v) + protocol.ByteCount(initialSealer.Overhead())
|
size += p.longHeaderPacketLength(initialHdr, initialPayload, v) + protocol.ByteCount(initialSealer.Overhead())
|
||||||
}
|
}
|
||||||
|
|
||||||
// // [UQUIC]
|
|
||||||
// if len(initialPayload.frames) > 0 {
|
|
||||||
// fmt.Printf("onlyAck: %t, PackCoalescedPacket: %v\n", onlyAck, initialPayload.frames[0].Frame)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a Handshake packet.
|
// Add a Handshake packet.
|
||||||
|
@ -401,24 +396,12 @@ func (p *packetPacker) PackCoalescedPacket(onlyAck bool, maxPacketSize protocol.
|
||||||
longHdrPackets: make([]*longHeaderPacket, 0, 3),
|
longHdrPackets: make([]*longHeaderPacket, 0, 3),
|
||||||
}
|
}
|
||||||
if initialPayload.length > 0 {
|
if initialPayload.length > 0 {
|
||||||
if onlyAck || len(initialPayload.frames) == 0 {
|
padding := p.initialPaddingLen(initialPayload.frames, size, maxPacketSize)
|
||||||
// padding := p.initialPaddingLen(initialPayload.frames, size, maxPacketSize)
|
cont, err := p.appendLongHeaderPacket(buffer, initialHdr, initialPayload, padding, protocol.EncryptionInitial, initialSealer, v)
|
||||||
// cont, err := p.appendLongHeaderPacket(buffer, initialHdr, initialPayload, padding, protocol.EncryptionInitial, initialSealer, v)
|
if err != nil {
|
||||||
// if err != nil {
|
return nil, err
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// packet.longHdrPackets = append(packet.longHdrPackets, cont)
|
|
||||||
return nil, nil // [UQUIC] not to send the ACK frame for Initial
|
|
||||||
} else { // [UQUIC]
|
|
||||||
cont, err := p.appendLongHeaderPacketExternalPadding(buffer, initialHdr, initialPayload, protocol.EncryptionInitial, initialSealer, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// fmt.Printf("!onlyAck buffer: %v\n", buffer.Data)
|
|
||||||
|
|
||||||
packet.longHdrPackets = append(packet.longHdrPackets, cont)
|
|
||||||
}
|
}
|
||||||
|
packet.longHdrPackets = append(packet.longHdrPackets, cont)
|
||||||
}
|
}
|
||||||
if handshakePayload.length > 0 {
|
if handshakePayload.length > 0 {
|
||||||
cont, err := p.appendLongHeaderPacket(buffer, handshakeHdr, handshakePayload, 0, protocol.EncryptionHandshake, handshakeSealer, v)
|
cont, err := p.appendLongHeaderPacket(buffer, handshakeHdr, handshakePayload, 0, protocol.EncryptionHandshake, handshakeSealer, v)
|
||||||
|
@ -688,7 +671,6 @@ func (p *packetPacker) MaybePackProbePacket(encLevel protocol.EncryptionLevel, m
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
hdr, pl = p.maybeGetCryptoPacket(maxPacketSize-protocol.ByteCount(sealer.Overhead()), protocol.EncryptionInitial, false, true, v)
|
hdr, pl = p.maybeGetCryptoPacket(maxPacketSize-protocol.ByteCount(sealer.Overhead()), protocol.EncryptionInitial, false, true, v)
|
||||||
fmt.Printf("MaybePackProbePacket: %x\n", pl.frames[0])
|
|
||||||
case protocol.EncryptionHandshake:
|
case protocol.EncryptionHandshake:
|
||||||
var err error
|
var err error
|
||||||
sealer, err = p.cryptoSetup.GetHandshakeSealer()
|
sealer, err = p.cryptoSetup.GetHandshakeSealer()
|
||||||
|
@ -768,10 +750,6 @@ func (p *packetPacker) appendLongHeaderPacket(buffer *packetBuffer, header *wire
|
||||||
}
|
}
|
||||||
paddingLen += padding
|
paddingLen += padding
|
||||||
|
|
||||||
if encLevel == protocol.EncryptionInitial {
|
|
||||||
paddingLen = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
header.Length = pnLen + protocol.ByteCount(sealer.Overhead()) + pl.length + paddingLen
|
header.Length = pnLen + protocol.ByteCount(sealer.Overhead()) + pl.length + paddingLen
|
||||||
|
|
||||||
startLen := len(buffer.Data)
|
startLen := len(buffer.Data)
|
||||||
|
@ -800,49 +778,6 @@ func (p *packetPacker) appendLongHeaderPacket(buffer *packetBuffer, header *wire
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// [UQUIC]
|
|
||||||
func (p *packetPacker) appendLongHeaderPacketExternalPadding(buffer *packetBuffer, header *wire.ExtendedHeader, pl payload, encLevel protocol.EncryptionLevel, sealer sealer, v protocol.VersionNumber) (*longHeaderPacket, error) {
|
|
||||||
pnLen := protocol.ByteCount(header.PacketNumberLen)
|
|
||||||
header.Length = pnLen + protocol.ByteCount(sealer.Overhead()) + pl.length
|
|
||||||
|
|
||||||
startLen := len(buffer.Data)
|
|
||||||
raw := buffer.Data[startLen:] // [UQUIC] raw is a sub-slice of buffer.Data, whose len < size
|
|
||||||
raw, err := header.Append(raw, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Pre-Payload: %x\n", raw)
|
|
||||||
|
|
||||||
payloadOffset := protocol.ByteCount(len(raw))
|
|
||||||
raw, err = p.appendCustomInitialPacketPayload(raw, pl, 0, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Pre-Encryption: %x\n", raw)
|
|
||||||
|
|
||||||
raw = p.encryptPacket(raw, sealer, header.PacketNumber, payloadOffset, pnLen)
|
|
||||||
buffer.Data = buffer.Data[:len(buffer.Data)+len(raw)]
|
|
||||||
|
|
||||||
fmt.Printf("Post-Encryption: %x\n", raw)
|
|
||||||
|
|
||||||
// [UQUIC]
|
|
||||||
// append zero to buffer.Data until 1200 bytes
|
|
||||||
buffer.Data = append(buffer.Data, make([]byte, 1357-len(buffer.Data))...)
|
|
||||||
|
|
||||||
if pn := p.pnManager.PopPacketNumber(encLevel); pn != header.PacketNumber {
|
|
||||||
return nil, fmt.Errorf("packetPacker BUG: Peeked and Popped packet numbers do not match: expected %d, got %d", pn, header.PacketNumber)
|
|
||||||
}
|
|
||||||
return &longHeaderPacket{
|
|
||||||
header: header,
|
|
||||||
ack: pl.ack,
|
|
||||||
frames: pl.frames,
|
|
||||||
streamFrames: pl.streamFrames,
|
|
||||||
length: protocol.ByteCount(len(raw)),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *packetPacker) appendShortHeaderPacket(
|
func (p *packetPacker) appendShortHeaderPacket(
|
||||||
buffer *packetBuffer,
|
buffer *packetBuffer,
|
||||||
connID protocol.ConnectionID,
|
connID protocol.ConnectionID,
|
||||||
|
@ -930,44 +865,6 @@ func (p *packetPacker) appendPacketPayload(raw []byte, pl payload, paddingLen pr
|
||||||
return raw, nil
|
return raw, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *packetPacker) appendCustomInitialPacketPayload(raw []byte, pl payload, paddingLen protocol.ByteCount, v protocol.VersionNumber) ([]byte, error) {
|
|
||||||
payloadOffset := len(raw)
|
|
||||||
|
|
||||||
// [UQUIC] ignores the default ACK/PADDING frame and uses its own frames
|
|
||||||
// if pl.ack != nil {
|
|
||||||
// var err error
|
|
||||||
// raw, err = pl.ack.Append(raw, v)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if paddingLen > 0 {
|
|
||||||
// raw = append(raw, make([]byte, paddingLen)...)
|
|
||||||
// }
|
|
||||||
|
|
||||||
for _, f := range pl.frames {
|
|
||||||
var err error
|
|
||||||
raw, err = f.Frame.Append(raw, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fmt.Printf("UQUIC: appending frame %v\n", f)
|
|
||||||
}
|
|
||||||
for _, f := range pl.streamFrames {
|
|
||||||
var err error
|
|
||||||
raw, err = f.Frame.Append(raw, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fmt.Printf("UQUIC: appending stream frame %v\n", f)
|
|
||||||
}
|
|
||||||
|
|
||||||
if payloadSize := protocol.ByteCount(len(raw)-payloadOffset) - paddingLen; payloadSize != pl.length {
|
|
||||||
return nil, fmt.Errorf("PacketPacker BUG: payload size inconsistent (expected %d, got %d bytes)", pl.length, payloadSize)
|
|
||||||
}
|
|
||||||
return raw, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *packetPacker) encryptPacket(raw []byte, sealer sealer, pn protocol.PacketNumber, payloadOffset, pnLen protocol.ByteCount) []byte {
|
func (p *packetPacker) encryptPacket(raw []byte, sealer sealer, pn protocol.PacketNumber, payloadOffset, pnLen protocol.ByteCount) []byte {
|
||||||
_ = sealer.Seal(raw[payloadOffset:payloadOffset], raw[payloadOffset:], pn, raw[:payloadOffset])
|
_ = sealer.Seal(raw[payloadOffset:payloadOffset], raw[payloadOffset:], pn, raw[:payloadOffset])
|
||||||
raw = raw[:len(raw)+sealer.Overhead()]
|
raw = raw[:len(raw)+sealer.Overhead()]
|
||||||
|
|
20
transport.go
20
transport.go
|
@ -87,8 +87,6 @@ type Transport struct {
|
||||||
isSingleUse bool // was created for a single server or client, i.e. by calling quic.Listen or quic.Dial
|
isSingleUse bool // was created for a single server or client, i.e. by calling quic.Listen or quic.Dial
|
||||||
|
|
||||||
logger utils.Logger
|
logger utils.Logger
|
||||||
|
|
||||||
ClientHelloSpec *tls.ClientHelloSpec // [UQUIC]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen starts listening for incoming QUIC connections.
|
// Listen starts listening for incoming QUIC connections.
|
||||||
|
@ -156,12 +154,6 @@ func (t *Transport) Dial(ctx context.Context, addr net.Addr, tlsConf *tls.Config
|
||||||
}
|
}
|
||||||
conf = populateConfig(conf)
|
conf = populateConfig(conf)
|
||||||
|
|
||||||
// [UQUIC]
|
|
||||||
if conf.SrcConnIDLength != 0 {
|
|
||||||
t.ConnectionIDGenerator = &protocol.DefaultConnectionIDGenerator{ConnLen: conf.SrcConnIDLength}
|
|
||||||
}
|
|
||||||
// [/UQUIC]
|
|
||||||
|
|
||||||
if err := t.init(t.isSingleUse); err != nil {
|
if err := t.init(t.isSingleUse); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -172,9 +164,6 @@ func (t *Transport) Dial(ctx context.Context, addr net.Addr, tlsConf *tls.Config
|
||||||
tlsConf = tlsConf.Clone()
|
tlsConf = tlsConf.Clone()
|
||||||
tlsConf.MinVersion = tls.VersionTLS13
|
tlsConf.MinVersion = tls.VersionTLS13
|
||||||
|
|
||||||
if t.ClientHelloSpec != nil { // [UQUIC]
|
|
||||||
return dialWithCHS(ctx, newSendConn(t.conn, addr), t.connIDGenerator, t.handlerMap, tlsConf, conf, onClose, false, t.ClientHelloSpec)
|
|
||||||
}
|
|
||||||
return dial(ctx, newSendConn(t.conn, addr), t.connIDGenerator, t.handlerMap, tlsConf, conf, onClose, false)
|
return dial(ctx, newSendConn(t.conn, addr), t.connIDGenerator, t.handlerMap, tlsConf, conf, onClose, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,12 +174,6 @@ func (t *Transport) DialEarly(ctx context.Context, addr net.Addr, tlsConf *tls.C
|
||||||
}
|
}
|
||||||
conf = populateConfig(conf)
|
conf = populateConfig(conf)
|
||||||
|
|
||||||
// [UQUIC]
|
|
||||||
if conf.SrcConnIDLength != 0 {
|
|
||||||
t.ConnectionIDGenerator = &protocol.DefaultConnectionIDGenerator{ConnLen: conf.SrcConnIDLength}
|
|
||||||
}
|
|
||||||
// [/UQUIC]
|
|
||||||
|
|
||||||
if err := t.init(t.isSingleUse); err != nil {
|
if err := t.init(t.isSingleUse); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -201,9 +184,6 @@ func (t *Transport) DialEarly(ctx context.Context, addr net.Addr, tlsConf *tls.C
|
||||||
tlsConf = tlsConf.Clone()
|
tlsConf = tlsConf.Clone()
|
||||||
tlsConf.MinVersion = tls.VersionTLS13
|
tlsConf.MinVersion = tls.VersionTLS13
|
||||||
|
|
||||||
if t.ClientHelloSpec != nil { // [UQUIC]
|
|
||||||
return dialWithCHS(ctx, newSendConn(t.conn, addr), t.connIDGenerator, t.handlerMap, tlsConf, conf, onClose, false, t.ClientHelloSpec)
|
|
||||||
}
|
|
||||||
return dial(ctx, newSendConn(t.conn, addr), t.connIDGenerator, t.handlerMap, tlsConf, conf, onClose, true)
|
return dial(ctx, newSendConn(t.conn, addr), t.connIDGenerator, t.handlerMap, tlsConf, conf, onClose, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
150
u_client.go
Normal file
150
u_client.go
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
package quic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/quic-go/quic-go/internal/protocol"
|
||||||
|
tls "github.com/refraction-networking/utls"
|
||||||
|
)
|
||||||
|
|
||||||
|
type uClient struct {
|
||||||
|
*client
|
||||||
|
uSpec *QUICSpec // [UQUIC]
|
||||||
|
}
|
||||||
|
|
||||||
|
func udial(
|
||||||
|
ctx context.Context,
|
||||||
|
conn sendConn,
|
||||||
|
connIDGenerator ConnectionIDGenerator,
|
||||||
|
packetHandlers packetHandlerManager,
|
||||||
|
tlsConf *tls.Config,
|
||||||
|
config *Config,
|
||||||
|
onClose func(),
|
||||||
|
use0RTT bool,
|
||||||
|
uSpec *QUICSpec, // [UQUIC]
|
||||||
|
) (quicConn, error) {
|
||||||
|
c, err := newClient(conn, connIDGenerator, config, tlsConf, onClose, use0RTT)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.packetHandlers = packetHandlers
|
||||||
|
|
||||||
|
// [UQUIC]
|
||||||
|
if uSpec.InitialPacketSpec.DestConnIDLength > 0 {
|
||||||
|
destConnID, err := generateConnectionIDForInitialWithLength(uSpec.InitialPacketSpec.DestConnIDLength)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.destConnID = destConnID
|
||||||
|
}
|
||||||
|
c.initialPacketNumber = protocol.PacketNumber(uSpec.InitialPacketSpec.InitPacketNumber)
|
||||||
|
// [/UQUIC]
|
||||||
|
|
||||||
|
c.tracingID = nextConnTracingID()
|
||||||
|
if c.config.Tracer != nil {
|
||||||
|
c.tracer = c.config.Tracer(context.WithValue(ctx, ConnectionTracingKey, c.tracingID), protocol.PerspectiveClient, c.destConnID)
|
||||||
|
}
|
||||||
|
if c.tracer != nil {
|
||||||
|
c.tracer.StartedConnection(c.sendConn.LocalAddr(), c.sendConn.RemoteAddr(), c.srcConnID, c.destConnID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// [UQUIC]
|
||||||
|
uc := &uClient{
|
||||||
|
client: c,
|
||||||
|
uSpec: uSpec,
|
||||||
|
}
|
||||||
|
// [/UQUIC]
|
||||||
|
|
||||||
|
if err := uc.dial(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return uc.conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *uClient) dial(ctx context.Context) error {
|
||||||
|
c.logger.Infof("Starting new uQUIC connection to %s (%s -> %s), source connection ID %s, destination connection ID %s, version %s", c.tlsConf.ServerName, c.sendConn.LocalAddr(), c.sendConn.RemoteAddr(), c.srcConnID, c.destConnID, c.version)
|
||||||
|
|
||||||
|
// [UQUIC]
|
||||||
|
if c.uSpec.ClientHelloSpec == nil {
|
||||||
|
c.conn = newClientConnection(
|
||||||
|
c.sendConn,
|
||||||
|
c.packetHandlers,
|
||||||
|
c.destConnID,
|
||||||
|
c.srcConnID,
|
||||||
|
c.connIDGenerator,
|
||||||
|
c.config,
|
||||||
|
c.tlsConf,
|
||||||
|
c.initialPacketNumber,
|
||||||
|
c.use0RTT,
|
||||||
|
c.hasNegotiatedVersion,
|
||||||
|
c.tracer,
|
||||||
|
c.tracingID,
|
||||||
|
c.logger,
|
||||||
|
c.version,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// [UQUIC]: use custom version of the connection
|
||||||
|
c.conn = newUClientConnection(
|
||||||
|
c.sendConn,
|
||||||
|
c.packetHandlers,
|
||||||
|
c.destConnID,
|
||||||
|
c.srcConnID,
|
||||||
|
c.connIDGenerator,
|
||||||
|
c.config,
|
||||||
|
c.tlsConf,
|
||||||
|
c.initialPacketNumber,
|
||||||
|
c.use0RTT,
|
||||||
|
c.hasNegotiatedVersion,
|
||||||
|
c.tracer,
|
||||||
|
c.tracingID,
|
||||||
|
c.logger,
|
||||||
|
c.version,
|
||||||
|
c.uSpec,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// [/UQUIC]
|
||||||
|
|
||||||
|
c.packetHandlers.Add(c.srcConnID, c.conn)
|
||||||
|
|
||||||
|
errorChan := make(chan error, 1)
|
||||||
|
recreateChan := make(chan errCloseForRecreating)
|
||||||
|
go func() {
|
||||||
|
err := c.conn.run()
|
||||||
|
var recreateErr *errCloseForRecreating
|
||||||
|
if errors.As(err, &recreateErr) {
|
||||||
|
recreateChan <- *recreateErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c.onClose != nil {
|
||||||
|
c.onClose()
|
||||||
|
}
|
||||||
|
errorChan <- err // returns as soon as the connection is closed
|
||||||
|
}()
|
||||||
|
|
||||||
|
// only set when we're using 0-RTT
|
||||||
|
// Otherwise, earlyConnChan will be nil. Receiving from a nil chan blocks forever.
|
||||||
|
var earlyConnChan <-chan struct{}
|
||||||
|
if c.use0RTT {
|
||||||
|
earlyConnChan = c.conn.earlyConnReady()
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
c.conn.shutdown()
|
||||||
|
return ctx.Err()
|
||||||
|
case err := <-errorChan:
|
||||||
|
return err
|
||||||
|
case recreateErr := <-recreateChan:
|
||||||
|
c.initialPacketNumber = recreateErr.nextPacketNumber
|
||||||
|
c.version = recreateErr.nextVersion
|
||||||
|
c.hasNegotiatedVersion = true
|
||||||
|
return c.dial(ctx)
|
||||||
|
case <-earlyConnChan:
|
||||||
|
// ready to send 0-RTT data
|
||||||
|
return nil
|
||||||
|
case <-c.conn.HandshakeComplete():
|
||||||
|
// handshake successfully completed
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
6
u_conn_id_manager.go
Normal file
6
u_conn_id_manager.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package quic
|
||||||
|
|
||||||
|
// [UQUIC]
|
||||||
|
func (h *connIDManager) SetConnectionIDLimit(limit uint64) {
|
||||||
|
h.connectionIDLimit = limit
|
||||||
|
}
|
170
u_connection.go
Normal file
170
u_connection.go
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
package quic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/quic-go/quic-go/internal/ackhandler"
|
||||||
|
"github.com/quic-go/quic-go/internal/handshake"
|
||||||
|
"github.com/quic-go/quic-go/internal/protocol"
|
||||||
|
"github.com/quic-go/quic-go/internal/utils"
|
||||||
|
"github.com/quic-go/quic-go/internal/wire"
|
||||||
|
"github.com/quic-go/quic-go/logging"
|
||||||
|
tls "github.com/refraction-networking/utls"
|
||||||
|
)
|
||||||
|
|
||||||
|
// [UQUIC]
|
||||||
|
var newUClientConnection = func(
|
||||||
|
conn sendConn,
|
||||||
|
runner connRunner,
|
||||||
|
destConnID protocol.ConnectionID,
|
||||||
|
srcConnID protocol.ConnectionID,
|
||||||
|
connIDGenerator ConnectionIDGenerator,
|
||||||
|
conf *Config,
|
||||||
|
tlsConf *tls.Config,
|
||||||
|
initialPacketNumber protocol.PacketNumber,
|
||||||
|
enable0RTT bool,
|
||||||
|
hasNegotiatedVersion bool,
|
||||||
|
tracer logging.ConnectionTracer,
|
||||||
|
tracingID uint64,
|
||||||
|
logger utils.Logger,
|
||||||
|
v protocol.VersionNumber,
|
||||||
|
// chs *tls.ClientHelloSpec,
|
||||||
|
// initPktNbrLen PacketNumberLen,
|
||||||
|
// qfs QUICFrames,
|
||||||
|
// udpDatagramMinSize int,
|
||||||
|
uSpec *QUICSpec, // [UQUIC]
|
||||||
|
) quicConn {
|
||||||
|
s := &connection{
|
||||||
|
conn: conn,
|
||||||
|
config: conf,
|
||||||
|
origDestConnID: destConnID,
|
||||||
|
handshakeDestConnID: destConnID,
|
||||||
|
srcConnIDLen: srcConnID.Len(),
|
||||||
|
perspective: protocol.PerspectiveClient,
|
||||||
|
logID: destConnID.String(),
|
||||||
|
logger: logger,
|
||||||
|
tracer: tracer,
|
||||||
|
versionNegotiated: hasNegotiatedVersion,
|
||||||
|
version: v,
|
||||||
|
}
|
||||||
|
s.connIDManager = newConnIDManager(
|
||||||
|
destConnID,
|
||||||
|
func(token protocol.StatelessResetToken) { runner.AddResetToken(token, s) },
|
||||||
|
runner.RemoveResetToken,
|
||||||
|
s.queueControlFrame,
|
||||||
|
)
|
||||||
|
|
||||||
|
s.connIDGenerator = newConnIDGenerator(
|
||||||
|
srcConnID,
|
||||||
|
nil,
|
||||||
|
func(connID protocol.ConnectionID) { runner.Add(connID, s) },
|
||||||
|
runner.GetStatelessResetToken,
|
||||||
|
runner.Remove,
|
||||||
|
runner.Retire,
|
||||||
|
runner.ReplaceWithClosed,
|
||||||
|
s.queueControlFrame,
|
||||||
|
connIDGenerator,
|
||||||
|
)
|
||||||
|
s.preSetup()
|
||||||
|
s.ctx, s.ctxCancel = context.WithCancelCause(context.WithValue(context.Background(), ConnectionTracingKey, tracingID))
|
||||||
|
s.sentPacketHandler, s.receivedPacketHandler = ackhandler.NewUAckHandler( // [UQUIC]
|
||||||
|
initialPacketNumber,
|
||||||
|
getMaxPacketSize(s.conn.RemoteAddr()),
|
||||||
|
s.rttStats,
|
||||||
|
false, /* has no effect */
|
||||||
|
s.perspective,
|
||||||
|
s.tracer,
|
||||||
|
s.logger,
|
||||||
|
)
|
||||||
|
// [UQUIC]
|
||||||
|
if uSpec.InitialPacketSpec.InitPacketNumberLength != 0 {
|
||||||
|
ackhandler.SetInitialPacketNumberLength(s.sentPacketHandler, uSpec.InitialPacketSpec.InitPacketNumberLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.mtuDiscoverer = newMTUDiscoverer(s.rttStats, getMaxPacketSize(s.conn.RemoteAddr()), s.sentPacketHandler.SetMaxDatagramSize)
|
||||||
|
oneRTTStream := newCryptoStream()
|
||||||
|
|
||||||
|
var params *wire.TransportParameters
|
||||||
|
|
||||||
|
if uSpec.ClientHelloSpec != nil {
|
||||||
|
// iterate over all Extensions to set the TransportParameters
|
||||||
|
var tpSet bool
|
||||||
|
FOR_EACH_TLS_EXTENSION:
|
||||||
|
for _, ext := range uSpec.ClientHelloSpec.Extensions {
|
||||||
|
switch ext := ext.(type) {
|
||||||
|
case *tls.QUICTransportParametersExtension:
|
||||||
|
params = &wire.TransportParameters{
|
||||||
|
InitialSourceConnectionID: srcConnID,
|
||||||
|
}
|
||||||
|
params.PopulateFromUQUIC(ext.TransportParameters)
|
||||||
|
s.connIDManager.SetConnectionIDLimit(params.ActiveConnectionIDLimit)
|
||||||
|
tpSet = true
|
||||||
|
break FOR_EACH_TLS_EXTENSION
|
||||||
|
default:
|
||||||
|
continue FOR_EACH_TLS_EXTENSION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !tpSet {
|
||||||
|
panic("applied ClientHelloSpec must contain a QUICTransportParametersExtension to proceed")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// use default TransportParameters
|
||||||
|
params = &wire.TransportParameters{
|
||||||
|
InitialMaxStreamDataBidiRemote: protocol.ByteCount(s.config.InitialStreamReceiveWindow),
|
||||||
|
InitialMaxStreamDataBidiLocal: protocol.ByteCount(s.config.InitialStreamReceiveWindow),
|
||||||
|
InitialMaxStreamDataUni: protocol.ByteCount(s.config.InitialStreamReceiveWindow),
|
||||||
|
InitialMaxData: protocol.ByteCount(s.config.InitialConnectionReceiveWindow),
|
||||||
|
MaxIdleTimeout: s.config.MaxIdleTimeout,
|
||||||
|
MaxBidiStreamNum: protocol.StreamNum(s.config.MaxIncomingStreams),
|
||||||
|
MaxUniStreamNum: protocol.StreamNum(s.config.MaxIncomingUniStreams),
|
||||||
|
MaxAckDelay: protocol.MaxAckDelayInclGranularity,
|
||||||
|
AckDelayExponent: protocol.AckDelayExponent,
|
||||||
|
DisableActiveMigration: true,
|
||||||
|
// For interoperability with quic-go versions before May 2023, this value must be set to a value
|
||||||
|
// different from protocol.DefaultActiveConnectionIDLimit.
|
||||||
|
// If set to the default value, it will be omitted from the transport parameters, which will make
|
||||||
|
// old quic-go versions interpret it as 0, instead of the default value of 2.
|
||||||
|
// See https://github.com/quic-go/quic-go/pull/3806.
|
||||||
|
ActiveConnectionIDLimit: protocol.MaxActiveConnectionIDs,
|
||||||
|
InitialSourceConnectionID: srcConnID,
|
||||||
|
}
|
||||||
|
if s.config.EnableDatagrams {
|
||||||
|
params.MaxDatagramFrameSize = protocol.MaxDatagramFrameSize
|
||||||
|
} else {
|
||||||
|
params.MaxDatagramFrameSize = protocol.InvalidByteCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.tracer != nil {
|
||||||
|
s.tracer.SentTransportParameters(params)
|
||||||
|
}
|
||||||
|
cs := handshake.NewUCryptoSetupClient(
|
||||||
|
destConnID,
|
||||||
|
params,
|
||||||
|
tlsConf,
|
||||||
|
enable0RTT,
|
||||||
|
s.rttStats,
|
||||||
|
tracer,
|
||||||
|
logger,
|
||||||
|
s.version,
|
||||||
|
uSpec.ClientHelloSpec,
|
||||||
|
)
|
||||||
|
s.cryptoStreamHandler = cs
|
||||||
|
s.cryptoStreamManager = newCryptoStreamManager(cs, s.initialStream, s.handshakeStream, oneRTTStream)
|
||||||
|
s.unpacker = newPacketUnpacker(cs, s.srcConnIDLen)
|
||||||
|
s.packer = newUPacketPacker(
|
||||||
|
newPacketPacker(srcConnID, s.connIDManager.Get, s.initialStream, s.handshakeStream, s.sentPacketHandler, s.retransmissionQueue, cs, s.framer, s.receivedPacketHandler, s.datagramQueue, s.perspective),
|
||||||
|
uSpec,
|
||||||
|
)
|
||||||
|
if len(tlsConf.ServerName) > 0 {
|
||||||
|
s.tokenStoreKey = tlsConf.ServerName
|
||||||
|
} else {
|
||||||
|
s.tokenStoreKey = conn.RemoteAddr().String()
|
||||||
|
}
|
||||||
|
if s.config.TokenStore != nil {
|
||||||
|
if token := s.config.TokenStore.Pop(s.tokenStoreKey); token != nil {
|
||||||
|
s.packer.SetToken(token.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
205
u_initial_packet_spec.go
Normal file
205
u_initial_packet_spec.go
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
package quic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/gaukas/clienthellod"
|
||||||
|
"github.com/quic-go/quic-go/quicvarint"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InitialPacketSpec struct {
|
||||||
|
// SrcConnIDLength specifies how many bytes should the SrcConnID be
|
||||||
|
SrcConnIDLength int
|
||||||
|
|
||||||
|
// DestConnIDLength specifies how many bytes should the DestConnID be
|
||||||
|
DestConnIDLength int
|
||||||
|
|
||||||
|
// InitPacketNumberLength specifies how many bytes should the InitPacketNumber
|
||||||
|
// be interpreted as. It is usually 1 or 2 bytes. If unset, UQUIC will use the
|
||||||
|
// default algorithm to compute the length which is at least 2 bytes.
|
||||||
|
InitPacketNumberLength PacketNumberLen
|
||||||
|
|
||||||
|
// InitPacketNumber is the packet number of the first Initial packet. Following
|
||||||
|
// Initial packets, if any, will increment the Packet Number accordingly.
|
||||||
|
InitPacketNumber uint64 // [UQUIC]
|
||||||
|
|
||||||
|
// TokenStore is used to store and retrieve tokens. If set, will override the
|
||||||
|
// one set in the Config.
|
||||||
|
TokenStore TokenStore
|
||||||
|
|
||||||
|
// If ClientTokenLength is set when TokenStore is not set, a dummy TokenStore
|
||||||
|
// will be created to randomly generate tokens of the specified length for
|
||||||
|
// Pop() calls with any key and silently drop any Put() calls.
|
||||||
|
//
|
||||||
|
// However, the tokens will not be stored anywhere and are expected to be
|
||||||
|
// invalid since not assigned by the server.
|
||||||
|
ClientTokenLength int
|
||||||
|
|
||||||
|
// QUICFrames specifies a list of QUIC frames to be sent in the first Initial
|
||||||
|
// packet.
|
||||||
|
//
|
||||||
|
// If nil, it will be treated as a list with only a single QUICFrameCrypto.
|
||||||
|
FrameOrder QUICFrames
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *InitialPacketSpec) UpdateConfig(conf *Config) {
|
||||||
|
conf.TokenStore = ps.getTokenStore()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *InitialPacketSpec) getTokenStore() TokenStore {
|
||||||
|
if ps.TokenStore != nil {
|
||||||
|
return ps.TokenStore
|
||||||
|
}
|
||||||
|
|
||||||
|
if ps.ClientTokenLength > 0 {
|
||||||
|
return &dummyTokenStore{
|
||||||
|
tokenLength: ps.ClientTokenLength,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dummyTokenStore struct {
|
||||||
|
tokenLength int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dummyTokenStore) Pop(key string) (token *ClientToken) {
|
||||||
|
var data []byte = make([]byte, d.tokenLength)
|
||||||
|
rand.Read(data)
|
||||||
|
|
||||||
|
return &ClientToken{
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dummyTokenStore) Put(_ string, _ *ClientToken) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
type QUICFrames []QUICFrame
|
||||||
|
|
||||||
|
func (qfs QUICFrames) MarshalWithCryptoData(cryptoData []byte) (payload []byte, err error) {
|
||||||
|
if len(qfs) == 0 { // If no frames specified, send a single crypto frame
|
||||||
|
qfs = QUICFrames{QUICFrameCrypto{0, 0}}
|
||||||
|
return qfs.MarshalWithCryptoData(cryptoData)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, frame := range qfs {
|
||||||
|
var frameBytes []byte
|
||||||
|
if offset, length, cryptoOK := frame.CryptoFrameInfo(); cryptoOK {
|
||||||
|
if length == 0 {
|
||||||
|
// calculate length: from offset to the end of cryptoData
|
||||||
|
length = len(cryptoData) - offset
|
||||||
|
}
|
||||||
|
frameBytes = []byte{0x06} // CRYPTO frame type
|
||||||
|
frameBytes = quicvarint.Append(frameBytes, uint64(offset))
|
||||||
|
frameBytes = quicvarint.Append(frameBytes, uint64(length))
|
||||||
|
frameCryptoData := make([]byte, length)
|
||||||
|
copy(frameCryptoData, cryptoData[offset:]) // copy at most length bytes
|
||||||
|
frameBytes = append(frameBytes, frameCryptoData...)
|
||||||
|
} else { // Handle none crypto frames: read and append to payload
|
||||||
|
frameBytes, err = frame.Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
payload = append(payload, frameBytes...)
|
||||||
|
}
|
||||||
|
return payload, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qfs QUICFrames) MarshalWithFrames(frames []byte) (payload []byte, err error) {
|
||||||
|
// parse frames
|
||||||
|
r := bytes.NewReader(frames)
|
||||||
|
qchframes, err := clienthellod.ReadAllFrames(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse crypto data
|
||||||
|
cryptoData, err := clienthellod.ReassembleCRYPTOFrames(qchframes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshal
|
||||||
|
return qfs.MarshalWithCryptoData(cryptoData)
|
||||||
|
}
|
||||||
|
|
||||||
|
type QUICFrame interface {
|
||||||
|
// None crypto frames should return false for cryptoOK
|
||||||
|
CryptoFrameInfo() (offset, length int, cryptoOK bool)
|
||||||
|
|
||||||
|
// None crypto frames should return the byte representation of the frame.
|
||||||
|
// Crypto frames' behavior is undefined and unused.
|
||||||
|
Read() ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QUICFrameCrypto is used to specify the crypto frames containing the TLS ClientHello
|
||||||
|
// to be sent in the first Initial packet.
|
||||||
|
type QUICFrameCrypto struct {
|
||||||
|
// Offset is used to specify the starting offset of the crypto frame.
|
||||||
|
// Used when sending multiple crypto frames in a single packet.
|
||||||
|
//
|
||||||
|
// Multiple crypto frames in a single packet must not overlap and must
|
||||||
|
// make up an entire crypto stream continuously.
|
||||||
|
Offset int
|
||||||
|
|
||||||
|
// Length is used to specify the length of the crypto frame.
|
||||||
|
//
|
||||||
|
// Must be set if it is NOT the last crypto frame in a packet.
|
||||||
|
Length int
|
||||||
|
}
|
||||||
|
|
||||||
|
// CryptoFrameInfo() implements the QUICFrame interface.
|
||||||
|
//
|
||||||
|
// Crypto frames are later replaced by the crypto message using the information
|
||||||
|
// returned by this function.
|
||||||
|
func (q QUICFrameCrypto) CryptoFrameInfo() (offset, length int, cryptoOK bool) {
|
||||||
|
return q.Offset, q.Length, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read() implements the QUICFrame interface.
|
||||||
|
//
|
||||||
|
// Crypto frames are later replaced by the crypto message, so they are not Read()-able.
|
||||||
|
func (q QUICFrameCrypto) Read() ([]byte, error) {
|
||||||
|
return nil, errors.New("crypto frames are not Read()-able")
|
||||||
|
}
|
||||||
|
|
||||||
|
// QUICFramePadding is used to specify the padding frames to be sent in the first Initial
|
||||||
|
// packet.
|
||||||
|
type QUICFramePadding struct {
|
||||||
|
// Length is used to specify the length of the padding frame.
|
||||||
|
Length int
|
||||||
|
}
|
||||||
|
|
||||||
|
// CryptoFrameInfo() implements the QUICFrame interface.
|
||||||
|
func (q QUICFramePadding) CryptoFrameInfo() (offset, length int, cryptoOK bool) {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read() implements the QUICFrame interface.
|
||||||
|
//
|
||||||
|
// Padding simply returns a slice of bytes of the specified length filled with 0.
|
||||||
|
func (q QUICFramePadding) Read() ([]byte, error) {
|
||||||
|
return make([]byte, q.Length), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QUICFramePing is used to specify the ping frames to be sent in the first Initial
|
||||||
|
// packet.
|
||||||
|
type QUICFramePing struct{}
|
||||||
|
|
||||||
|
// CryptoFrameInfo() implements the QUICFrame interface.
|
||||||
|
func (q QUICFramePing) CryptoFrameInfo() (offset, length int, cryptoOK bool) {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read() implements the QUICFrame interface.
|
||||||
|
//
|
||||||
|
// Ping simply returns a slice of bytes of size 1 with value 0x01(PING).
|
||||||
|
func (q QUICFramePing) Read() ([]byte, error) {
|
||||||
|
return []byte{0x01}, nil
|
||||||
|
}
|
243
u_packet_packer.go
Normal file
243
u_packet_packer.go
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
package quic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/quic-go/quic-go/internal/handshake"
|
||||||
|
"github.com/quic-go/quic-go/internal/protocol"
|
||||||
|
"github.com/quic-go/quic-go/internal/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// uPacketPacker is an extended packetPacker which is used
|
||||||
|
// to customize some of the packetPacker's behaviors for
|
||||||
|
// UQUIC.
|
||||||
|
type uPacketPacker struct {
|
||||||
|
*packetPacker
|
||||||
|
|
||||||
|
// initPktNbrLen PacketNumberLen
|
||||||
|
// qfs QUICFrames // [UQUIC] uses QUICFrames to customize encrypted frames
|
||||||
|
// udpDatagramMinSize int
|
||||||
|
uSpec *QUICSpec // [UQUIC]
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUPacketPacker(
|
||||||
|
packetPacker *packetPacker,
|
||||||
|
uSpec *QUICSpec, // [UQUIC]
|
||||||
|
) *uPacketPacker {
|
||||||
|
return &uPacketPacker{
|
||||||
|
packetPacker: packetPacker,
|
||||||
|
uSpec: uSpec, // [UQUIC]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackCoalescedPacket packs a new packet.
|
||||||
|
// It packs an Initial / Handshake if there is data to send in these packet number spaces.
|
||||||
|
// It should only be called before the handshake is confirmed.
|
||||||
|
func (p *uPacketPacker) PackCoalescedPacket(onlyAck bool, maxPacketSize protocol.ByteCount, v protocol.VersionNumber) (*coalescedPacket, error) {
|
||||||
|
var (
|
||||||
|
initialHdr, handshakeHdr, zeroRTTHdr *wire.ExtendedHeader
|
||||||
|
initialPayload, handshakePayload, zeroRTTPayload, oneRTTPayload payload
|
||||||
|
oneRTTPacketNumber protocol.PacketNumber
|
||||||
|
oneRTTPacketNumberLen protocol.PacketNumberLen
|
||||||
|
)
|
||||||
|
// Try packing an Initial packet.
|
||||||
|
initialSealer, err := p.cryptoSetup.GetInitialSealer()
|
||||||
|
if err != nil && err != handshake.ErrKeysDropped {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var size protocol.ByteCount
|
||||||
|
if initialSealer != nil {
|
||||||
|
initialHdr, initialPayload = p.maybeGetCryptoPacket(maxPacketSize-protocol.ByteCount(initialSealer.Overhead()), protocol.EncryptionInitial, onlyAck, true, v)
|
||||||
|
if initialPayload.length > 0 {
|
||||||
|
size += p.longHeaderPacketLength(initialHdr, initialPayload, v) + protocol.ByteCount(initialSealer.Overhead())
|
||||||
|
}
|
||||||
|
|
||||||
|
// // [UQUIC]
|
||||||
|
// if len(initialPayload.frames) > 0 {
|
||||||
|
// fmt.Printf("onlyAck: %t, PackCoalescedPacket: %v\n", onlyAck, initialPayload.frames[0].Frame)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a Handshake packet.
|
||||||
|
var handshakeSealer sealer
|
||||||
|
if (onlyAck && size == 0) || (!onlyAck && size < maxPacketSize-protocol.MinCoalescedPacketSize) {
|
||||||
|
var err error
|
||||||
|
handshakeSealer, err = p.cryptoSetup.GetHandshakeSealer()
|
||||||
|
if err != nil && err != handshake.ErrKeysDropped && err != handshake.ErrKeysNotYetAvailable {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if handshakeSealer != nil {
|
||||||
|
handshakeHdr, handshakePayload = p.maybeGetCryptoPacket(maxPacketSize-size-protocol.ByteCount(handshakeSealer.Overhead()), protocol.EncryptionHandshake, onlyAck, size == 0, v)
|
||||||
|
if handshakePayload.length > 0 {
|
||||||
|
s := p.longHeaderPacketLength(handshakeHdr, handshakePayload, v) + protocol.ByteCount(handshakeSealer.Overhead())
|
||||||
|
size += s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a 0-RTT / 1-RTT packet.
|
||||||
|
var zeroRTTSealer sealer
|
||||||
|
var oneRTTSealer handshake.ShortHeaderSealer
|
||||||
|
var connID protocol.ConnectionID
|
||||||
|
var kp protocol.KeyPhaseBit
|
||||||
|
if (onlyAck && size == 0) || (!onlyAck && size < maxPacketSize-protocol.MinCoalescedPacketSize) {
|
||||||
|
var err error
|
||||||
|
oneRTTSealer, err = p.cryptoSetup.Get1RTTSealer()
|
||||||
|
if err != nil && err != handshake.ErrKeysDropped && err != handshake.ErrKeysNotYetAvailable {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err == nil { // 1-RTT
|
||||||
|
kp = oneRTTSealer.KeyPhase()
|
||||||
|
connID = p.getDestConnID()
|
||||||
|
oneRTTPacketNumber, oneRTTPacketNumberLen = p.pnManager.PeekPacketNumber(protocol.Encryption1RTT)
|
||||||
|
hdrLen := wire.ShortHeaderLen(connID, oneRTTPacketNumberLen)
|
||||||
|
oneRTTPayload = p.maybeGetShortHeaderPacket(oneRTTSealer, hdrLen, maxPacketSize-size, onlyAck, size == 0, v)
|
||||||
|
if oneRTTPayload.length > 0 {
|
||||||
|
size += p.shortHeaderPacketLength(connID, oneRTTPacketNumberLen, oneRTTPayload) + protocol.ByteCount(oneRTTSealer.Overhead())
|
||||||
|
}
|
||||||
|
} else if p.perspective == protocol.PerspectiveClient && !onlyAck { // 0-RTT packets can't contain ACK frames
|
||||||
|
var err error
|
||||||
|
zeroRTTSealer, err = p.cryptoSetup.Get0RTTSealer()
|
||||||
|
if err != nil && err != handshake.ErrKeysDropped && err != handshake.ErrKeysNotYetAvailable {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if zeroRTTSealer != nil {
|
||||||
|
zeroRTTHdr, zeroRTTPayload = p.maybeGetAppDataPacketFor0RTT(zeroRTTSealer, maxPacketSize-size, v)
|
||||||
|
if zeroRTTPayload.length > 0 {
|
||||||
|
size += p.longHeaderPacketLength(zeroRTTHdr, zeroRTTPayload, v) + protocol.ByteCount(zeroRTTSealer.Overhead())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if initialPayload.length == 0 && handshakePayload.length == 0 && zeroRTTPayload.length == 0 && oneRTTPayload.length == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := getPacketBuffer()
|
||||||
|
packet := &coalescedPacket{
|
||||||
|
buffer: buffer,
|
||||||
|
longHdrPackets: make([]*longHeaderPacket, 0, 3),
|
||||||
|
}
|
||||||
|
if initialPayload.length > 0 {
|
||||||
|
if onlyAck || len(initialPayload.frames) == 0 {
|
||||||
|
// TODO: uQUIC should send Initial Packet if requested.
|
||||||
|
// However, it should be otherwise configurable whether to request
|
||||||
|
// to send Initial Packet or not. See quic-go#4007
|
||||||
|
padding := p.initialPaddingLen(initialPayload.frames, size, maxPacketSize)
|
||||||
|
cont, err := p.appendLongHeaderPacket(buffer, initialHdr, initialPayload, padding, protocol.EncryptionInitial, initialSealer, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
packet.longHdrPackets = append(packet.longHdrPackets, cont)
|
||||||
|
} else { // [UQUIC]
|
||||||
|
cont, err := p.appendInitialPacket(buffer, initialHdr, initialPayload, protocol.EncryptionInitial, initialSealer, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
packet.longHdrPackets = append(packet.longHdrPackets, cont)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if handshakePayload.length > 0 {
|
||||||
|
cont, err := p.appendLongHeaderPacket(buffer, handshakeHdr, handshakePayload, 0, protocol.EncryptionHandshake, handshakeSealer, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
packet.longHdrPackets = append(packet.longHdrPackets, cont)
|
||||||
|
}
|
||||||
|
if zeroRTTPayload.length > 0 {
|
||||||
|
longHdrPacket, err := p.appendLongHeaderPacket(buffer, zeroRTTHdr, zeroRTTPayload, 0, protocol.Encryption0RTT, zeroRTTSealer, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
packet.longHdrPackets = append(packet.longHdrPackets, longHdrPacket)
|
||||||
|
} else if oneRTTPayload.length > 0 {
|
||||||
|
shp, err := p.appendShortHeaderPacket(buffer, connID, oneRTTPacketNumber, oneRTTPacketNumberLen, kp, oneRTTPayload, 0, maxPacketSize, oneRTTSealer, false, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
packet.shortHdrPacket = &shp
|
||||||
|
}
|
||||||
|
return packet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// [UQUIC]
|
||||||
|
func (p *uPacketPacker) appendInitialPacket(buffer *packetBuffer, header *wire.ExtendedHeader, pl payload, encLevel protocol.EncryptionLevel, sealer sealer, v protocol.VersionNumber) (*longHeaderPacket, error) {
|
||||||
|
// Shouldn't need this?
|
||||||
|
// if p.uSpec.InitialPacketSpec.InitPacketNumberLength > 0 {
|
||||||
|
// header.PacketNumberLen = p.uSpec.InitialPacketSpec.InitPacketNumberLength
|
||||||
|
// }
|
||||||
|
|
||||||
|
uPayload, err := p.MarshalInitialPacketPayload(pl, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pnLen := protocol.ByteCount(header.PacketNumberLen)
|
||||||
|
header.Length = pnLen + protocol.ByteCount(sealer.Overhead()) + protocol.ByteCount(len(uPayload))
|
||||||
|
|
||||||
|
startLen := len(buffer.Data)
|
||||||
|
raw := buffer.Data[startLen:] // [UQUIC] the raw here is a sub-slice of buffer.Data, latter's len < size
|
||||||
|
|
||||||
|
raw, err = header.Append(raw, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
payloadOffset := protocol.ByteCount(len(raw))
|
||||||
|
raw = append(raw, uPayload...)
|
||||||
|
|
||||||
|
// fmt.Printf("Payload: %x\n", raw[payloadOffset:])
|
||||||
|
|
||||||
|
// fmt.Printf("Pre-Encryption: %x\n", raw)
|
||||||
|
|
||||||
|
raw = p.encryptPacket(raw, sealer, header.PacketNumber, payloadOffset, pnLen)
|
||||||
|
buffer.Data = buffer.Data[:len(buffer.Data)+len(raw)]
|
||||||
|
|
||||||
|
// fmt.Printf("Post-Encryption: %x\n", raw)
|
||||||
|
|
||||||
|
// [UQUIC]
|
||||||
|
// append zero to buffer.Data until min size is reached
|
||||||
|
minUDPSize := p.uSpec.UDPDatagramMinSize
|
||||||
|
if minUDPSize == 0 {
|
||||||
|
minUDPSize = DefaultUDPDatagramMinSize
|
||||||
|
}
|
||||||
|
if len(buffer.Data) < minUDPSize {
|
||||||
|
buffer.Data = append(buffer.Data, make([]byte, minUDPSize-len(buffer.Data))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pn := p.pnManager.PopPacketNumber(encLevel); pn != header.PacketNumber {
|
||||||
|
return nil, fmt.Errorf("packetPacker BUG: Peeked and Popped packet numbers do not match: expected %d, got %d", pn, header.PacketNumber)
|
||||||
|
}
|
||||||
|
return &longHeaderPacket{
|
||||||
|
header: header,
|
||||||
|
ack: pl.ack,
|
||||||
|
frames: pl.frames,
|
||||||
|
streamFrames: pl.streamFrames,
|
||||||
|
length: protocol.ByteCount(len(raw)),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *uPacketPacker) MarshalInitialPacketPayload(pl payload, v protocol.VersionNumber) ([]byte, error) {
|
||||||
|
var originalFrameBytes []byte
|
||||||
|
|
||||||
|
for _, f := range pl.frames {
|
||||||
|
var err error
|
||||||
|
// only append crypto frames
|
||||||
|
if _, ok := f.Frame.(*wire.CryptoFrame); !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
originalFrameBytes, err = f.Frame.Append(originalFrameBytes, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uPayload, err := p.uSpec.InitialPacketSpec.FrameOrder.MarshalWithFrames(originalFrameBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return uPayload, nil
|
||||||
|
}
|
198
u_quic_spec.go
198
u_quic_spec.go
|
@ -1,200 +1,18 @@
|
||||||
package quic
|
package quic
|
||||||
|
|
||||||
import (
|
import tls "github.com/refraction-networking/utls"
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/gaukas/clienthellod"
|
const (
|
||||||
"github.com/quic-go/quic-go/quicvarint"
|
DefaultUDPDatagramMinSize = 1200
|
||||||
)
|
)
|
||||||
|
|
||||||
type QUICSpec struct {
|
type QUICSpec struct {
|
||||||
// SrcConnIDLength specifies how many bytes should the SrcConnID be
|
InitialPacketSpec InitialPacketSpec
|
||||||
SrcConnIDLength int
|
ClientHelloSpec *tls.ClientHelloSpec
|
||||||
|
|
||||||
// DestConnIDLength specifies how many bytes should the DestConnID be
|
UDPDatagramMinSize int
|
||||||
DstConnIDLength int
|
|
||||||
|
|
||||||
// InitPacketNumberLength specifies how many bytes should the InitPacketNumber
|
|
||||||
// be interpreted as. It is usually 1 or 2 bytes. If unset, UQUIC will use the
|
|
||||||
// default algorithm to compute the length which is at least 2 bytes.
|
|
||||||
InitPacketNumberLength PacketNumberLen
|
|
||||||
|
|
||||||
// InitPacketNumber is the packet number of the first Initial packet. Following
|
|
||||||
// Initial packets, if any, will increment the Packet Number accordingly.
|
|
||||||
InitPacketNumber uint64 // [UQUIC]
|
|
||||||
|
|
||||||
// TokenStore is used to store and retrieve tokens. If set, will override the
|
|
||||||
// one set in the Config.
|
|
||||||
TokenStore TokenStore
|
|
||||||
|
|
||||||
// If ClientTokenLength is set when TokenStore is not set, a dummy TokenStore
|
|
||||||
// will be created to randomly generate tokens of the specified length for
|
|
||||||
// Pop() calls with any key and silently drop any Put() calls.
|
|
||||||
//
|
|
||||||
// However, the tokens will not be stored anywhere and are expected to be
|
|
||||||
// invalid since not assigned by the server.
|
|
||||||
ClientTokenLength int
|
|
||||||
|
|
||||||
// QUICFrames specifies a list of QUIC frames to be sent in the first Initial
|
|
||||||
// packet.
|
|
||||||
//
|
|
||||||
// If nil, it will be treated as a list with only a single QUICFrameCrypto.
|
|
||||||
QUICFrames []QUICFrame
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QUICSpec) getTokenStore() TokenStore {
|
func (s *QUICSpec) UpdateConfig(config *Config) {
|
||||||
if s.TokenStore != nil {
|
s.InitialPacketSpec.UpdateConfig(config)
|
||||||
return s.TokenStore
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.ClientTokenLength > 0 {
|
|
||||||
return &dummyTokenStore{
|
|
||||||
tokenLength: s.ClientTokenLength,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type dummyTokenStore struct {
|
|
||||||
tokenLength int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dummyTokenStore) Pop(key string) (token *ClientToken) {
|
|
||||||
var data []byte = make([]byte, d.tokenLength)
|
|
||||||
rand.Read(data)
|
|
||||||
|
|
||||||
return &ClientToken{
|
|
||||||
data: data,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dummyTokenStore) Put(_ string, _ *ClientToken) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
type QUICFrames []QUICFrame
|
|
||||||
|
|
||||||
func (qfs QUICFrames) MarshalWithCryptoData(cryptoData []byte) (payload []byte, err error) {
|
|
||||||
if len(qfs) == 0 { // If no frames specified, send a single crypto frame
|
|
||||||
payload = make([]byte, len(cryptoData)+1)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, frame := range qfs {
|
|
||||||
var frameBytes []byte
|
|
||||||
if offset, length, cryptoOK := frame.CryptoFrameInfo(); cryptoOK {
|
|
||||||
if length == 0 {
|
|
||||||
// calculate length: from offset to the end of cryptoData
|
|
||||||
length = len(cryptoData) - offset
|
|
||||||
}
|
|
||||||
frameBytes = []byte{0x06} // CRYPTO frame type
|
|
||||||
frameBytes = quicvarint.Append(frameBytes, uint64(offset))
|
|
||||||
frameBytes = quicvarint.Append(frameBytes, uint64(length))
|
|
||||||
frameCryptoData := make([]byte, length)
|
|
||||||
copy(frameCryptoData, cryptoData[offset:]) // copy at most length bytes
|
|
||||||
frameBytes = append(frameBytes, frameCryptoData...)
|
|
||||||
} else { // Handle none crypto frames: read and append to payload
|
|
||||||
frameBytes, err = frame.Read()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
payload = append(payload, frameBytes...)
|
|
||||||
}
|
|
||||||
return payload, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qfs QUICFrames) MarshalWithFrames(frames []byte) (payload []byte, err error) {
|
|
||||||
// parse frames
|
|
||||||
r := bytes.NewReader(frames)
|
|
||||||
qchframes, err := clienthellod.ReadAllFrames(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse crypto data
|
|
||||||
cryptoData, err := clienthellod.ReassembleCRYPTOFrames(qchframes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// marshal
|
|
||||||
return qfs.MarshalWithCryptoData(cryptoData)
|
|
||||||
}
|
|
||||||
|
|
||||||
type QUICFrame interface {
|
|
||||||
// None crypto frames should return false for cryptoOK
|
|
||||||
CryptoFrameInfo() (offset, length int, cryptoOK bool)
|
|
||||||
|
|
||||||
// None crypto frames should return the byte representation of the frame.
|
|
||||||
// Crypto frames' behavior is undefined and unused.
|
|
||||||
Read() ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// QUICFrameCrypto is used to specify the crypto frames containing the TLS ClientHello
|
|
||||||
// to be sent in the first Initial packet.
|
|
||||||
type QUICFrameCrypto struct {
|
|
||||||
// Offset is used to specify the starting offset of the crypto frame.
|
|
||||||
// Used when sending multiple crypto frames in a single packet.
|
|
||||||
//
|
|
||||||
// Multiple crypto frames in a single packet must not overlap and must
|
|
||||||
// make up an entire crypto stream continuously.
|
|
||||||
Offset int
|
|
||||||
|
|
||||||
// Length is used to specify the length of the crypto frame.
|
|
||||||
//
|
|
||||||
// Must be set if it is NOT the last crypto frame in a packet.
|
|
||||||
Length int
|
|
||||||
}
|
|
||||||
|
|
||||||
// CryptoFrameInfo() implements the QUICFrame interface.
|
|
||||||
//
|
|
||||||
// Crypto frames are later replaced by the crypto message using the information
|
|
||||||
// returned by this function.
|
|
||||||
func (q QUICFrameCrypto) CryptoFrameInfo() (offset, length int, cryptoOK bool) {
|
|
||||||
return q.Offset, q.Length, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read() implements the QUICFrame interface.
|
|
||||||
//
|
|
||||||
// Crypto frames are later replaced by the crypto message, so they are not Read()-able.
|
|
||||||
func (q QUICFrameCrypto) Read() ([]byte, error) {
|
|
||||||
return nil, errors.New("crypto frames are not Read()-able")
|
|
||||||
}
|
|
||||||
|
|
||||||
// QUICFramePadding is used to specify the padding frames to be sent in the first Initial
|
|
||||||
// packet.
|
|
||||||
type QUICFramePadding struct {
|
|
||||||
// Length is used to specify the length of the padding frame.
|
|
||||||
Length int
|
|
||||||
}
|
|
||||||
|
|
||||||
// CryptoFrameInfo() implements the QUICFrame interface.
|
|
||||||
func (q QUICFramePadding) CryptoFrameInfo() (offset, length int, cryptoOK bool) {
|
|
||||||
return 0, 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read() implements the QUICFrame interface.
|
|
||||||
//
|
|
||||||
// Padding simply returns a slice of bytes of the specified length filled with 0.
|
|
||||||
func (q QUICFramePadding) Read() ([]byte, error) {
|
|
||||||
return make([]byte, q.Length), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// QUICFramePing is used to specify the ping frames to be sent in the first Initial
|
|
||||||
// packet.
|
|
||||||
type QUICFramePing struct{}
|
|
||||||
|
|
||||||
// CryptoFrameInfo() implements the QUICFrame interface.
|
|
||||||
func (q QUICFramePing) CryptoFrameInfo() (offset, length int, cryptoOK bool) {
|
|
||||||
return 0, 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read() implements the QUICFrame interface.
|
|
||||||
//
|
|
||||||
// Ping simply returns a slice of bytes of size 1 with value 0x01(PING).
|
|
||||||
func (q QUICFramePing) Read() ([]byte, error) {
|
|
||||||
return []byte{0x01}, nil
|
|
||||||
}
|
}
|
||||||
|
|
87
u_transport.go
Normal file
87
u_transport.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
package quic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/quic-go/quic-go/internal/protocol"
|
||||||
|
tls "github.com/refraction-networking/utls"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UTransport struct {
|
||||||
|
*Transport
|
||||||
|
|
||||||
|
QUICSpec *QUICSpec // [UQUIC] using ptr to avoid copying
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial dials a new connection to a remote host (not using 0-RTT).
|
||||||
|
func (t *UTransport) Dial(ctx context.Context, addr net.Addr, tlsConf *tls.Config, conf *Config) (Connection, error) {
|
||||||
|
if err := validateConfig(conf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conf = populateConfig(conf)
|
||||||
|
|
||||||
|
// [UQUIC]
|
||||||
|
// Override the default connection ID generator if the user has specified a length in QUICSpec.
|
||||||
|
if t.QUICSpec != nil {
|
||||||
|
if t.QUICSpec.InitialPacketSpec.SrcConnIDLength != 0 {
|
||||||
|
t.ConnectionIDGenerator = &protocol.DefaultConnectionIDGenerator{ConnLen: t.QUICSpec.InitialPacketSpec.SrcConnIDLength}
|
||||||
|
} else {
|
||||||
|
t.ConnectionIDGenerator = &protocol.ExpEmptyConnectionIDGenerator{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// [/UQUIC]
|
||||||
|
|
||||||
|
if err := t.init(t.isSingleUse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var onClose func()
|
||||||
|
if t.isSingleUse {
|
||||||
|
onClose = func() { t.Close() }
|
||||||
|
}
|
||||||
|
tlsConf = tlsConf.Clone()
|
||||||
|
tlsConf.MinVersion = tls.VersionTLS13
|
||||||
|
|
||||||
|
return udial(ctx, newSendConn(t.conn, addr), t.connIDGenerator, t.handlerMap, tlsConf, conf, onClose, false, t.QUICSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialEarly dials a new connection, attempting to use 0-RTT if possible.
|
||||||
|
func (t *UTransport) DialEarly(ctx context.Context, addr net.Addr, tlsConf *tls.Config, conf *Config) (EarlyConnection, error) {
|
||||||
|
if err := validateConfig(conf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conf = populateConfig(conf)
|
||||||
|
|
||||||
|
// [UQUIC]
|
||||||
|
// Override the default connection ID generator if the user has specified a length in QUICSpec.
|
||||||
|
if t.QUICSpec != nil {
|
||||||
|
if t.QUICSpec.InitialPacketSpec.SrcConnIDLength != 0 {
|
||||||
|
t.ConnectionIDGenerator = &protocol.DefaultConnectionIDGenerator{ConnLen: t.QUICSpec.InitialPacketSpec.SrcConnIDLength}
|
||||||
|
} else {
|
||||||
|
t.ConnectionIDGenerator = &protocol.ExpEmptyConnectionIDGenerator{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// [/UQUIC]
|
||||||
|
|
||||||
|
if err := t.init(t.isSingleUse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var onClose func()
|
||||||
|
if t.isSingleUse {
|
||||||
|
onClose = func() { t.Close() }
|
||||||
|
}
|
||||||
|
tlsConf = tlsConf.Clone()
|
||||||
|
tlsConf.MinVersion = tls.VersionTLS13
|
||||||
|
|
||||||
|
return udial(ctx, newSendConn(t.conn, addr), t.connIDGenerator, t.handlerMap, tlsConf, conf, onClose, true, t.QUICSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ut *UTransport) MakeDialer() func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *Config) (EarlyConnection, error) {
|
||||||
|
return func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *Config) (EarlyConnection, error) {
|
||||||
|
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ut.DialEarly(ctx, udpAddr, tlsCfg, cfg)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue