mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-05-13 23:58:40 +00:00
Update quic-go with uquic 0.0.6
Co-authored-by: ll11l1lIllIl1lll <88377095+ll11l1lIllIl1lll@users.noreply.github.com> Co-authored-by: mmmray <142015632+mmmray@users.noreply.github.com> Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
This commit is contained in:
parent
b7aacd3245
commit
0b7a5086a2
10 changed files with 326 additions and 26 deletions
|
@ -9,8 +9,9 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"github.com/refraction-networking/uquic"
|
||||
"github.com/refraction-networking/uquic/http3"
|
||||
utls "github.com/refraction-networking/utls"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
c "github.com/xtls/xray-core/common/ctx"
|
||||
|
@ -79,9 +80,9 @@ func getHTTPClient(ctx context.Context, dest net.Destination, streamSettings *in
|
|||
KeepAlivePeriod: h3KeepalivePeriod,
|
||||
}
|
||||
roundTripper := &http3.RoundTripper{
|
||||
QUICConfig: quicConfig,
|
||||
TLSClientConfig: tlsConfigs.GetTLSConfig(tls.WithDestination(dest)),
|
||||
Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
QuicConfig: quicConfig,
|
||||
TLSClientConfig: tls.CopyConfig(tlsConfigs.GetTLSConfig(tls.WithDestination(dest))),
|
||||
Dial: func(ctx context.Context, addr string, tlsCfg *utls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -8,8 +8,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"github.com/refraction-networking/uquic"
|
||||
"github.com/refraction-networking/uquic/http3"
|
||||
goreality "github.com/xtls/reality"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
|
@ -170,7 +170,7 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
|
|||
if err != nil {
|
||||
return nil, errors.New("failed to listen UDP(for SH3) on ", address, ":", port).Base(err)
|
||||
}
|
||||
h3listener, err := quic.ListenEarly(Conn, tlsConfig, nil)
|
||||
h3listener, err := quic.ListenEarly(Conn, config.GetUTLSConfig(), nil)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to listen QUIC(for SH3) on ", address, ":", port).Base(err)
|
||||
}
|
||||
|
|
|
@ -9,8 +9,9 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"github.com/refraction-networking/uquic"
|
||||
"github.com/refraction-networking/uquic/http3"
|
||||
utls "github.com/refraction-networking/utls"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
|
@ -145,9 +146,9 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
|
|||
KeepAlivePeriod: h3KeepalivePeriod,
|
||||
}
|
||||
transport = &http3.RoundTripper{
|
||||
QUICConfig: quicConfig,
|
||||
TLSClientConfig: gotlsConfig,
|
||||
Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
QuicConfig: quicConfig,
|
||||
TLSClientConfig: tls.CopyConfig(gotlsConfig),
|
||||
Dial: func(ctx context.Context, addr string, tlsCfg *utls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -11,8 +11,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"github.com/refraction-networking/uquic"
|
||||
"github.com/refraction-networking/uquic/http3"
|
||||
goreality "github.com/xtls/reality"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
|
@ -308,7 +308,8 @@ func ListenSH(ctx context.Context, address net.Address, port net.Port, streamSet
|
|||
if err != nil {
|
||||
return nil, errors.New("failed to listen UDP(for SH3) on ", address, ":", port).Base(err)
|
||||
}
|
||||
h3listener, err := quic.ListenEarly(Conn, tlsConfig, nil)
|
||||
config := v2tls.ConfigFromStreamSettings(streamSettings)
|
||||
h3listener, err := quic.ListenEarly(Conn, config.GetUTLSConfig(), nil)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to listen QUIC(for SH3) on ", address, ":", port).Base(err)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package tls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/tls"
|
||||
|
@ -10,8 +11,8 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"bytes"
|
||||
|
||||
utls "github.com/refraction-networking/utls"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/ocsp"
|
||||
|
@ -21,6 +22,7 @@ import (
|
|||
)
|
||||
|
||||
var globalSessionCache = tls.NewLRUClientSessionCache(128)
|
||||
var globalSessionCacheU = utls.NewLRUClientSessionCache(128)
|
||||
|
||||
// ParseCertificate converts a cert.Certificate to Certificate.
|
||||
func ParseCertificate(c *cert.Certificate) *Certificate {
|
||||
|
@ -92,6 +94,54 @@ func (c *Config) BuildCertificates() []*tls.Certificate {
|
|||
return certs
|
||||
}
|
||||
|
||||
// BuildCertificates builds a list of UTLS certificates from proto definition.
|
||||
func (c *Config) BuildCertificatesU() []*utls.Certificate {
|
||||
certs := make([]*utls.Certificate, 0, len(c.Certificate))
|
||||
for _, entry := range c.Certificate {
|
||||
if entry.Usage != Certificate_ENCIPHERMENT {
|
||||
continue
|
||||
}
|
||||
getX509KeyPair := func() *utls.Certificate {
|
||||
keyPair, err := utls.X509KeyPair(entry.Certificate, entry.Key)
|
||||
if err != nil {
|
||||
errors.LogWarningInner(context.Background(), err, "ignoring invalid X509 key pair")
|
||||
return nil
|
||||
}
|
||||
keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
|
||||
if err != nil {
|
||||
errors.LogWarningInner(context.Background(), err, "ignoring invalid certificate")
|
||||
return nil
|
||||
}
|
||||
return &keyPair
|
||||
}
|
||||
if keyPair := getX509KeyPair(); keyPair != nil {
|
||||
certs = append(certs, keyPair)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
index := len(certs) - 1
|
||||
setupOcspTicker(entry, func(isReloaded, isOcspstapling bool){
|
||||
cert := certs[index]
|
||||
if isReloaded {
|
||||
if newKeyPair := getX509KeyPair(); newKeyPair != nil {
|
||||
cert = newKeyPair
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
if isOcspstapling {
|
||||
if newOCSPData, err := ocsp.GetOCSPForCert(cert.Certificate); err != nil {
|
||||
errors.LogWarningInner(context.Background(), err, "ignoring invalid OCSP")
|
||||
} else if string(newOCSPData) != string(cert.OCSPStaple) {
|
||||
cert.OCSPStaple = newOCSPData
|
||||
}
|
||||
}
|
||||
certs[index] = cert
|
||||
})
|
||||
}
|
||||
return certs
|
||||
}
|
||||
|
||||
func setupOcspTicker(entry *Certificate, callback func(isReloaded, isOcspstapling bool)) {
|
||||
go func() {
|
||||
if entry.OneTimeLoading {
|
||||
|
@ -140,6 +190,17 @@ func isCertificateExpired(c *tls.Certificate) bool {
|
|||
return c.Leaf != nil && c.Leaf.NotAfter.Before(time.Now().Add(time.Minute*2))
|
||||
}
|
||||
|
||||
func isCertificateExpiredU(c *utls.Certificate) bool {
|
||||
if c.Leaf == nil && len(c.Certificate) > 0 {
|
||||
if pc, err := x509.ParseCertificate(c.Certificate[0]); err == nil {
|
||||
c.Leaf = pc
|
||||
}
|
||||
}
|
||||
|
||||
// If leaf is not there, the certificate is probably not used yet. We trust user to provide a valid certificate.
|
||||
return c.Leaf != nil && c.Leaf.NotAfter.Before(time.Now().Add(time.Minute*2))
|
||||
}
|
||||
|
||||
func issueCertificate(rawCA *Certificate, domain string) (*tls.Certificate, error) {
|
||||
parent, err := cert.ParseCertificate(rawCA.Certificate, rawCA.Key)
|
||||
if err != nil {
|
||||
|
@ -157,6 +218,23 @@ func issueCertificate(rawCA *Certificate, domain string) (*tls.Certificate, erro
|
|||
return &cert, err
|
||||
}
|
||||
|
||||
func issueCertificateU(rawCA *Certificate, domain string) (*utls.Certificate, error) {
|
||||
parent, err := cert.ParseCertificate(rawCA.Certificate, rawCA.Key)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to parse raw certificate").Base(err)
|
||||
}
|
||||
newCert, err := cert.Generate(parent, cert.CommonName(domain), cert.DNSNames(domain))
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to generate new certificate for ", domain).Base(err)
|
||||
}
|
||||
newCertPEM, newKeyPEM := newCert.ToPEM()
|
||||
if rawCA.BuildChain {
|
||||
newCertPEM = bytes.Join([][]byte{newCertPEM, rawCA.Certificate}, []byte("\n"))
|
||||
}
|
||||
cert, err := utls.X509KeyPair(newCertPEM, newKeyPEM)
|
||||
return &cert, err
|
||||
}
|
||||
|
||||
func (c *Config) getCustomCA() []*Certificate {
|
||||
certs := make([]*Certificate, 0, len(c.Certificate))
|
||||
for _, certificate := range c.Certificate {
|
||||
|
@ -272,6 +350,110 @@ func getNewGetCertificateFunc(certs []*tls.Certificate, rejectUnknownSNI bool) f
|
|||
}
|
||||
}
|
||||
|
||||
func getGetCertificateUFunc(c *utls.Config, ca []*Certificate) func(hello *utls.ClientHelloInfo) (*utls.Certificate, error) {
|
||||
var access sync.RWMutex
|
||||
|
||||
return func(hello *utls.ClientHelloInfo) (*utls.Certificate, error) {
|
||||
domain := hello.ServerName
|
||||
certExpired := false
|
||||
|
||||
access.RLock()
|
||||
certificate, found := c.NameToCertificate[domain]
|
||||
access.RUnlock()
|
||||
|
||||
if found {
|
||||
if !isCertificateExpiredU(certificate) {
|
||||
return certificate, nil
|
||||
}
|
||||
certExpired = true
|
||||
}
|
||||
|
||||
if certExpired {
|
||||
newCerts := make([]utls.Certificate, 0, len(c.Certificates))
|
||||
|
||||
access.Lock()
|
||||
for _, certificate := range c.Certificates {
|
||||
if !isCertificateExpiredU(&certificate) {
|
||||
newCerts = append(newCerts, certificate)
|
||||
} else if certificate.Leaf != nil {
|
||||
expTime := certificate.Leaf.NotAfter.Format(time.RFC3339)
|
||||
errors.LogInfo(context.Background(), "old certificate for ", domain, " (expire on ", expTime, ") discarded")
|
||||
}
|
||||
}
|
||||
|
||||
c.Certificates = newCerts
|
||||
access.Unlock()
|
||||
}
|
||||
|
||||
var issuedCertificate *utls.Certificate
|
||||
|
||||
// Create a new certificate from existing CA if possible
|
||||
for _, rawCert := range ca {
|
||||
if rawCert.Usage == Certificate_AUTHORITY_ISSUE {
|
||||
newCert, err := issueCertificateU(rawCert, domain)
|
||||
if err != nil {
|
||||
errors.LogInfoInner(context.Background(), err, "failed to issue new certificate for ", domain)
|
||||
continue
|
||||
}
|
||||
parsed, err := x509.ParseCertificate(newCert.Certificate[0])
|
||||
if err == nil {
|
||||
newCert.Leaf = parsed
|
||||
expTime := parsed.NotAfter.Format(time.RFC3339)
|
||||
errors.LogInfo(context.Background(), "new certificate for ", domain, " (expire on ", expTime, ") issued")
|
||||
} else {
|
||||
errors.LogInfoInner(context.Background(), err, "failed to parse new certificate for ", domain)
|
||||
}
|
||||
|
||||
access.Lock()
|
||||
c.Certificates = append(c.Certificates, *newCert)
|
||||
issuedCertificate = &c.Certificates[len(c.Certificates)-1]
|
||||
access.Unlock()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if issuedCertificate == nil {
|
||||
return nil, errors.New("failed to create a new certificate for ", domain)
|
||||
}
|
||||
|
||||
access.Lock()
|
||||
c.BuildNameToCertificate()
|
||||
access.Unlock()
|
||||
|
||||
return issuedCertificate, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getNewGetCertificateUFunc(certs []*utls.Certificate, rejectUnknownSNI bool) func(hello *utls.ClientHelloInfo) (*utls.Certificate, error) {
|
||||
return func(hello *utls.ClientHelloInfo) (*utls.Certificate, error) {
|
||||
if len(certs) == 0 {
|
||||
return nil, errNoCertificates
|
||||
}
|
||||
sni := strings.ToLower(hello.ServerName)
|
||||
if !rejectUnknownSNI && (len(certs) == 1 || sni == "") {
|
||||
return certs[0], nil
|
||||
}
|
||||
gsni := "*"
|
||||
if index := strings.IndexByte(sni, '.'); index != -1 {
|
||||
gsni += sni[index:]
|
||||
}
|
||||
for _, keyPair := range certs {
|
||||
if keyPair.Leaf.Subject.CommonName == sni || keyPair.Leaf.Subject.CommonName == gsni {
|
||||
return keyPair, nil
|
||||
}
|
||||
for _, name := range keyPair.Leaf.DNSNames {
|
||||
if name == sni || name == gsni {
|
||||
return keyPair, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if rejectUnknownSNI {
|
||||
return nil, errNoCertificates
|
||||
}
|
||||
return certs[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) parseServerName() string {
|
||||
return c.ServerName
|
||||
}
|
||||
|
@ -394,6 +576,97 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
|
|||
return config
|
||||
}
|
||||
|
||||
// GetUTLSConfig converts this Config into utls.Config.
|
||||
func (c *Config) GetUTLSConfig() *utls.Config {
|
||||
root, err := c.getCertPool()
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to load system root certificate")
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
return &utls.Config{
|
||||
ClientSessionCache: globalSessionCacheU,
|
||||
RootCAs: root,
|
||||
InsecureSkipVerify: false,
|
||||
NextProtos: nil,
|
||||
SessionTicketsDisabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
config := &utls.Config{
|
||||
ClientSessionCache: globalSessionCacheU,
|
||||
RootCAs: root,
|
||||
InsecureSkipVerify: c.AllowInsecure,
|
||||
NextProtos: c.NextProtocol,
|
||||
SessionTicketsDisabled: !c.EnableSessionResumption,
|
||||
VerifyPeerCertificate: c.verifyPeerCert,
|
||||
}
|
||||
|
||||
// for _, opt := range opts {
|
||||
// opt(config)
|
||||
// }
|
||||
|
||||
caCerts := c.getCustomCA()
|
||||
if len(caCerts) > 0 {
|
||||
config.GetCertificate = getGetCertificateUFunc(config, caCerts)
|
||||
} else {
|
||||
config.GetCertificate = getNewGetCertificateUFunc(c.BuildCertificatesU(), c.RejectUnknownSni)
|
||||
}
|
||||
|
||||
if sn := c.parseServerName(); len(sn) > 0 {
|
||||
config.ServerName = sn
|
||||
}
|
||||
|
||||
if len(config.NextProtos) == 0 {
|
||||
config.NextProtos = []string{"h2", "http/1.1"}
|
||||
}
|
||||
|
||||
switch c.MinVersion {
|
||||
case "1.0":
|
||||
config.MinVersion = tls.VersionTLS10
|
||||
case "1.1":
|
||||
config.MinVersion = tls.VersionTLS11
|
||||
case "1.2":
|
||||
config.MinVersion = tls.VersionTLS12
|
||||
case "1.3":
|
||||
config.MinVersion = tls.VersionTLS13
|
||||
}
|
||||
|
||||
switch c.MaxVersion {
|
||||
case "1.0":
|
||||
config.MaxVersion = tls.VersionTLS10
|
||||
case "1.1":
|
||||
config.MaxVersion = tls.VersionTLS11
|
||||
case "1.2":
|
||||
config.MaxVersion = tls.VersionTLS12
|
||||
case "1.3":
|
||||
config.MaxVersion = tls.VersionTLS13
|
||||
}
|
||||
|
||||
if len(c.CipherSuites) > 0 {
|
||||
id := make(map[string]uint16)
|
||||
for _, s := range tls.CipherSuites() {
|
||||
id[s.Name] = s.ID
|
||||
}
|
||||
for _, n := range strings.Split(c.CipherSuites, ":") {
|
||||
if id[n] != 0 {
|
||||
config.CipherSuites = append(config.CipherSuites, id[n])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.MasterKeyLog) > 0 && c.MasterKeyLog != "none" {
|
||||
writer, err := os.OpenFile(c.MasterKeyLog, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to open ", c.MasterKeyLog, " as master key log")
|
||||
} else {
|
||||
config.KeyLogWriter = writer
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// Option for building TLS config.
|
||||
type Option func(*tls.Config)
|
||||
|
||||
|
|
|
@ -129,11 +129,11 @@ func (c *UConn) NegotiatedProtocol() string {
|
|||
}
|
||||
|
||||
func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) net.Conn {
|
||||
utlsConn := utls.UClient(c, copyConfig(config), *fingerprint)
|
||||
utlsConn := utls.UClient(c, CopyConfig(config), *fingerprint)
|
||||
return &UConn{UConn: utlsConn}
|
||||
}
|
||||
|
||||
func copyConfig(c *tls.Config) *utls.Config {
|
||||
func CopyConfig(c *tls.Config) *utls.Config {
|
||||
return &utls.Config{
|
||||
RootCAs: c.RootCAs,
|
||||
ServerName: c.ServerName,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue