diff --git a/go.mod b/go.mod index be3148b1b..f3b5e9d29 100644 --- a/go.mod +++ b/go.mod @@ -13,11 +13,12 @@ require ( github.com/seiflotfy/cuckoofilter v0.0.0-20200511222245-56093a4d3841 github.com/stretchr/testify v1.6.1 github.com/xiaokangwang/VSign v0.0.0-20200828155424-dc1c86b73fbf + github.com/xtls/go v0.0.0-20200921133830-416584838a0f go.starlark.net v0.0.0-20200901195727-6e684ef5eeee golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a - golang.org/x/net v0.0.0-20200822124328-c89045814202 + golang.org/x/net v0.0.0-20200904194848-62affa334b73 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 - golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a + golang.org/x/sys v0.0.0-20200918174421-af09f7315aff google.golang.org/grpc v1.32.0 google.golang.org/protobuf v1.25.0 h12.io/socks v1.0.1 diff --git a/go.sum b/go.sum index 32160f707..a1ff9f8f2 100644 --- a/go.sum +++ b/go.sum @@ -6,7 +6,6 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 h1:BS21ZUJ/B5X2UVUbczfmdWH7GapPWAhxcMsDnjJTU1E= github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= @@ -18,7 +17,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -36,28 +34,24 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364 h1:5XxdakFhqd9dnXoAZy1Mb2R/DZ6D1e+0bGC/JhucGYI= github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c= -github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo= github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pires/go-proxyproto v0.1.3 h1:2XEuhsQluSNA5QIQkiUv8PfgZ51sNYIQkq/yFquiSQM= github.com/pires/go-proxyproto v0.1.3/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/seiflotfy/cuckoofilter v0.0.0-20200511222245-56093a4d3841 h1:pnfutQFsV7ySmHUeX6ANGfPsBo29RctUvDn8G3rmJVw= github.com/seiflotfy/cuckoofilter v0.0.0-20200511222245-56093a4d3841/go.mod h1:ET5mVvNjwaGXRgZxO9UZr7X+8eAf87AfIYNwRSp9s4Y= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/xiaokangwang/VSign v0.0.0-20200828155424-dc1c86b73fbf h1:d4keT3SwLbrgnEe2zbtijPLgKE15n0ZbvJZzRH/a9GM= github.com/xiaokangwang/VSign v0.0.0-20200828155424-dc1c86b73fbf/go.mod h1:jTwBnzBuqZP3VX/Z65ErYb9zd4anQprSC7N38TmAp1E= +github.com/xtls/go v0.0.0-20200921133830-416584838a0f h1:HNJx0SKT77PmtX0Xj8Ep5ak3cIG19ZFxCYkMa2yJfSg= +github.com/xtls/go v0.0.0-20200921133830-416584838a0f/go.mod h1:5TB2+k58gx4A4g2Nf5miSHNDF6CuAzHKpWBooLAshTs= go.starlark.net v0.0.0-20200901195727-6e684ef5eeee h1:N4eRtIIYHZE5Mw/Km/orb+naLdwAe+lv2HCxRR5rEBw= go.starlark.net v0.0.0-20200901195727-6e684ef5eeee/go.mod h1:f0znQkUKRrkk36XxWbGjMqQM8wGv/xHBVE2qc3B5oFU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -77,13 +71,12 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -91,8 +84,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a h1:i47hUS795cOydZI4AwJQCKXOr4BvxzvikwDoDtHhP2Y= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200918174421-af09f7315aff h1:1CPUrky56AcgSpxz/KfgzQWzfG09u5YOL8MvPYBlrL8= +golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -102,7 +95,6 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -114,8 +106,6 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.31.1 h1:SfXqXS5hkufcdZ/mHtYCh53P2b+92WQq/DZcKLgsFRs= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -128,11 +118,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -h12.io/socks v1.0.1 h1:bXESSI/+hbdrp+22vcc7/JiXjmLH4UWktKdYgGr3ShA= h12.io/socks v1.0.1/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 14655fe5d..44efd7f9e 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -16,6 +16,7 @@ import ( "v2ray.com/core/transport/internet/tcp" "v2ray.com/core/transport/internet/tls" "v2ray.com/core/transport/internet/websocket" + "v2ray.com/core/transport/internet/xtls" ) var ( @@ -168,6 +169,7 @@ type HTTPConfig struct { Path string `json:"path"` } +// Build implements Buildable. func (c *HTTPConfig) Build() (proto.Message, error) { config := &http.Config{ Path: c.Path, @@ -184,6 +186,7 @@ type QUICConfig struct { Key string `json:"key"` } +// Build implements Buildable. func (c *QUICConfig) Build() (proto.Message, error) { config := &quic.Config{ Key: c.Key, @@ -225,6 +228,7 @@ type DomainSocketConfig struct { AcceptProxyProtocol bool `json:"acceptProxyProtocol"` } +// Build implements Buildable. func (c *DomainSocketConfig) Build() (proto.Message, error) { return &domainsocket.Config{ Path: c.Path, @@ -234,14 +238,6 @@ func (c *DomainSocketConfig) Build() (proto.Message, error) { }, nil } -type TLSCertConfig struct { - CertFile string `json:"certificateFile"` - CertStr []string `json:"certificate"` - KeyFile string `json:"keyFile"` - KeyStr []string `json:"key"` - Usage string `json:"usage"` -} - func readFileOrString(f string, s []string) ([]byte, error) { if len(f) > 0 { return filesystem.ReadFile(f) @@ -252,6 +248,15 @@ func readFileOrString(f string, s []string) ([]byte, error) { return nil, newError("both file and bytes are empty.") } +type TLSCertConfig struct { + CertFile string `json:"certificateFile"` + CertStr []string `json:"certificate"` + KeyFile string `json:"keyFile"` + KeyStr []string `json:"key"` + Usage string `json:"usage"` +} + +// Build implements Buildable. func (c *TLSCertConfig) Build() (*tls.Certificate, error) { certificate := new(tls.Certificate) @@ -318,6 +323,81 @@ func (c *TLSConfig) Build() (proto.Message, error) { return config, nil } +type XTLSCertConfig struct { + CertFile string `json:"certificateFile"` + CertStr []string `json:"certificate"` + KeyFile string `json:"keyFile"` + KeyStr []string `json:"key"` + Usage string `json:"usage"` +} + +// Build implements Buildable. +func (c *XTLSCertConfig) Build() (*xtls.Certificate, error) { + certificate := new(xtls.Certificate) + + cert, err := readFileOrString(c.CertFile, c.CertStr) + if err != nil { + return nil, newError("failed to parse certificate").Base(err) + } + certificate.Certificate = cert + + if len(c.KeyFile) > 0 || len(c.KeyStr) > 0 { + key, err := readFileOrString(c.KeyFile, c.KeyStr) + if err != nil { + return nil, newError("failed to parse key").Base(err) + } + certificate.Key = key + } + + switch strings.ToLower(c.Usage) { + case "encipherment": + certificate.Usage = xtls.Certificate_ENCIPHERMENT + case "verify": + certificate.Usage = xtls.Certificate_AUTHORITY_VERIFY + case "issue": + certificate.Usage = xtls.Certificate_AUTHORITY_ISSUE + default: + certificate.Usage = xtls.Certificate_ENCIPHERMENT + } + + return certificate, nil +} + +type XTLSConfig struct { + Insecure bool `json:"allowInsecure"` + InsecureCiphers bool `json:"allowInsecureCiphers"` + Certs []*XTLSCertConfig `json:"certificates"` + ServerName string `json:"serverName"` + ALPN *StringList `json:"alpn"` + DisableSessionResumption bool `json:"disableSessionResumption"` + DisableSystemRoot bool `json:"disableSystemRoot"` +} + +// Build implements Buildable. +func (c *XTLSConfig) Build() (proto.Message, error) { + config := new(xtls.Config) + config.Certificate = make([]*xtls.Certificate, len(c.Certs)) + for idx, certConf := range c.Certs { + cert, err := certConf.Build() + if err != nil { + return nil, err + } + config.Certificate[idx] = cert + } + serverName := c.ServerName + config.AllowInsecure = c.Insecure + config.AllowInsecureCiphers = c.InsecureCiphers + if len(c.ServerName) > 0 { + config.ServerName = serverName + } + if c.ALPN != nil && len(*c.ALPN) > 0 { + config.NextProtocol = []string(*c.ALPN) + } + config.DisableSessionResumption = c.DisableSessionResumption + config.DisableSystemRoot = c.DisableSystemRoot + return config, nil +} + type TransportProtocol string // Build implements Buildable. @@ -346,6 +426,7 @@ type SocketConfig struct { TProxy string `json:"tproxy"` } +// Build implements Buildable. func (c *SocketConfig) Build() (*internet.SocketConfig, error) { var tfoSettings internet.SocketConfig_TCPFastOpenState if c.TFO != nil { @@ -376,6 +457,7 @@ type StreamConfig struct { Network *TransportProtocol `json:"network"` Security string `json:"security"` TLSSettings *TLSConfig `json:"tlsSettings"` + XTLSSettings *XTLSConfig `json:"xtlsSettings"` TCPSettings *TCPConfig `json:"tcpSettings"` KCPSettings *KCPConfig `json:"kcpSettings"` WSSettings *WebSocketConfig `json:"wsSettings"` @@ -400,6 +482,9 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) { if strings.EqualFold(c.Security, "tls") { tlsSettings := c.TLSSettings if tlsSettings == nil { + if c.XTLSSettings != nil { + return nil, newError(`TLS: Please use "tlsSettings" instead of "xtlsSettings".`) + } tlsSettings = &TLSConfig{} } ts, err := tlsSettings.Build() @@ -410,6 +495,25 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) { config.SecuritySettings = append(config.SecuritySettings, tm) config.SecurityType = tm.Type } + if strings.EqualFold(c.Security, "xtls") { + if config.ProtocolName != "tcp" { + return nil, newError("XTLS only supports TCP for now.") + } + xtlsSettings := c.XTLSSettings + if xtlsSettings == nil { + if c.TLSSettings != nil { + return nil, newError(`XTLS: Please use "xtlsSettings" instead of "tlsSettings".`) + } + xtlsSettings = &XTLSConfig{} + } + ts, err := xtlsSettings.Build() + if err != nil { + return nil, newError("Failed to build XTLS config.").Base(err) + } + tm := serial.ToTypedMessage(ts) + config.SecuritySettings = append(config.SecuritySettings, tm) + config.SecurityType = tm.Type + } if c.TCPSettings != nil { ts, err := c.TCPSettings.Build() if err != nil { @@ -463,7 +567,7 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) { if c.QUICSettings != nil { qs, err := c.QUICSettings.Build() if err != nil { - return nil, newError("failed to build QUIC config").Base(err) + return nil, newError("Failed to build QUIC config").Base(err) } config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{ ProtocolName: "quic", @@ -473,7 +577,7 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) { if c.SocketSettings != nil { ss, err := c.SocketSettings.Build() if err != nil { - return nil, newError("failed to build sockopt").Base(err) + return nil, newError("Failed to build sockopt").Base(err) } config.SocketSettings = ss } diff --git a/infra/conf/v2ray.go b/infra/conf/v2ray.go index f8499923b..70eb2b616 100644 --- a/infra/conf/v2ray.go +++ b/infra/conf/v2ray.go @@ -11,6 +11,7 @@ import ( "v2ray.com/core/app/proxyman" "v2ray.com/core/app/stats" "v2ray.com/core/common/serial" + "v2ray.com/core/transport/internet/xtls" ) var ( @@ -59,6 +60,7 @@ type SniffingConfig struct { DestOverride *StringList `json:"destOverride"` } +// Build implements Buildable. func (c *SniffingConfig) Build() (*proxyman.SniffingConfig, error) { var p []string if c.DestOverride != nil { @@ -184,6 +186,9 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) { if err != nil { return nil, err } + if ss.SecurityType == serial.GetMessageType(&xtls.Config{}) && !strings.EqualFold(c.Protocol, "vless") { + return nil, newError("XTLS only supports VLESS for now.") + } receiverSettings.StreamSettings = ss } if c.SniffingConfig != nil { @@ -251,6 +256,9 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) { if err != nil { return nil, err } + if ss.SecurityType == serial.GetMessageType(&xtls.Config{}) && !strings.EqualFold(c.Protocol, "vless") { + return nil, newError("XTLS only supports VLESS for now.") + } senderSettings.StreamSettings = ss } @@ -263,7 +271,15 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) { } if c.MuxSettings != nil { - senderSettings.MultiplexSettings = c.MuxSettings.Build() + ms := c.MuxSettings.Build() + if ms != nil && ms.Enabled { + if ss := senderSettings.StreamSettings; ss != nil { + if ss.SecurityType == serial.GetMessageType(&xtls.Config{}) { + return nil, newError("XTLS doesn't support Mux for now.") + } + } + } + senderSettings.MultiplexSettings = ms } settings := []byte("{}") @@ -288,6 +304,7 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) { type StatsConfig struct{} +// Build implements Buildable. func (c *StatsConfig) Build() (*stats.Config, error) { return &stats.Config{}, nil } diff --git a/infra/conf/v2ray_test.go b/infra/conf/v2ray_test.go index 8c59a7ae8..e51a42c58 100644 --- a/infra/conf/v2ray_test.go +++ b/infra/conf/v2ray_test.go @@ -404,39 +404,39 @@ func TestConfig_Override(t *testing.T) { }, }, {"combine/newattr", - &Config{InboundConfigs: []InboundDetourConfig{InboundDetourConfig{Tag: "old"}}}, + &Config{InboundConfigs: []InboundDetourConfig{{Tag: "old"}}}, &Config{LogConfig: &LogConfig{}}, "", - &Config{LogConfig: &LogConfig{}, InboundConfigs: []InboundDetourConfig{InboundDetourConfig{Tag: "old"}}}}, + &Config{LogConfig: &LogConfig{}, InboundConfigs: []InboundDetourConfig{{Tag: "old"}}}}, {"replace/inbounds", - &Config{InboundConfigs: []InboundDetourConfig{InboundDetourConfig{Tag: "pos0"}, InboundDetourConfig{Protocol: "vmess", Tag: "pos1"}}}, - &Config{InboundConfigs: []InboundDetourConfig{InboundDetourConfig{Tag: "pos1", Protocol: "kcp"}}}, + &Config{InboundConfigs: []InboundDetourConfig{{Tag: "pos0"}, {Protocol: "vmess", Tag: "pos1"}}}, + &Config{InboundConfigs: []InboundDetourConfig{{Tag: "pos1", Protocol: "kcp"}}}, "", - &Config{InboundConfigs: []InboundDetourConfig{InboundDetourConfig{Tag: "pos0"}, InboundDetourConfig{Tag: "pos1", Protocol: "kcp"}}}}, + &Config{InboundConfigs: []InboundDetourConfig{{Tag: "pos0"}, {Tag: "pos1", Protocol: "kcp"}}}}, {"replace/inbounds-replaceall", - &Config{InboundConfigs: []InboundDetourConfig{InboundDetourConfig{Tag: "pos0"}, InboundDetourConfig{Protocol: "vmess", Tag: "pos1"}}}, - &Config{InboundConfigs: []InboundDetourConfig{InboundDetourConfig{Tag: "pos1", Protocol: "kcp"}, InboundDetourConfig{Tag: "pos2", Protocol: "kcp"}}}, + &Config{InboundConfigs: []InboundDetourConfig{{Tag: "pos0"}, {Protocol: "vmess", Tag: "pos1"}}}, + &Config{InboundConfigs: []InboundDetourConfig{{Tag: "pos1", Protocol: "kcp"}, {Tag: "pos2", Protocol: "kcp"}}}, "", - &Config{InboundConfigs: []InboundDetourConfig{InboundDetourConfig{Tag: "pos1", Protocol: "kcp"}, InboundDetourConfig{Tag: "pos2", Protocol: "kcp"}}}}, + &Config{InboundConfigs: []InboundDetourConfig{{Tag: "pos1", Protocol: "kcp"}, {Tag: "pos2", Protocol: "kcp"}}}}, {"replace/notag-append", - &Config{InboundConfigs: []InboundDetourConfig{InboundDetourConfig{}, InboundDetourConfig{Protocol: "vmess"}}}, - &Config{InboundConfigs: []InboundDetourConfig{InboundDetourConfig{Tag: "pos1", Protocol: "kcp"}}}, + &Config{InboundConfigs: []InboundDetourConfig{{}, {Protocol: "vmess"}}}, + &Config{InboundConfigs: []InboundDetourConfig{{Tag: "pos1", Protocol: "kcp"}}}, "", - &Config{InboundConfigs: []InboundDetourConfig{InboundDetourConfig{}, InboundDetourConfig{Protocol: "vmess"}, InboundDetourConfig{Tag: "pos1", Protocol: "kcp"}}}}, + &Config{InboundConfigs: []InboundDetourConfig{{}, {Protocol: "vmess"}, {Tag: "pos1", Protocol: "kcp"}}}}, {"replace/outbounds", - &Config{OutboundConfigs: []OutboundDetourConfig{OutboundDetourConfig{Tag: "pos0"}, OutboundDetourConfig{Protocol: "vmess", Tag: "pos1"}}}, - &Config{OutboundConfigs: []OutboundDetourConfig{OutboundDetourConfig{Tag: "pos1", Protocol: "kcp"}}}, + &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos0"}, {Protocol: "vmess", Tag: "pos1"}}}, + &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos1", Protocol: "kcp"}}}, "", - &Config{OutboundConfigs: []OutboundDetourConfig{OutboundDetourConfig{Tag: "pos0"}, OutboundDetourConfig{Tag: "pos1", Protocol: "kcp"}}}}, + &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos0"}, {Tag: "pos1", Protocol: "kcp"}}}}, {"replace/outbounds-prepend", - &Config{OutboundConfigs: []OutboundDetourConfig{OutboundDetourConfig{Tag: "pos0"}, OutboundDetourConfig{Protocol: "vmess", Tag: "pos1"}}}, - &Config{OutboundConfigs: []OutboundDetourConfig{OutboundDetourConfig{Tag: "pos1", Protocol: "kcp"}, OutboundDetourConfig{Tag: "pos2", Protocol: "kcp"}}}, + &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos0"}, {Protocol: "vmess", Tag: "pos1"}}}, + &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos1", Protocol: "kcp"}, {Tag: "pos2", Protocol: "kcp"}}}, "config.json", - &Config{OutboundConfigs: []OutboundDetourConfig{OutboundDetourConfig{Tag: "pos1", Protocol: "kcp"}, OutboundDetourConfig{Tag: "pos2", Protocol: "kcp"}}}}, + &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos1", Protocol: "kcp"}, {Tag: "pos2", Protocol: "kcp"}}}}, {"replace/outbounds-append", - &Config{OutboundConfigs: []OutboundDetourConfig{OutboundDetourConfig{Tag: "pos0"}, OutboundDetourConfig{Protocol: "vmess", Tag: "pos1"}}}, - &Config{OutboundConfigs: []OutboundDetourConfig{OutboundDetourConfig{Tag: "pos2", Protocol: "kcp"}}}, + &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos0"}, {Protocol: "vmess", Tag: "pos1"}}}, + &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos2", Protocol: "kcp"}}}, "config_tail.json", - &Config{OutboundConfigs: []OutboundDetourConfig{OutboundDetourConfig{Tag: "pos0"}, OutboundDetourConfig{Protocol: "vmess", Tag: "pos1"}, OutboundDetourConfig{Tag: "pos2", Protocol: "kcp"}}}}, + &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos0"}, {Protocol: "vmess", Tag: "pos1"}, {Tag: "pos2", Protocol: "kcp"}}}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/main/distro/all/all.go b/main/distro/all/all.go index 9df5b6ac2..a71fdf5da 100644 --- a/main/distro/all/all.go +++ b/main/distro/all/all.go @@ -45,6 +45,7 @@ import ( _ "v2ray.com/core/transport/internet/tls" _ "v2ray.com/core/transport/internet/udp" _ "v2ray.com/core/transport/internet/websocket" + _ "v2ray.com/core/transport/internet/xtls" // Transport headers _ "v2ray.com/core/transport/internet/headers/http" diff --git a/transport/internet/tcp/dialer.go b/transport/internet/tcp/dialer.go index e6f414e0b..6c744dead 100644 --- a/transport/internet/tcp/dialer.go +++ b/transport/internet/tcp/dialer.go @@ -10,6 +10,7 @@ import ( "v2ray.com/core/common/session" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/tls" + "v2ray.com/core/transport/internet/xtls" ) // Dial dials a new TCP connection to the given destination. @@ -30,6 +31,9 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me } */ conn = tls.Client(conn, tlsConfig) + } else if config := xtls.ConfigFromStreamSettings(streamSettings); config != nil { + xtlsConfig := config.GetXTLSConfig(xtls.WithDestination(dest)) + conn = xtls.Client(conn, xtlsConfig) } tcpSettings := streamSettings.ProtocolSettings.(*Config) diff --git a/transport/internet/tcp/hub.go b/transport/internet/tcp/hub.go index 7813c5e4f..de13e76f8 100644 --- a/transport/internet/tcp/hub.go +++ b/transport/internet/tcp/hub.go @@ -9,18 +9,21 @@ import ( "time" "github.com/pires/go-proxyproto" + goxtls "github.com/xtls/go" "v2ray.com/core/common" "v2ray.com/core/common/net" "v2ray.com/core/common/session" "v2ray.com/core/transport/internet" "v2ray.com/core/transport/internet/tls" + "v2ray.com/core/transport/internet/xtls" ) // Listener is an internet.Listener that listens for TCP connections. type Listener struct { listener net.Listener tlsConfig *gotls.Config + xtlsConfig *goxtls.Config authConfig internet.ConnectionAuthenticator config *Config addConn internet.ConnHandler @@ -59,6 +62,9 @@ func ListenTCP(ctx context.Context, address net.Address, port net.Port, streamSe if config := tls.ConfigFromStreamSettings(streamSettings); config != nil { l.tlsConfig = config.GetTLSConfig(tls.WithNextProto("h2")) } + if config := xtls.ConfigFromStreamSettings(streamSettings); config != nil { + l.xtlsConfig = config.GetXTLSConfig(xtls.WithNextProto("h2")) + } if tcpSettings.HeaderSettings != nil { headerConfig, err := tcpSettings.HeaderSettings.GetInstance() @@ -93,6 +99,8 @@ func (v *Listener) keepAccepting() { if v.tlsConfig != nil { conn = tls.Server(conn, v.tlsConfig) + } else if v.xtlsConfig != nil { + conn = xtls.Server(conn, v.xtlsConfig) } if v.authConfig != nil { conn = v.authConfig.Server(conn) diff --git a/transport/internet/xtls/config.go b/transport/internet/xtls/config.go new file mode 100644 index 000000000..580d1cead --- /dev/null +++ b/transport/internet/xtls/config.go @@ -0,0 +1,231 @@ +// +build !confonly + +package xtls + +import ( + "crypto/x509" + "sync" + "time" + + xtls "github.com/xtls/go" + + "v2ray.com/core/common/net" + "v2ray.com/core/common/protocol/tls/cert" + "v2ray.com/core/transport/internet" +) + +var ( + globalSessionCache = xtls.NewLRUClientSessionCache(128) +) + +// ParseCertificate converts a cert.Certificate to Certificate. +func ParseCertificate(c *cert.Certificate) *Certificate { + certPEM, keyPEM := c.ToPEM() + return &Certificate{ + Certificate: certPEM, + Key: keyPEM, + } +} + +func (c *Config) loadSelfCertPool() (*x509.CertPool, error) { + root := x509.NewCertPool() + for _, cert := range c.Certificate { + if !root.AppendCertsFromPEM(cert.Certificate) { + return nil, newError("failed to append cert").AtWarning() + } + } + return root, nil +} + +// BuildCertificates builds a list of TLS certificates from proto definition. +func (c *Config) BuildCertificates() []xtls.Certificate { + certs := make([]xtls.Certificate, 0, len(c.Certificate)) + for _, entry := range c.Certificate { + if entry.Usage != Certificate_ENCIPHERMENT { + continue + } + keyPair, err := xtls.X509KeyPair(entry.Certificate, entry.Key) + if err != nil { + newError("ignoring invalid X509 key pair").Base(err).AtWarning().WriteToLog() + continue + } + certs = append(certs, keyPair) + } + return certs +} + +func isCertificateExpired(c *xtls.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)) +} + +func issueCertificate(rawCA *Certificate, domain string) (*xtls.Certificate, error) { + parent, err := cert.ParseCertificate(rawCA.Certificate, rawCA.Key) + if err != nil { + return nil, newError("failed to parse raw certificate").Base(err) + } + newCert, err := cert.Generate(parent, cert.CommonName(domain), cert.DNSNames(domain)) + if err != nil { + return nil, newError("failed to generate new certificate for ", domain).Base(err) + } + newCertPEM, newKeyPEM := newCert.ToPEM() + cert, err := xtls.X509KeyPair(newCertPEM, newKeyPEM) + return &cert, err +} + +func (c *Config) getCustomCA() []*Certificate { + certs := make([]*Certificate, 0, len(c.Certificate)) + for _, certificate := range c.Certificate { + if certificate.Usage == Certificate_AUTHORITY_ISSUE { + certs = append(certs, certificate) + } + } + return certs +} + +func getGetCertificateFunc(c *xtls.Config, ca []*Certificate) func(hello *xtls.ClientHelloInfo) (*xtls.Certificate, error) { + var access sync.RWMutex + + return func(hello *xtls.ClientHelloInfo) (*xtls.Certificate, error) { + domain := hello.ServerName + certExpired := false + + access.RLock() + certificate, found := c.NameToCertificate[domain] + access.RUnlock() + + if found { + if !isCertificateExpired(certificate) { + return certificate, nil + } + certExpired = true + } + + if certExpired { + newCerts := make([]xtls.Certificate, 0, len(c.Certificates)) + + access.Lock() + for _, certificate := range c.Certificates { + if !isCertificateExpired(&certificate) { + newCerts = append(newCerts, certificate) + } + } + + c.Certificates = newCerts + access.Unlock() + } + + var issuedCertificate *xtls.Certificate + + // Create a new certificate from existing CA if possible + for _, rawCert := range ca { + if rawCert.Usage == Certificate_AUTHORITY_ISSUE { + newCert, err := issueCertificate(rawCert, domain) + if err != nil { + newError("failed to issue new certificate for ", domain).Base(err).WriteToLog() + continue + } + + access.Lock() + c.Certificates = append(c.Certificates, *newCert) + issuedCertificate = &c.Certificates[len(c.Certificates)-1] + access.Unlock() + break + } + } + + if issuedCertificate == nil { + return nil, newError("failed to create a new certificate for ", domain) + } + + access.Lock() + c.BuildNameToCertificate() + access.Unlock() + + return issuedCertificate, nil + } +} + +func (c *Config) parseServerName() string { + return c.ServerName +} + +// GetXTLSConfig converts this Config into xtls.Config. +func (c *Config) GetXTLSConfig(opts ...Option) *xtls.Config { + root, err := c.getCertPool() + if err != nil { + newError("failed to load system root certificate").AtError().Base(err).WriteToLog() + } + + config := &xtls.Config{ + ClientSessionCache: globalSessionCache, + RootCAs: root, + InsecureSkipVerify: c.AllowInsecure, + NextProtos: c.NextProtocol, + SessionTicketsDisabled: c.DisableSessionResumption, + } + if c == nil { + return config + } + + for _, opt := range opts { + opt(config) + } + + config.Certificates = c.BuildCertificates() + config.BuildNameToCertificate() + + caCerts := c.getCustomCA() + if len(caCerts) > 0 { + config.GetCertificate = getGetCertificateFunc(config, caCerts) + } + + if sn := c.parseServerName(); len(sn) > 0 { + config.ServerName = sn + } + + if len(config.NextProtos) == 0 { + config.NextProtos = []string{"h2", "http/1.1"} + } + + return config +} + +// Option for building XTLS config. +type Option func(*xtls.Config) + +// WithDestination sets the server name in XTLS config. +func WithDestination(dest net.Destination) Option { + return func(config *xtls.Config) { + if dest.Address.Family().IsDomain() && config.ServerName == "" { + config.ServerName = dest.Address.Domain() + } + } +} + +// WithNextProto sets the ALPN values in XTLS config. +func WithNextProto(protocol ...string) Option { + return func(config *xtls.Config) { + if len(config.NextProtos) == 0 { + config.NextProtos = protocol + } + } +} + +// ConfigFromStreamSettings fetches Config from stream settings. Nil if not found. +func ConfigFromStreamSettings(settings *internet.MemoryStreamConfig) *Config { + if settings == nil { + return nil + } + config, ok := settings.SecuritySettings.(*Config) + if !ok { + return nil + } + return config +} diff --git a/transport/internet/xtls/config.pb.go b/transport/internet/xtls/config.pb.go new file mode 100644 index 000000000..6ec1e71ed --- /dev/null +++ b/transport/internet/xtls/config.pb.go @@ -0,0 +1,378 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.13.0 +// source: transport/internet/xtls/config.proto + +package xtls + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type Certificate_Usage int32 + +const ( + Certificate_ENCIPHERMENT Certificate_Usage = 0 + Certificate_AUTHORITY_VERIFY Certificate_Usage = 1 + Certificate_AUTHORITY_ISSUE Certificate_Usage = 2 +) + +// Enum value maps for Certificate_Usage. +var ( + Certificate_Usage_name = map[int32]string{ + 0: "ENCIPHERMENT", + 1: "AUTHORITY_VERIFY", + 2: "AUTHORITY_ISSUE", + } + Certificate_Usage_value = map[string]int32{ + "ENCIPHERMENT": 0, + "AUTHORITY_VERIFY": 1, + "AUTHORITY_ISSUE": 2, + } +) + +func (x Certificate_Usage) Enum() *Certificate_Usage { + p := new(Certificate_Usage) + *p = x + return p +} + +func (x Certificate_Usage) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Certificate_Usage) Descriptor() protoreflect.EnumDescriptor { + return file_transport_internet_xtls_config_proto_enumTypes[0].Descriptor() +} + +func (Certificate_Usage) Type() protoreflect.EnumType { + return &file_transport_internet_xtls_config_proto_enumTypes[0] +} + +func (x Certificate_Usage) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Certificate_Usage.Descriptor instead. +func (Certificate_Usage) EnumDescriptor() ([]byte, []int) { + return file_transport_internet_xtls_config_proto_rawDescGZIP(), []int{0, 0} +} + +type Certificate struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // XTLS certificate in x509 format. + Certificate []byte `protobuf:"bytes,1,opt,name=Certificate,proto3" json:"Certificate,omitempty"` + // XTLS key in x509 format. + Key []byte `protobuf:"bytes,2,opt,name=Key,proto3" json:"Key,omitempty"` + Usage Certificate_Usage `protobuf:"varint,3,opt,name=usage,proto3,enum=v2ray.core.transport.internet.xtls.Certificate_Usage" json:"usage,omitempty"` +} + +func (x *Certificate) Reset() { + *x = Certificate{} + if protoimpl.UnsafeEnabled { + mi := &file_transport_internet_xtls_config_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Certificate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Certificate) ProtoMessage() {} + +func (x *Certificate) ProtoReflect() protoreflect.Message { + mi := &file_transport_internet_xtls_config_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Certificate.ProtoReflect.Descriptor instead. +func (*Certificate) Descriptor() ([]byte, []int) { + return file_transport_internet_xtls_config_proto_rawDescGZIP(), []int{0} +} + +func (x *Certificate) GetCertificate() []byte { + if x != nil { + return x.Certificate + } + return nil +} + +func (x *Certificate) GetKey() []byte { + if x != nil { + return x.Key + } + return nil +} + +func (x *Certificate) GetUsage() Certificate_Usage { + if x != nil { + return x.Usage + } + return Certificate_ENCIPHERMENT +} + +type Config struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Whether or not to allow self-signed certificates. + AllowInsecure bool `protobuf:"varint,1,opt,name=allow_insecure,json=allowInsecure,proto3" json:"allow_insecure,omitempty"` + // Whether or not to allow insecure cipher suites. + AllowInsecureCiphers bool `protobuf:"varint,5,opt,name=allow_insecure_ciphers,json=allowInsecureCiphers,proto3" json:"allow_insecure_ciphers,omitempty"` + // List of certificates to be served on server. + Certificate []*Certificate `protobuf:"bytes,2,rep,name=certificate,proto3" json:"certificate,omitempty"` + // Override server name. + ServerName string `protobuf:"bytes,3,opt,name=server_name,json=serverName,proto3" json:"server_name,omitempty"` + // Lists of string as ALPN values. + NextProtocol []string `protobuf:"bytes,4,rep,name=next_protocol,json=nextProtocol,proto3" json:"next_protocol,omitempty"` + // Whether or not to disable session (ticket) resumption. + DisableSessionResumption bool `protobuf:"varint,6,opt,name=disable_session_resumption,json=disableSessionResumption,proto3" json:"disable_session_resumption,omitempty"` + // If true, root certificates on the system will not be loaded for verification. + DisableSystemRoot bool `protobuf:"varint,7,opt,name=disable_system_root,json=disableSystemRoot,proto3" json:"disable_system_root,omitempty"` +} + +func (x *Config) Reset() { + *x = Config{} + if protoimpl.UnsafeEnabled { + mi := &file_transport_internet_xtls_config_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Config) ProtoMessage() {} + +func (x *Config) ProtoReflect() protoreflect.Message { + mi := &file_transport_internet_xtls_config_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Config.ProtoReflect.Descriptor instead. +func (*Config) Descriptor() ([]byte, []int) { + return file_transport_internet_xtls_config_proto_rawDescGZIP(), []int{1} +} + +func (x *Config) GetAllowInsecure() bool { + if x != nil { + return x.AllowInsecure + } + return false +} + +func (x *Config) GetAllowInsecureCiphers() bool { + if x != nil { + return x.AllowInsecureCiphers + } + return false +} + +func (x *Config) GetCertificate() []*Certificate { + if x != nil { + return x.Certificate + } + return nil +} + +func (x *Config) GetServerName() string { + if x != nil { + return x.ServerName + } + return "" +} + +func (x *Config) GetNextProtocol() []string { + if x != nil { + return x.NextProtocol + } + return nil +} + +func (x *Config) GetDisableSessionResumption() bool { + if x != nil { + return x.DisableSessionResumption + } + return false +} + +func (x *Config) GetDisableSystemRoot() bool { + if x != nil { + return x.DisableSystemRoot + } + return false +} + +var File_transport_internet_xtls_config_proto protoreflect.FileDescriptor + +var file_transport_internet_xtls_config_proto_rawDesc = []byte{ + 0x0a, 0x24, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x22, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x78, 0x74, 0x6c, 0x73, 0x22, 0xd4, 0x01, 0x0a, 0x0b, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, + 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x4b, 0x65, 0x79, 0x12, 0x4b, + 0x0a, 0x05, 0x75, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x35, 0x2e, + 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x78, 0x74, + 0x6c, 0x73, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x2e, 0x55, + 0x73, 0x61, 0x67, 0x65, 0x52, 0x05, 0x75, 0x73, 0x61, 0x67, 0x65, 0x22, 0x44, 0x0a, 0x05, 0x55, + 0x73, 0x61, 0x67, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x4e, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, + 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, + 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, + 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, + 0x02, 0x22, 0xec, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, + 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, + 0x75, 0x72, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73, + 0x65, 0x63, 0x75, 0x72, 0x65, 0x5f, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x14, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, + 0x72, 0x65, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x73, 0x12, 0x51, 0x0a, 0x0b, 0x63, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, + 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x78, + 0x74, 0x6c, 0x73, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, + 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, + 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, + 0x6f, 0x6c, 0x12, 0x3c, 0x0a, 0x1a, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x73, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6d, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x53, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x73, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x64, + 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x6f, 0x6f, 0x74, + 0x42, 0x77, 0x0a, 0x26, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x78, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x26, 0x76, 0x32, + 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, + 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, + 0x78, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x22, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, + 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x58, 0x74, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_transport_internet_xtls_config_proto_rawDescOnce sync.Once + file_transport_internet_xtls_config_proto_rawDescData = file_transport_internet_xtls_config_proto_rawDesc +) + +func file_transport_internet_xtls_config_proto_rawDescGZIP() []byte { + file_transport_internet_xtls_config_proto_rawDescOnce.Do(func() { + file_transport_internet_xtls_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_xtls_config_proto_rawDescData) + }) + return file_transport_internet_xtls_config_proto_rawDescData +} + +var file_transport_internet_xtls_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_transport_internet_xtls_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_transport_internet_xtls_config_proto_goTypes = []interface{}{ + (Certificate_Usage)(0), // 0: v2ray.core.transport.internet.xtls.Certificate.Usage + (*Certificate)(nil), // 1: v2ray.core.transport.internet.xtls.Certificate + (*Config)(nil), // 2: v2ray.core.transport.internet.xtls.Config +} +var file_transport_internet_xtls_config_proto_depIdxs = []int32{ + 0, // 0: v2ray.core.transport.internet.xtls.Certificate.usage:type_name -> v2ray.core.transport.internet.xtls.Certificate.Usage + 1, // 1: v2ray.core.transport.internet.xtls.Config.certificate:type_name -> v2ray.core.transport.internet.xtls.Certificate + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_transport_internet_xtls_config_proto_init() } +func file_transport_internet_xtls_config_proto_init() { + if File_transport_internet_xtls_config_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_transport_internet_xtls_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Certificate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_transport_internet_xtls_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Config); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_transport_internet_xtls_config_proto_rawDesc, + NumEnums: 1, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_transport_internet_xtls_config_proto_goTypes, + DependencyIndexes: file_transport_internet_xtls_config_proto_depIdxs, + EnumInfos: file_transport_internet_xtls_config_proto_enumTypes, + MessageInfos: file_transport_internet_xtls_config_proto_msgTypes, + }.Build() + File_transport_internet_xtls_config_proto = out.File + file_transport_internet_xtls_config_proto_rawDesc = nil + file_transport_internet_xtls_config_proto_goTypes = nil + file_transport_internet_xtls_config_proto_depIdxs = nil +} diff --git a/transport/internet/xtls/config.proto b/transport/internet/xtls/config.proto new file mode 100644 index 000000000..9ad77221e --- /dev/null +++ b/transport/internet/xtls/config.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; + +package v2ray.core.transport.internet.xtls; +option csharp_namespace = "V2Ray.Core.Transport.Internet.Xtls"; +option go_package = "v2ray.com/core/transport/internet/xtls"; +option java_package = "com.v2ray.core.transport.internet.xtls"; +option java_multiple_files = true; + +message Certificate { + // XTLS certificate in x509 format. + bytes Certificate = 1; + + // XTLS key in x509 format. + bytes Key = 2; + + enum Usage { + ENCIPHERMENT = 0; + AUTHORITY_VERIFY = 1; + AUTHORITY_ISSUE = 2; + } + + Usage usage = 3; +} + +message Config { + // Whether or not to allow self-signed certificates. + bool allow_insecure = 1; + + // Whether or not to allow insecure cipher suites. + bool allow_insecure_ciphers = 5; + + // List of certificates to be served on server. + repeated Certificate certificate = 2; + + // Override server name. + string server_name = 3; + + // Lists of string as ALPN values. + repeated string next_protocol = 4; + + // Whether or not to disable session (ticket) resumption. + bool disable_session_resumption = 6; + + // If true, root certificates on the system will not be loaded for verification. + bool disable_system_root = 7; +} diff --git a/transport/internet/xtls/config_other.go b/transport/internet/xtls/config_other.go new file mode 100644 index 000000000..a1dda046c --- /dev/null +++ b/transport/internet/xtls/config_other.go @@ -0,0 +1,53 @@ +// +build !windows +// +build !confonly + +package xtls + +import ( + "crypto/x509" + "sync" +) + +type rootCertsCache struct { + sync.Mutex + pool *x509.CertPool +} + +func (c *rootCertsCache) load() (*x509.CertPool, error) { + c.Lock() + defer c.Unlock() + + if c.pool != nil { + return c.pool, nil + } + + pool, err := x509.SystemCertPool() + if err != nil { + return nil, err + } + c.pool = pool + return pool, nil +} + +var rootCerts rootCertsCache + +func (c *Config) getCertPool() (*x509.CertPool, error) { + if c.DisableSystemRoot { + return c.loadSelfCertPool() + } + + if len(c.Certificate) == 0 { + return rootCerts.load() + } + + pool, err := x509.SystemCertPool() + if err != nil { + return nil, newError("system root").AtWarning().Base(err) + } + for _, cert := range c.Certificate { + if !pool.AppendCertsFromPEM(cert.Certificate) { + return nil, newError("append cert to root").AtWarning().Base(err) + } + } + return pool, err +} diff --git a/transport/internet/xtls/config_test.go b/transport/internet/xtls/config_test.go new file mode 100644 index 000000000..9e7227c9c --- /dev/null +++ b/transport/internet/xtls/config_test.go @@ -0,0 +1,100 @@ +package xtls_test + +import ( + "crypto/x509" + "testing" + "time" + + xtls "github.com/xtls/go" + + "v2ray.com/core/common" + "v2ray.com/core/common/protocol/tls/cert" + . "v2ray.com/core/transport/internet/xtls" +) + +func TestCertificateIssuing(t *testing.T) { + certificate := ParseCertificate(cert.MustGenerate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageCertSign))) + certificate.Usage = Certificate_AUTHORITY_ISSUE + + c := &Config{ + Certificate: []*Certificate{ + certificate, + }, + } + + xtlsConfig := c.GetXTLSConfig() + v2rayCert, err := xtlsConfig.GetCertificate(&xtls.ClientHelloInfo{ + ServerName: "www.v2fly.org", + }) + common.Must(err) + + x509Cert, err := x509.ParseCertificate(v2rayCert.Certificate[0]) + common.Must(err) + if !x509Cert.NotAfter.After(time.Now()) { + t.Error("NotAfter: ", x509Cert.NotAfter) + } +} + +func TestExpiredCertificate(t *testing.T) { + caCert := cert.MustGenerate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageCertSign)) + expiredCert := cert.MustGenerate(caCert, cert.NotAfter(time.Now().Add(time.Minute*-2)), cert.CommonName("www.v2fly.org"), cert.DNSNames("www.v2fly.org")) + + certificate := ParseCertificate(caCert) + certificate.Usage = Certificate_AUTHORITY_ISSUE + + certificate2 := ParseCertificate(expiredCert) + + c := &Config{ + Certificate: []*Certificate{ + certificate, + certificate2, + }, + } + + xtlsConfig := c.GetXTLSConfig() + v2rayCert, err := xtlsConfig.GetCertificate(&xtls.ClientHelloInfo{ + ServerName: "www.v2fly.org", + }) + common.Must(err) + + x509Cert, err := x509.ParseCertificate(v2rayCert.Certificate[0]) + common.Must(err) + if !x509Cert.NotAfter.After(time.Now()) { + t.Error("NotAfter: ", x509Cert.NotAfter) + } +} + +func TestInsecureCertificates(t *testing.T) { + c := &Config{ + AllowInsecureCiphers: true, + } + + xtlsConfig := c.GetXTLSConfig() + if len(xtlsConfig.CipherSuites) > 0 { + t.Fatal("Unexpected tls cipher suites list: ", xtlsConfig.CipherSuites) + } +} + +func BenchmarkCertificateIssuing(b *testing.B) { + certificate := ParseCertificate(cert.MustGenerate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageCertSign))) + certificate.Usage = Certificate_AUTHORITY_ISSUE + + c := &Config{ + Certificate: []*Certificate{ + certificate, + }, + } + + xtlsConfig := c.GetXTLSConfig() + lenCerts := len(xtlsConfig.Certificates) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _, _ = xtlsConfig.GetCertificate(&xtls.ClientHelloInfo{ + ServerName: "www.v2fly.org", + }) + delete(xtlsConfig.NameToCertificate, "www.v2fly.org") + xtlsConfig.Certificates = xtlsConfig.Certificates[:lenCerts] + } +} diff --git a/transport/internet/xtls/config_windows.go b/transport/internet/xtls/config_windows.go new file mode 100644 index 000000000..8c5bf01d7 --- /dev/null +++ b/transport/internet/xtls/config_windows.go @@ -0,0 +1,14 @@ +// +build windows +// +build !confonly + +package xtls + +import "crypto/x509" + +func (c *Config) getCertPool() (*x509.CertPool, error) { + if c.DisableSystemRoot { + return c.loadSelfCertPool() + } + + return nil, nil +} diff --git a/transport/internet/xtls/errors.generated.go b/transport/internet/xtls/errors.generated.go new file mode 100644 index 000000000..9269f5584 --- /dev/null +++ b/transport/internet/xtls/errors.generated.go @@ -0,0 +1,9 @@ +package xtls + +import "v2ray.com/core/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/transport/internet/xtls/xtls.go b/transport/internet/xtls/xtls.go new file mode 100644 index 000000000..e34408bf5 --- /dev/null +++ b/transport/internet/xtls/xtls.go @@ -0,0 +1,50 @@ +// +build !confonly + +package xtls + +import ( + xtls "github.com/xtls/go" + + "v2ray.com/core/common/buf" + "v2ray.com/core/common/net" +) + +//go:generate errorgen + +var ( + _ buf.Writer = (*Conn)(nil) +) + +type Conn struct { + *xtls.Conn +} + +func (c *Conn) WriteMultiBuffer(mb buf.MultiBuffer) error { + mb = buf.Compact(mb) + mb, err := buf.WriteMultiBuffer(c, mb) + buf.ReleaseMulti(mb) + return err +} + +func (c *Conn) HandshakeAddress() net.Address { + if err := c.Handshake(); err != nil { + return nil + } + state := c.ConnectionState() + if state.ServerName == "" { + return nil + } + return net.ParseAddress(state.ServerName) +} + +// Client initiates a XTLS client handshake on the given connection. +func Client(c net.Conn, config *xtls.Config) net.Conn { + xtlsConn := xtls.Client(c, config) + return &Conn{Conn: xtlsConn} +} + +// Server initiates a XTLS server handshake on the given connection. +func Server(c net.Conn, config *xtls.Config) net.Conn { + xtlsConn := xtls.Server(c, config) + return &Conn{Conn: xtlsConn} +}