1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2024-06-10 09:50:43 +00:00

implement header and auth for quic

This commit is contained in:
Darien Raymond 2018-11-21 22:02:19 +01:00
parent 92a6699706
commit 3335f77c70
No known key found for this signature in database
GPG Key ID: 7251FFA14BB18169
10 changed files with 337 additions and 50 deletions

View File

@ -0,0 +1,47 @@
package quic
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"golang.org/x/crypto/chacha20poly1305"
"v2ray.com/core/common"
"v2ray.com/core/common/protocol"
"v2ray.com/core/transport/internet"
)
func getAuth(config *Config) (cipher.AEAD, error) {
security := config.Security.GetSecurityType()
if security == protocol.SecurityType_NONE {
return nil, nil
}
salted := []byte(config.Key + "v2ray-quic-salt")
key := sha256.Sum256(salted)
if security == protocol.SecurityType_AES128_GCM {
block, err := aes.NewCipher(key[:16])
common.Must(err)
return cipher.NewGCM(block)
}
if security == protocol.SecurityType_CHACHA20_POLY1305 {
return chacha20poly1305.New(key[:])
}
return nil, newError("unsupported security type")
}
func getHeader(config *Config) (internet.PacketHeader, error) {
if config.Header == nil {
return nil, nil
}
msg, err := config.Header.GetInstance()
if err != nil {
return nil, err
}
return internet.CreatePacketHeader(msg)
}

View File

@ -4,6 +4,7 @@ import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
protocol "v2ray.com/core/common/protocol"
serial "v2ray.com/core/common/serial"
)
@ -19,11 +20,12 @@ var _ = math.Inf
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type Config struct {
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
Header *serial.TypedMessage `protobuf:"bytes,2,opt,name=header,proto3" json:"header,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
Security *protocol.SecurityConfig `protobuf:"bytes,2,opt,name=security,proto3" json:"security,omitempty"`
Header *serial.TypedMessage `protobuf:"bytes,3,opt,name=header,proto3" json:"header,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Config) Reset() { *m = Config{} }
@ -58,6 +60,13 @@ func (m *Config) GetKey() string {
return ""
}
func (m *Config) GetSecurity() *protocol.SecurityConfig {
if m != nil {
return m.Security
}
return nil
}
func (m *Config) GetHeader() *serial.TypedMessage {
if m != nil {
return m.Header
@ -74,20 +83,22 @@ func init() {
}
var fileDescriptor_462e2eb906061b36 = []byte{
// 226 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x8e, 0xb1, 0x4b, 0x03, 0x31,
0x14, 0x87, 0x49, 0x95, 0x03, 0xe3, 0x22, 0x37, 0x15, 0xa7, 0x72, 0x43, 0xe9, 0xf4, 0x22, 0xd7,
0xdd, 0xc1, 0x4e, 0x0e, 0x82, 0x1e, 0xd5, 0xa1, 0x8b, 0xc4, 0xf4, 0x59, 0x83, 0x26, 0xef, 0x7c,
0x49, 0x85, 0xfc, 0x4b, 0xfe, 0x95, 0x92, 0xc6, 0x3b, 0xc4, 0xa5, 0x53, 0x32, 0xfc, 0xbe, 0xef,
0x7d, 0x72, 0xf9, 0xd5, 0xb2, 0x4e, 0x60, 0xc8, 0x29, 0x43, 0x8c, 0x2a, 0xb2, 0xf6, 0xa1, 0x27,
0x8e, 0xca, 0xfa, 0x88, 0xec, 0x31, 0xaa, 0xcf, 0xbd, 0x35, 0xca, 0x90, 0x7f, 0xb5, 0x3b, 0xe8,
0x99, 0x22, 0xd5, 0xcd, 0x00, 0x31, 0xc2, 0x08, 0xc0, 0x00, 0x40, 0x06, 0x2e, 0xaf, 0xfe, 0x89,
0x0d, 0x39, 0x47, 0x5e, 0x05, 0x64, 0xab, 0x3f, 0x54, 0x4c, 0x3d, 0x6e, 0x9f, 0x1d, 0x86, 0xa0,
0x77, 0x58, 0xac, 0xcd, 0x46, 0x56, 0xab, 0xc3, 0x95, 0xfa, 0x42, 0x9e, 0xbc, 0x63, 0x9a, 0x8a,
0x99, 0x58, 0x9c, 0x75, 0xf9, 0x5b, 0x5f, 0xcb, 0xea, 0x0d, 0xf5, 0x16, 0x79, 0x3a, 0x99, 0x89,
0xc5, 0x79, 0x3b, 0x87, 0x3f, 0x09, 0x45, 0x0d, 0x45, 0x0d, 0xeb, 0xac, 0xbe, 0x2b, 0xe6, 0xee,
0x97, 0xba, 0x79, 0x94, 0x73, 0x43, 0x0e, 0x8e, 0x77, 0xdf, 0x8b, 0xcd, 0x69, 0x7e, 0xbf, 0x27,
0xcd, 0x53, 0xdb, 0xe9, 0x04, 0xab, 0x3c, 0x5e, 0x8f, 0xe3, 0xdb, 0x61, 0xfc, 0xb0, 0xb7, 0xe6,
0xa5, 0x3a, 0x94, 0x2f, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x9a, 0x6a, 0xd1, 0x56, 0x46, 0x01,
0x00, 0x00,
// 269 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x90, 0xc1, 0x4b, 0xc3, 0x30,
0x14, 0xc6, 0xe9, 0x26, 0x45, 0xe3, 0x45, 0x7a, 0x2a, 0x3b, 0x8d, 0x1e, 0xc6, 0x10, 0x79, 0x91,
0xee, 0xee, 0xc1, 0x81, 0xe0, 0x41, 0xd0, 0x3a, 0x3d, 0x78, 0x91, 0x98, 0x3d, 0x67, 0x70, 0xed,
0xab, 0x2f, 0xa9, 0xd0, 0x7f, 0xc7, 0xa3, 0x7f, 0xa5, 0xa4, 0x59, 0x8b, 0xc8, 0xc0, 0x53, 0x0b,
0xf9, 0x7e, 0xbf, 0x7c, 0x5f, 0xc4, 0xe2, 0x33, 0x67, 0xd5, 0x82, 0xa6, 0x52, 0x6a, 0x62, 0x94,
0x8e, 0x55, 0x65, 0x6b, 0x62, 0x27, 0x4d, 0xe5, 0x90, 0x2b, 0x74, 0xf2, 0xa3, 0x31, 0x5a, 0x6a,
0xaa, 0x5e, 0xcd, 0x06, 0x6a, 0x26, 0x47, 0x49, 0xd6, 0x43, 0x8c, 0x30, 0x00, 0xd0, 0x03, 0xe0,
0x81, 0xc9, 0xf9, 0x1f, 0xb1, 0xa6, 0xb2, 0xa4, 0x4a, 0x5a, 0x64, 0xa3, 0xb6, 0xd2, 0xb5, 0x35,
0xae, 0x9f, 0x4b, 0xb4, 0x56, 0x6d, 0x30, 0x58, 0x27, 0x67, 0xfb, 0x89, 0xee, 0x50, 0xd3, 0x56,
0xbe, 0xa1, 0x5a, 0x23, 0xdb, 0x90, 0xce, 0xbe, 0x22, 0x11, 0x2f, 0xbb, 0x52, 0xc9, 0x89, 0x18,
0xbf, 0x63, 0x9b, 0x46, 0xd3, 0x68, 0x7e, 0x54, 0xf8, 0xdf, 0xe4, 0x4a, 0x1c, 0x5a, 0xd4, 0x0d,
0x1b, 0xd7, 0xa6, 0xa3, 0x69, 0x34, 0x3f, 0xce, 0x4f, 0xe1, 0x57, 0xe7, 0x60, 0x86, 0xde, 0x0c,
0xf7, 0xbb, 0x6c, 0xf0, 0x15, 0x03, 0x9b, 0x5c, 0x88, 0x38, 0xdc, 0x9a, 0x8e, 0x3b, 0xcb, 0x6c,
0x8f, 0x25, 0x2c, 0x82, 0x95, 0x5f, 0x74, 0x13, 0x06, 0x15, 0x3b, 0xea, 0xf2, 0x41, 0xcc, 0x34,
0x95, 0xf0, 0xff, 0x73, 0xdd, 0x46, 0x4f, 0x07, 0xfe, 0xfb, 0x3d, 0xca, 0x1e, 0xf3, 0x42, 0xb5,
0xb0, 0xf4, 0xe1, 0xd5, 0x10, 0xbe, 0xee, 0xc3, 0x77, 0x8d, 0xd1, 0x2f, 0x71, 0xd7, 0x7c, 0xf1,
0x13, 0x00, 0x00, 0xff, 0xff, 0xd0, 0x05, 0x0f, 0xf6, 0xbd, 0x01, 0x00, 0x00,
}

View File

@ -7,8 +7,10 @@ option java_package = "com.v2ray.core.transport.internet.quic";
option java_multiple_files = true;
import "v2ray.com/core/common/serial/typed_message.proto";
import "v2ray.com/core/common/protocol/headers.proto";
message Config {
string key = 1;
v2ray.core.common.serial.TypedMessage header = 2;
v2ray.core.common.protocol.SecurityConfig security = 2;
v2ray.core.common.serial.TypedMessage header = 3;
}

View File

@ -1,10 +1,14 @@
package quic
import (
"crypto/cipher"
"crypto/rand"
"errors"
"time"
quic "github.com/lucas-clemente/quic-go"
"v2ray.com/core/common"
"v2ray.com/core/common/net"
"v2ray.com/core/transport/internet"
)
@ -12,43 +16,100 @@ import (
type sysConn struct {
conn net.PacketConn
header internet.PacketHeader
auth cipher.AEAD
}
func (c *sysConn) ReadFrom(p []byte) (int, net.Addr, error) {
if c.header == nil {
return c.conn.ReadFrom(p)
func wrapSysConn(rawConn net.PacketConn, config *Config) (*sysConn, error) {
header, err := getHeader(config)
if err != nil {
return nil, err
}
auth, err := getAuth(config)
if err != nil {
return nil, err
}
return &sysConn{
conn: rawConn,
header: header,
auth: auth,
}, nil
}
overhead := int(c.header.Size())
var errCipherError = errors.New("cipher error")
func (c *sysConn) readFromInternal(p []byte) (int, net.Addr, error) {
buffer := getBuffer()
defer putBuffer(buffer)
nBytes, addr, err := c.conn.ReadFrom(buffer[:len(p)+overhead])
nBytes, addr, err := c.conn.ReadFrom(buffer)
if err != nil {
return 0, nil, err
}
copy(p, buffer[overhead:nBytes])
payload := buffer[:nBytes]
if c.header != nil {
payload = payload[c.header.Size():]
}
return nBytes - overhead, addr, nil
if c.auth == nil {
n := copy(p, payload)
return n, addr, nil
}
nonce := payload[:c.auth.NonceSize()]
payload = payload[c.auth.NonceSize():]
p, err = c.auth.Open(p[:0], nonce, payload, nil)
if err != nil {
return 0, nil, errCipherError
}
return len(p), addr, nil
}
func (c *sysConn) ReadFrom(p []byte) (int, net.Addr, error) {
if c.header == nil && c.auth == nil {
return c.conn.ReadFrom(p)
}
for {
n, addr, err := c.readFromInternal(p)
if err != nil && err != errCipherError {
return 0, nil, err
}
if err == nil {
return n, addr, nil
}
}
}
func (c *sysConn) WriteTo(p []byte, addr net.Addr) (int, error) {
if c.header == nil {
if c.header == nil && c.auth == nil {
return c.conn.WriteTo(p, addr)
}
buffer := getBuffer()
defer putBuffer(buffer)
overhead := int(c.header.Size())
c.header.Serialize(buffer)
copy(buffer[overhead:], p)
nBytes, err := c.conn.WriteTo(buffer[:len(p)+overhead], addr)
if err != nil {
return 0, err
payload := buffer
n := 0
if c.header != nil {
c.header.Serialize(payload)
n = int(c.header.Size())
}
return nBytes - overhead, nil
if c.auth == nil {
nBytes := copy(payload[n:], p)
n += nBytes
} else {
nounce := payload[n : n+c.auth.NonceSize()]
common.Must2(rand.Read(nounce))
n += c.auth.NonceSize()
pp := c.auth.Seal(payload[:n], nounce, p, nil)
n = len(pp)
}
return c.conn.WriteTo(payload[:n], addr)
}
func (c *sysConn) Close() error {

View File

@ -18,7 +18,7 @@ type clientSessions struct {
sessions map[net.Destination]quic.Session
}
func (s *clientSessions) getSession(destAddr net.Addr, tlsConfig *tls.Config, sockopt *internet.SocketConfig) (quic.Session, error) {
func (s *clientSessions) getSession(destAddr net.Addr, config *Config, tlsConfig *tls.Config, sockopt *internet.SocketConfig) (quic.Session, error) {
s.access.Lock()
defer s.access.Unlock()
@ -32,7 +32,7 @@ func (s *clientSessions) getSession(destAddr net.Addr, tlsConfig *tls.Config, so
return session, nil
}
conn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{
rawConn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{
IP: []byte{0, 0, 0, 0},
Port: 0,
}, sockopt)
@ -40,14 +40,21 @@ func (s *clientSessions) getSession(destAddr net.Addr, tlsConfig *tls.Config, so
return nil, err
}
config := &quic.Config{
quicConfig := &quic.Config{
Versions: []quic.VersionNumber{quic.VersionMilestone0_10_0},
ConnectionIDLength: 12,
KeepAlive: true,
}
session, err := quic.DialContext(context.Background(), conn, destAddr, "", tlsConfig.GetTLSConfig(tls.WithDestination(dest)), config)
conn, err := wrapSysConn(rawConn, config)
if err != nil {
rawConn.Close()
return nil, err
}
session, err := quic.DialContext(context.Background(), conn, destAddr, "", tlsConfig.GetTLSConfig(tls.WithDestination(dest)), quicConfig)
if err != nil {
rawConn.Close()
return nil, err
}
@ -60,7 +67,10 @@ var client clientSessions
func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) {
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
if tlsConfig == nil {
return nil, newError("TLS not enabled for QUIC")
tlsConfig = &tls.Config{
ServerName: internalDomain,
AllowInsecure: true,
}
}
destAddr, err := net.ResolveUDPAddr("udp", dest.NetAddr())
@ -68,7 +78,9 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
return nil, err
}
session, err := client.getSession(destAddr, tlsConfig, streamSettings.SocketSettings)
config := streamSettings.ProtocolSettings.(*Config)
session, err := client.getSession(destAddr, config, tlsConfig, streamSettings.SocketSettings)
if err != nil {
return nil, err
}

View File

@ -2,5 +2,8 @@ package quic
import "v2ray.com/core/common/errors"
type errPathObjHolder struct {}
func newError(values ...interface{}) *errors.Error { return errors.New(values...).WithPathObj(errPathObjHolder{}) }
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

View File

@ -6,6 +6,7 @@ import (
quic "github.com/lucas-clemente/quic-go"
"v2ray.com/core/common"
"v2ray.com/core/common/net"
"v2ray.com/core/common/protocol/tls/cert"
"v2ray.com/core/transport/internet"
"v2ray.com/core/transport/internet/tls"
)
@ -66,10 +67,13 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
if tlsConfig == nil {
return nil, newError("TLS config not enabled for QUIC")
tlsConfig = &tls.Config{
Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil, cert.DNSNames(internalDomain), cert.CommonName(internalDomain)))},
}
}
conn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{
config := streamSettings.ProtocolSettings.(*Config)
rawConn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{
IP: address.IP(),
Port: int(port),
}, streamSettings.SocketSettings)
@ -78,15 +82,22 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
return nil, err
}
config := &quic.Config{
quicConfig := &quic.Config{
Versions: []quic.VersionNumber{quic.VersionMilestone0_10_0},
ConnectionIDLength: 12,
KeepAlive: true,
AcceptCookie: func(net.Addr, *quic.Cookie) bool { return true },
}
qListener, err := quic.Listen(conn, tlsConfig.GetTLSConfig(), config)
conn, err := wrapSysConn(rawConn, config)
if err != nil {
conn.Close()
return nil, err
}
qListener, err := quic.Listen(conn, tlsConfig.GetTLSConfig(), quicConfig)
if err != nil {
rawConn.Close()
return nil, err
}

View File

@ -14,6 +14,7 @@ import (
//
const protocolName = "quic"
const internalDomain = "quic.internal.v2ray.com"
func init() {
common.Must(internet.RegisterProtocolConfigCreatorByName(protocolName, func() interface{} {

View File

@ -9,9 +9,12 @@ import (
"v2ray.com/core/common"
"v2ray.com/core/common/buf"
"v2ray.com/core/common/net"
"v2ray.com/core/common/protocol"
"v2ray.com/core/common/protocol/tls/cert"
"v2ray.com/core/common/serial"
"v2ray.com/core/testing/servers/udp"
"v2ray.com/core/transport/internet"
"v2ray.com/core/transport/internet/headers/wireguard"
"v2ray.com/core/transport/internet/quic"
"v2ray.com/core/transport/internet/tls"
. "v2ray.com/ext/assert"
@ -87,3 +90,139 @@ func TestQuicConnection(t *testing.T) {
common.Must2(b2.ReadFullFrom(conn, N))
assert(b2.Bytes(), Equals, b1)
}
func TestQuicConnectionWithoutTLS(t *testing.T) {
assert := With(t)
port := udp.PickPort()
listener, err := quic.Listen(context.Background(), net.LocalHostIP, port, &internet.MemoryStreamConfig{
ProtocolName: "quic",
ProtocolSettings: &quic.Config{},
}, func(conn internet.Connection) {
go func() {
defer conn.Close()
b := buf.New()
defer b.Release()
for {
b.Clear()
if _, err := b.ReadFrom(conn); err != nil {
return
}
nBytes, err := conn.Write(b.Bytes())
assert(err, IsNil)
assert(int32(nBytes), Equals, b.Len())
}
}()
})
assert(err, IsNil)
defer listener.Close()
time.Sleep(time.Second)
dctx := context.Background()
conn, err := quic.Dial(dctx, net.TCPDestination(net.LocalHostIP, port), &internet.MemoryStreamConfig{
ProtocolName: "quic",
ProtocolSettings: &quic.Config{},
})
assert(err, IsNil)
defer conn.Close()
const N = 1024
b1 := make([]byte, N)
common.Must2(rand.Read(b1))
b2 := buf.New()
nBytes, err := conn.Write(b1)
assert(nBytes, Equals, N)
assert(err, IsNil)
b2.Clear()
common.Must2(b2.ReadFullFrom(conn, N))
assert(b2.Bytes(), Equals, b1)
nBytes, err = conn.Write(b1)
assert(nBytes, Equals, N)
assert(err, IsNil)
b2.Clear()
common.Must2(b2.ReadFullFrom(conn, N))
assert(b2.Bytes(), Equals, b1)
}
func TestQuicConnectionAuthHeader(t *testing.T) {
assert := With(t)
port := udp.PickPort()
listener, err := quic.Listen(context.Background(), net.LocalHostIP, port, &internet.MemoryStreamConfig{
ProtocolName: "quic",
ProtocolSettings: &quic.Config{
Header: serial.ToTypedMessage(&wireguard.WireguardConfig{}),
Key: "abcd",
Security: &protocol.SecurityConfig{
Type: protocol.SecurityType_AES128_GCM,
},
},
}, func(conn internet.Connection) {
go func() {
defer conn.Close()
b := buf.New()
defer b.Release()
for {
b.Clear()
if _, err := b.ReadFrom(conn); err != nil {
return
}
nBytes, err := conn.Write(b.Bytes())
assert(err, IsNil)
assert(int32(nBytes), Equals, b.Len())
}
}()
})
assert(err, IsNil)
defer listener.Close()
time.Sleep(time.Second)
dctx := context.Background()
conn, err := quic.Dial(dctx, net.TCPDestination(net.LocalHostIP, port), &internet.MemoryStreamConfig{
ProtocolName: "quic",
ProtocolSettings: &quic.Config{
Header: serial.ToTypedMessage(&wireguard.WireguardConfig{}),
Key: "abcd",
Security: &protocol.SecurityConfig{
Type: protocol.SecurityType_AES128_GCM,
},
},
})
assert(err, IsNil)
defer conn.Close()
const N = 1024
b1 := make([]byte, N)
common.Must2(rand.Read(b1))
b2 := buf.New()
nBytes, err := conn.Write(b1)
assert(nBytes, Equals, N)
assert(err, IsNil)
b2.Clear()
common.Must2(b2.ReadFullFrom(conn, N))
assert(b2.Bytes(), Equals, b1)
nBytes, err = conn.Write(b1)
assert(nBytes, Equals, N)
assert(err, IsNil)
b2.Clear()
common.Must2(b2.ReadFullFrom(conn, N))
assert(b2.Bytes(), Equals, b1)
}

View File

@ -65,7 +65,7 @@ type ApplicationErrorCode uint16
// ethernet's max size, minus the IP and UDP headers. IPv6 has a 40 byte header,
// UDP adds an additional 8 bytes. This is a total overhead of 48 bytes.
// Ethernet's max packet size is 1500 bytes, 1500 - 48 = 1452.
const MaxReceivePacketSize ByteCount = 1452 - 32
const MaxReceivePacketSize ByteCount = 1452 - 64
// DefaultTCPMSS is the default maximum packet size used in the Linux TCP implementation.
// Used in QUIC for congestion window computations in bytes.