mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-08-23 01:56:48 +08:00
Compare commits
29 Commits
7f23a1cb65
...
v25.7.26
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b6b51c51c8 | ||
![]() |
fb7a9d8d61 | ||
![]() |
3fe02a658a | ||
![]() |
5f93ff6c3a | ||
![]() |
10376f5b4d | ||
![]() |
1ea00fad81 | ||
![]() |
cfcf2a63d1 | ||
![]() |
66025f2889 | ||
![]() |
c9cd26d6d3 | ||
![]() |
caee152adf | ||
![]() |
eb433d9462 | ||
![]() |
87d8b97d9a | ||
![]() |
9d15ecf1f9 | ||
![]() |
4f45c5faa5 | ||
![]() |
26de58933f | ||
![]() |
31b508d372 | ||
![]() |
955a569181 | ||
![]() |
d141d01d0c | ||
![]() |
4e826abebf | ||
![]() |
4433641e30 | ||
![]() |
a196a16c55 | ||
![]() |
8c0bf15901 | ||
![]() |
dbd9125686 | ||
![]() |
923b5d7229 | ||
![]() |
f90fae22aa | ||
![]() |
b065595f58 | ||
![]() |
308f8a7459 | ||
![]() |
050f596e8f | ||
![]() |
3b47d0846e |
3
.github/workflows/docker.yml
vendored
3
.github/workflows/docker.yml
vendored
@@ -38,6 +38,9 @@ jobs:
|
|||||||
if [[ -z "$SOURCE_TAG" ]]; then
|
if [[ -z "$SOURCE_TAG" ]]; then
|
||||||
SOURCE_TAG="${{ github.ref_name }}"
|
SOURCE_TAG="${{ github.ref_name }}"
|
||||||
fi
|
fi
|
||||||
|
if [[ -z "$SOURCE_TAG" ]]; then
|
||||||
|
SOURCE_TAG="${{ github.event.release.tag_name }}"
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -z "$SOURCE_TAG" ]]; then
|
if [[ -z "$SOURCE_TAG" ]]; then
|
||||||
echo "Error: Could not determine a valid tag source. Input tag and context tag (github.ref_name) are both empty."
|
echo "Error: Could not determine a valid tag source. Input tag and context tag (github.ref_name) are both empty."
|
||||||
|
18
README.md
18
README.md
@@ -1,5 +1,9 @@
|
|||||||
# Project X
|
# Project X
|
||||||
|
|
||||||
|
[](https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1)
|
||||||
|
|
||||||
|
### [Collect a Project X NFT to support the development of Project X!](https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1)
|
||||||
|
|
||||||
[Project X](https://github.com/XTLS) originates from XTLS protocol, providing a set of network tools such as [Xray-core](https://github.com/XTLS/Xray-core) and [REALITY](https://github.com/XTLS/REALITY).
|
[Project X](https://github.com/XTLS) originates from XTLS protocol, providing a set of network tools such as [Xray-core](https://github.com/XTLS/Xray-core) and [REALITY](https://github.com/XTLS/REALITY).
|
||||||
|
|
||||||
[README](https://github.com/XTLS/Xray-core#readme) is open, so feel free to submit your project [here](https://github.com/XTLS/Xray-core/pulls).
|
[README](https://github.com/XTLS/Xray-core#readme) is open, so feel free to submit your project [here](https://github.com/XTLS/Xray-core/pulls).
|
||||||
@@ -7,8 +11,9 @@
|
|||||||
## Donation & NFTs
|
## Donation & NFTs
|
||||||
|
|
||||||
- **ETH/USDT/USDC: `0xDc3Fe44F0f25D13CACb1C4896CD0D321df3146Ee`**
|
- **ETH/USDT/USDC: `0xDc3Fe44F0f25D13CACb1C4896CD0D321df3146Ee`**
|
||||||
- **Project X NFT: [Announcement of NFTs by Project X](https://github.com/XTLS/Xray-core/discussions/3633)**
|
- **Project X NFT: https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1**
|
||||||
- **REALITY NFT: [XHTTP: Beyond REALITY](https://github.com/XTLS/Xray-core/discussions/4113)**
|
- **REALITY NFT: https://opensea.io/item/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2**
|
||||||
|
- **Related links: https://opensea.io/collection/xtls, [Announcement of NFTs by Project X](https://github.com/XTLS/Xray-core/discussions/3633), [XHTTP: Beyond REALITY](https://github.com/XTLS/Xray-core/discussions/4113)**
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
@@ -88,25 +93,28 @@
|
|||||||
- [SaeedDev94/Xray](https://github.com/SaeedDev94/Xray)
|
- [SaeedDev94/Xray](https://github.com/SaeedDev94/Xray)
|
||||||
- [SimpleXray](https://github.com/lhear/SimpleXray)
|
- [SimpleXray](https://github.com/lhear/SimpleXray)
|
||||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||||
- iOS & macOS arm64
|
- iOS & macOS arm64 & tvOS
|
||||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215)
|
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) ([tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274))
|
||||||
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
|
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
|
||||||
- [OneXray](https://github.com/OneXray/OneXray)
|
- [OneXray](https://github.com/OneXray/OneXray)
|
||||||
- macOS arm64 & x64
|
- macOS arm64 & x64
|
||||||
|
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215)
|
||||||
- [V2rayU](https://github.com/yanue/V2rayU)
|
- [V2rayU](https://github.com/yanue/V2rayU)
|
||||||
- [V2RayXS](https://github.com/tzmax/V2RayXS)
|
- [V2RayXS](https://github.com/tzmax/V2RayXS)
|
||||||
- [Furious](https://github.com/LorenEteval/Furious)
|
- [Furious](https://github.com/LorenEteval/Furious)
|
||||||
- [OneXray](https://github.com/OneXray/OneXray)
|
- [OneXray](https://github.com/OneXray/OneXray)
|
||||||
|
- [GoXRay](https://github.com/goxray/desktop)
|
||||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||||
- Linux
|
- Linux
|
||||||
- [v2rayA](https://github.com/v2rayA/v2rayA)
|
- [v2rayA](https://github.com/v2rayA/v2rayA)
|
||||||
- [Furious](https://github.com/LorenEteval/Furious)
|
- [Furious](https://github.com/LorenEteval/Furious)
|
||||||
- [GorzRay](https://github.com/ketetefid/GorzRay)
|
- [GorzRay](https://github.com/ketetefid/GorzRay)
|
||||||
|
- [GoXRay](https://github.com/goxray/desktop)
|
||||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||||
|
|
||||||
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
|
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
|
||||||
|
|
||||||
- iOS & macOS arm64
|
- iOS & macOS arm64 & tvOS
|
||||||
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
|
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
|
||||||
- [Loon](https://apps.apple.com/us/app/loon/id1373567447)
|
- [Loon](https://apps.apple.com/us/app/loon/id1373567447)
|
||||||
- Xray Tools
|
- Xray Tools
|
||||||
|
@@ -204,7 +204,12 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, uint32, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Static host lookup
|
// Static host lookup
|
||||||
switch addrs := s.hosts.Lookup(domain, option); {
|
switch addrs, err := s.hosts.Lookup(domain, option); {
|
||||||
|
case err != nil:
|
||||||
|
if go_errors.Is(err, dns.ErrEmptyResponse) {
|
||||||
|
return nil, 0, dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
return nil, 0, errors.New("returning nil for domain ", domain).Base(err)
|
||||||
case addrs == nil: // Domain not recorded in static host
|
case addrs == nil: // Domain not recorded in static host
|
||||||
break
|
break
|
||||||
case len(addrs) == 0: // Domain recorded, but no valid IP returned (e.g. IPv4 address with only IPv6 enabled)
|
case len(addrs) == 0: // Domain recorded, but no valid IP returned (e.g. IPv4 address with only IPv6 enabled)
|
||||||
|
@@ -2,6 +2,8 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/strmatcher"
|
"github.com/xtls/xray-core/common/strmatcher"
|
||||||
@@ -31,7 +33,15 @@ func NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) {
|
|||||||
ips := make([]net.Address, 0, len(mapping.Ip)+1)
|
ips := make([]net.Address, 0, len(mapping.Ip)+1)
|
||||||
switch {
|
switch {
|
||||||
case len(mapping.ProxiedDomain) > 0:
|
case len(mapping.ProxiedDomain) > 0:
|
||||||
ips = append(ips, net.DomainAddress(mapping.ProxiedDomain))
|
if mapping.ProxiedDomain[0] == '#' {
|
||||||
|
rcode, err := strconv.Atoi(mapping.ProxiedDomain[1:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ips = append(ips, dns.RCodeError(rcode))
|
||||||
|
} else {
|
||||||
|
ips = append(ips, net.DomainAddress(mapping.ProxiedDomain))
|
||||||
|
}
|
||||||
case len(mapping.Ip) > 0:
|
case len(mapping.Ip) > 0:
|
||||||
for _, ip := range mapping.Ip {
|
for _, ip := range mapping.Ip {
|
||||||
addr := net.IPAddress(ip)
|
addr := net.IPAddress(ip)
|
||||||
@@ -58,38 +68,51 @@ func filterIP(ips []net.Address, option dns.IPOption) []net.Address {
|
|||||||
return filtered
|
return filtered
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *StaticHosts) lookupInternal(domain string) []net.Address {
|
func (h *StaticHosts) lookupInternal(domain string) ([]net.Address, error) {
|
||||||
ips := make([]net.Address, 0)
|
ips := make([]net.Address, 0)
|
||||||
found := false
|
found := false
|
||||||
for _, id := range h.matchers.Match(domain) {
|
for _, id := range h.matchers.Match(domain) {
|
||||||
|
for _, v := range h.ips[id] {
|
||||||
|
if err, ok := v.(dns.RCodeError); ok {
|
||||||
|
if uint16(err) == 0 {
|
||||||
|
return nil, dns.ErrEmptyResponse
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
ips = append(ips, h.ips[id]...)
|
ips = append(ips, h.ips[id]...)
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return ips
|
return ips, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) []net.Address {
|
func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) ([]net.Address, error) {
|
||||||
switch addrs := h.lookupInternal(domain); {
|
switch addrs, err := h.lookupInternal(domain); {
|
||||||
|
case err != nil:
|
||||||
|
return nil, err
|
||||||
case len(addrs) == 0: // Not recorded in static hosts, return nil
|
case len(addrs) == 0: // Not recorded in static hosts, return nil
|
||||||
return addrs
|
return addrs, nil
|
||||||
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Try to unwrap domain
|
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Try to unwrap domain
|
||||||
errors.LogDebug(context.Background(), "found replaced domain: ", domain, " -> ", addrs[0].Domain(), ". Try to unwrap it")
|
errors.LogDebug(context.Background(), "found replaced domain: ", domain, " -> ", addrs[0].Domain(), ". Try to unwrap it")
|
||||||
if maxDepth > 0 {
|
if maxDepth > 0 {
|
||||||
unwrapped := h.lookup(addrs[0].Domain(), option, maxDepth-1)
|
unwrapped, err := h.lookup(addrs[0].Domain(), option, maxDepth-1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if unwrapped != nil {
|
if unwrapped != nil {
|
||||||
return unwrapped
|
return unwrapped, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return addrs
|
return addrs, nil
|
||||||
default: // IP record found, return a non-nil IP array
|
default: // IP record found, return a non-nil IP array
|
||||||
return filterIP(addrs, option)
|
return filterIP(addrs, option), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup returns IP addresses or proxied domain for the given domain, if exists in this StaticHosts.
|
// Lookup returns IP addresses or proxied domain for the given domain, if exists in this StaticHosts.
|
||||||
func (h *StaticHosts) Lookup(domain string, option dns.IPOption) []net.Address {
|
func (h *StaticHosts) Lookup(domain string, option dns.IPOption) ([]net.Address, error) {
|
||||||
return h.lookup(domain, option, 5)
|
return h.lookup(domain, option, 5)
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,11 @@ import (
|
|||||||
|
|
||||||
func TestStaticHosts(t *testing.T) {
|
func TestStaticHosts(t *testing.T) {
|
||||||
pb := []*Config_HostMapping{
|
pb := []*Config_HostMapping{
|
||||||
|
{
|
||||||
|
Type: DomainMatchingType_Subdomain,
|
||||||
|
Domain: "lan",
|
||||||
|
ProxiedDomain: "#3",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Type: DomainMatchingType_Full,
|
Type: DomainMatchingType_Full,
|
||||||
Domain: "example.com",
|
Domain: "example.com",
|
||||||
@@ -54,7 +59,14 @@ func TestStaticHosts(t *testing.T) {
|
|||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
{
|
{
|
||||||
ips := hosts.Lookup("example.com", dns.IPOption{
|
_, err := hosts.Lookup("example.com.lan", dns.IPOption{})
|
||||||
|
if dns.RCodeFromError(err) != 3 {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ips, _ := hosts.Lookup("example.com", dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
})
|
||||||
@@ -67,7 +79,7 @@ func TestStaticHosts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
domain := hosts.Lookup("proxy.xray.com", dns.IPOption{
|
domain, _ := hosts.Lookup("proxy.xray.com", dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: false,
|
IPv6Enable: false,
|
||||||
})
|
})
|
||||||
@@ -80,7 +92,7 @@ func TestStaticHosts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
domain := hosts.Lookup("proxy2.xray.com", dns.IPOption{
|
domain, _ := hosts.Lookup("proxy2.xray.com", dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: false,
|
IPv6Enable: false,
|
||||||
})
|
})
|
||||||
@@ -93,7 +105,7 @@ func TestStaticHosts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
ips := hosts.Lookup("www.example.cn", dns.IPOption{
|
ips, _ := hosts.Lookup("www.example.cn", dns.IPOption{
|
||||||
IPv4Enable: true,
|
IPv4Enable: true,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
})
|
||||||
@@ -106,7 +118,7 @@ func TestStaticHosts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
ips := hosts.Lookup("baidu.com", dns.IPOption{
|
ips, _ := hosts.Lookup("baidu.com", dns.IPOption{
|
||||||
IPv4Enable: false,
|
IPv4Enable: false,
|
||||||
IPv6Enable: true,
|
IPv6Enable: true,
|
||||||
})
|
})
|
||||||
|
@@ -161,6 +161,7 @@ type udpConn struct {
|
|||||||
uplink stats.Counter
|
uplink stats.Counter
|
||||||
downlink stats.Counter
|
downlink stats.Counter
|
||||||
inactive bool
|
inactive bool
|
||||||
|
cancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *udpConn) setInactive() {
|
func (c *udpConn) setInactive() {
|
||||||
@@ -203,6 +204,9 @@ func (c *udpConn) Write(buf []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *udpConn) Close() error {
|
func (c *udpConn) Close() error {
|
||||||
|
if c.cancel != nil {
|
||||||
|
c.cancel()
|
||||||
|
}
|
||||||
common.Must(c.done.Close())
|
common.Must(c.done.Close())
|
||||||
common.Must(common.Close(c.writer))
|
common.Must(common.Close(c.writer))
|
||||||
return nil
|
return nil
|
||||||
@@ -259,6 +263,7 @@ func (w *udpWorker) getConnection(id connID) (*udpConn, bool) {
|
|||||||
defer w.Unlock()
|
defer w.Unlock()
|
||||||
|
|
||||||
if conn, found := w.activeConn[id]; found && !conn.done.Done() {
|
if conn, found := w.activeConn[id]; found && !conn.done.Done() {
|
||||||
|
conn.updateActivity()
|
||||||
return conn, true
|
return conn, true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +311,8 @@ func (w *udpWorker) callback(b *buf.Buffer, source net.Destination, originalDest
|
|||||||
common.Must(w.checker.Start())
|
common.Must(w.checker.Start())
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
ctx := w.ctx
|
ctx, cancel := context.WithCancel(w.ctx)
|
||||||
|
conn.cancel = cancel
|
||||||
sid := session.NewID()
|
sid := session.NewID()
|
||||||
ctx = c.ContextWithID(ctx, sid)
|
ctx = c.ContextWithID(ctx, sid)
|
||||||
|
|
||||||
|
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
func (c *Control) FillInRandom() {
|
func (c *Control) FillInRandom() {
|
||||||
randomLength := dice.Roll(64)
|
randomLength := dice.Roll(64)
|
||||||
|
randomLength++
|
||||||
c.Random = make([]byte, randomLength)
|
c.Random = make([]byte, randomLength)
|
||||||
io.ReadFull(rand.Reader, c.Random)
|
io.ReadFull(rand.Reader, c.Random)
|
||||||
}
|
}
|
||||||
|
@@ -170,7 +170,7 @@ func (p *StaticMuxPicker) PickAvailable() (*mux.ClientWorker, error) {
|
|||||||
if w.draining {
|
if w.draining {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if w.client.Closed() {
|
if w.IsFull() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if w.client.ActiveConnections() < minConn {
|
if w.client.ActiveConnections() < minConn {
|
||||||
@@ -211,6 +211,7 @@ type PortalWorker struct {
|
|||||||
writer buf.Writer
|
writer buf.Writer
|
||||||
reader buf.Reader
|
reader buf.Reader
|
||||||
draining bool
|
draining bool
|
||||||
|
counter uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPortalWorker(client *mux.ClientWorker) (*PortalWorker, error) {
|
func NewPortalWorker(client *mux.ClientWorker) (*PortalWorker, error) {
|
||||||
@@ -244,7 +245,7 @@ func NewPortalWorker(client *mux.ClientWorker) (*PortalWorker, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *PortalWorker) heartbeat() error {
|
func (w *PortalWorker) heartbeat() error {
|
||||||
if w.client.Closed() {
|
if w.Closed() {
|
||||||
return errors.New("client worker stopped")
|
return errors.New("client worker stopped")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,16 +261,21 @@ func (w *PortalWorker) heartbeat() error {
|
|||||||
msg.State = Control_DRAIN
|
msg.State = Control_DRAIN
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
|
w.client.GetTimer().Reset(time.Second * 16)
|
||||||
common.Close(w.writer)
|
common.Close(w.writer)
|
||||||
common.Interrupt(w.reader)
|
common.Interrupt(w.reader)
|
||||||
w.writer = nil
|
w.writer = nil
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := proto.Marshal(msg)
|
w.counter = (w.counter + 1) % 5
|
||||||
common.Must(err)
|
if w.draining || w.counter == 1 {
|
||||||
mb := buf.MergeBytes(nil, b)
|
b, err := proto.Marshal(msg)
|
||||||
return w.writer.WriteMultiBuffer(mb)
|
common.Must(err)
|
||||||
|
mb := buf.MergeBytes(nil, b)
|
||||||
|
return w.writer.WriteMultiBuffer(mb)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *PortalWorker) IsFull() bool {
|
func (w *PortalWorker) IsFull() bool {
|
||||||
|
@@ -173,6 +173,7 @@ type ClientWorker struct {
|
|||||||
sessionManager *SessionManager
|
sessionManager *SessionManager
|
||||||
link transport.Link
|
link transport.Link
|
||||||
done *done.Instance
|
done *done.Instance
|
||||||
|
timer *time.Ticker
|
||||||
strategy ClientStrategy
|
strategy ClientStrategy
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,6 +188,7 @@ func NewClientWorker(stream transport.Link, s ClientStrategy) (*ClientWorker, er
|
|||||||
sessionManager: NewSessionManager(),
|
sessionManager: NewSessionManager(),
|
||||||
link: stream,
|
link: stream,
|
||||||
done: done.New(),
|
done: done.New(),
|
||||||
|
timer: time.NewTicker(time.Second * 16),
|
||||||
strategy: s,
|
strategy: s,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,9 +211,12 @@ func (m *ClientWorker) Closed() bool {
|
|||||||
return m.done.Done()
|
return m.done.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *ClientWorker) GetTimer() *time.Ticker {
|
||||||
|
return m.timer
|
||||||
|
}
|
||||||
|
|
||||||
func (m *ClientWorker) monitor() {
|
func (m *ClientWorker) monitor() {
|
||||||
timer := time.NewTicker(time.Second * 16)
|
defer m.timer.Stop()
|
||||||
defer timer.Stop()
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -220,7 +225,7 @@ func (m *ClientWorker) monitor() {
|
|||||||
common.Close(m.link.Writer)
|
common.Close(m.link.Writer)
|
||||||
common.Interrupt(m.link.Reader)
|
common.Interrupt(m.link.Reader)
|
||||||
return
|
return
|
||||||
case <-timer.C:
|
case <-m.timer.C:
|
||||||
size := m.sessionManager.Size()
|
size := m.sessionManager.Size()
|
||||||
if size == 0 && m.sessionManager.CloseIfNoSession() {
|
if size == 0 && m.sessionManager.CloseIfNoSession() {
|
||||||
common.Must(m.done.Close())
|
common.Must(m.done.Close())
|
||||||
@@ -276,6 +281,8 @@ func (m *ClientWorker) IsClosing() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsFull returns true if this ClientWorker is unable to accept more connections.
|
||||||
|
// it might be because it is closing, or the number of connections has reached the limit.
|
||||||
func (m *ClientWorker) IsFull() bool {
|
func (m *ClientWorker) IsFull() bool {
|
||||||
if m.IsClosing() || m.Closed() {
|
if m.IsClosing() || m.Closed() {
|
||||||
return true
|
return true
|
||||||
@@ -289,12 +296,12 @@ func (m *ClientWorker) IsFull() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *ClientWorker) Dispatch(ctx context.Context, link *transport.Link) bool {
|
func (m *ClientWorker) Dispatch(ctx context.Context, link *transport.Link) bool {
|
||||||
if m.IsFull() || m.Closed() {
|
if m.IsFull() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
sm := m.sessionManager
|
sm := m.sessionManager
|
||||||
s := sm.Allocate()
|
s := sm.Allocate(&m.strategy)
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@@ -201,11 +201,12 @@ func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata,
|
|||||||
transferType: protocol.TransferTypePacket,
|
transferType: protocol.TransferTypePacket,
|
||||||
XUDP: x,
|
XUDP: x,
|
||||||
}
|
}
|
||||||
go handle(ctx, x.Mux, w.link.Writer)
|
|
||||||
x.Status = Active
|
x.Status = Active
|
||||||
if !w.sessionManager.Add(x.Mux) {
|
if !w.sessionManager.Add(x.Mux) {
|
||||||
x.Mux.Close(false)
|
x.Mux.Close(false)
|
||||||
|
return errors.New("failed to add new session")
|
||||||
}
|
}
|
||||||
|
go handle(ctx, x.Mux, w.link.Writer)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,18 +227,23 @@ func (w *ServerWorker) handleStatusNew(ctx context.Context, meta *FrameMetadata,
|
|||||||
if meta.Target.Network == net.Network_UDP {
|
if meta.Target.Network == net.Network_UDP {
|
||||||
s.transferType = protocol.TransferTypePacket
|
s.transferType = protocol.TransferTypePacket
|
||||||
}
|
}
|
||||||
w.sessionManager.Add(s)
|
if !w.sessionManager.Add(s) {
|
||||||
|
s.Close(false)
|
||||||
|
return errors.New("failed to add new session")
|
||||||
|
}
|
||||||
go handle(ctx, s, w.link.Writer)
|
go handle(ctx, s, w.link.Writer)
|
||||||
if !meta.Option.Has(OptionData) {
|
if !meta.Option.Has(OptionData) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
rr := s.NewReader(reader, &meta.Target)
|
rr := s.NewReader(reader, &meta.Target)
|
||||||
if err := buf.Copy(rr, s.output); err != nil {
|
err = buf.Copy(rr, s.output)
|
||||||
buf.Copy(rr, buf.Discard)
|
|
||||||
return s.Close(false)
|
if err != nil && buf.IsWriteError(err) {
|
||||||
|
s.Close(false)
|
||||||
|
return buf.Copy(rr, buf.Discard)
|
||||||
}
|
}
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *ServerWorker) handleStatusKeep(meta *FrameMetadata, reader *buf.BufferedReader) error {
|
func (w *ServerWorker) handleStatusKeep(meta *FrameMetadata, reader *buf.BufferedReader) error {
|
||||||
@@ -304,10 +310,11 @@ func (w *ServerWorker) handleFrame(ctx context.Context, reader *buf.BufferedRead
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *ServerWorker) run(ctx context.Context) {
|
func (w *ServerWorker) run(ctx context.Context) {
|
||||||
input := w.link.Reader
|
reader := &buf.BufferedReader{Reader: w.link.Reader}
|
||||||
reader := &buf.BufferedReader{Reader: input}
|
|
||||||
|
|
||||||
defer w.sessionManager.Close()
|
defer w.sessionManager.Close()
|
||||||
|
defer common.Close(w.link.Writer)
|
||||||
|
defer common.Interrupt(w.link.Reader)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -318,7 +325,6 @@ func (w *ServerWorker) run(ctx context.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Cause(err) != io.EOF {
|
if errors.Cause(err) != io.EOF {
|
||||||
errors.LogInfoInner(ctx, err, "unexpected EOF")
|
errors.LogInfoInner(ctx, err, "unexpected EOF")
|
||||||
common.Interrupt(input)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -50,11 +50,14 @@ func (m *SessionManager) Count() int {
|
|||||||
return int(m.count)
|
return int(m.count)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *SessionManager) Allocate() *Session {
|
func (m *SessionManager) Allocate(Strategy *ClientStrategy) *Session {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
defer m.Unlock()
|
defer m.Unlock()
|
||||||
|
|
||||||
|
MaxConcurrency := int(Strategy.MaxConcurrency)
|
||||||
|
MaxConnection := uint16(Strategy.MaxConnection)
|
||||||
|
|
||||||
if m.closed {
|
if m.closed || (MaxConcurrency > 0 && len(m.sessions) >= MaxConcurrency) || (MaxConnection > 0 && m.count >= MaxConnection) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,7 +9,7 @@ import (
|
|||||||
func TestSessionManagerAdd(t *testing.T) {
|
func TestSessionManagerAdd(t *testing.T) {
|
||||||
m := NewSessionManager()
|
m := NewSessionManager()
|
||||||
|
|
||||||
s := m.Allocate()
|
s := m.Allocate(&ClientStrategy{})
|
||||||
if s.ID != 1 {
|
if s.ID != 1 {
|
||||||
t.Error("id: ", s.ID)
|
t.Error("id: ", s.ID)
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ func TestSessionManagerAdd(t *testing.T) {
|
|||||||
t.Error("size: ", m.Size())
|
t.Error("size: ", m.Size())
|
||||||
}
|
}
|
||||||
|
|
||||||
s = m.Allocate()
|
s = m.Allocate(&ClientStrategy{})
|
||||||
if s.ID != 2 {
|
if s.ID != 2 {
|
||||||
t.Error("id: ", s.ID)
|
t.Error("id: ", s.ID)
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ func TestSessionManagerAdd(t *testing.T) {
|
|||||||
|
|
||||||
func TestSessionManagerClose(t *testing.T) {
|
func TestSessionManagerClose(t *testing.T) {
|
||||||
m := NewSessionManager()
|
m := NewSessionManager()
|
||||||
s := m.Allocate()
|
s := m.Allocate(&ClientStrategy{})
|
||||||
|
|
||||||
if m.CloseIfNoSession() {
|
if m.CloseIfNoSession() {
|
||||||
t.Error("able to close")
|
t.Error("able to close")
|
||||||
|
112
common/utils/typed_sync_map.go
Normal file
112
common/utils/typed_sync_map.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TypedSyncMap is a wrapper of sync.Map that provides type-safe for keys and values.
|
||||||
|
// No need to use type assertions every time, so you can have more time to enjoy other things like GochiUsa
|
||||||
|
// If sync.Map methods returned nil, it will return the zero value of the type V.
|
||||||
|
type TypedSyncMap[K, V any] struct {
|
||||||
|
syncMap *sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTypedSyncMap creates a new TypedSyncMap
|
||||||
|
// K is key type, V is value type
|
||||||
|
// It is recommended to use pointer types for V because sync.Map might return nil
|
||||||
|
// If sync.Map methods really returned nil, it will return the zero value of the type V
|
||||||
|
func NewTypedSyncMap[K any, V any]() *TypedSyncMap[K, V] {
|
||||||
|
return &TypedSyncMap[K, V]{
|
||||||
|
syncMap: &sync.Map{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear deletes all the entries, resulting in an empty Map.
|
||||||
|
func (m *TypedSyncMap[K, V]) Clear() {
|
||||||
|
m.syncMap.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompareAndDelete deletes the entry for key if its value is equal to old.
|
||||||
|
// The old value must be of a comparable type.
|
||||||
|
//
|
||||||
|
// If there is no current value for key in the map, CompareAndDelete
|
||||||
|
// returns false (even if the old value is the nil interface value).
|
||||||
|
func (m *TypedSyncMap[K, V]) CompareAndDelete(key K, old V) (deleted bool) {
|
||||||
|
return m.syncMap.CompareAndDelete(key, old)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompareAndSwap swaps the old and new values for key
|
||||||
|
// if the value stored in the map is equal to old.
|
||||||
|
// The old value must be of a comparable type.
|
||||||
|
func (m *TypedSyncMap[K, V]) CompareAndSwap(key K, old V, new V) (swapped bool) {
|
||||||
|
return m.syncMap.CompareAndSwap(key, old, new)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes the value for a key.
|
||||||
|
func (m *TypedSyncMap[K, V]) Delete(key K) {
|
||||||
|
m.syncMap.Delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load returns the value stored in the map for a key, or nil if no
|
||||||
|
// value is present.
|
||||||
|
// The ok result indicates whether value was found in the map.
|
||||||
|
func (m *TypedSyncMap[K, V]) Load(key K) (value V, ok bool) {
|
||||||
|
anyValue, ok := m.syncMap.Load(key)
|
||||||
|
// anyValue might be nil
|
||||||
|
if anyValue != nil {
|
||||||
|
value = anyValue.(V)
|
||||||
|
}
|
||||||
|
return value, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAndDelete deletes the value for a key, returning the previous value if any.
|
||||||
|
// The loaded result reports whether the key was present.
|
||||||
|
func (m *TypedSyncMap[K, V]) LoadAndDelete(key K) (value V, loaded bool) {
|
||||||
|
anyValue, loaded := m.syncMap.LoadAndDelete(key)
|
||||||
|
if anyValue != nil {
|
||||||
|
value = anyValue.(V)
|
||||||
|
}
|
||||||
|
return value, loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadOrStore returns the existing value for the key if present.
|
||||||
|
// Otherwise, it stores and returns the given value.
|
||||||
|
// The loaded result is true if the value was loaded, false if stored.
|
||||||
|
func (m *TypedSyncMap[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
|
||||||
|
anyActual, loaded := m.syncMap.LoadOrStore(key, value)
|
||||||
|
if anyActual != nil {
|
||||||
|
actual = anyActual.(V)
|
||||||
|
}
|
||||||
|
return actual, loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range calls f sequentially for each key and value present in the map.
|
||||||
|
// If f returns false, range stops the iteration.
|
||||||
|
//
|
||||||
|
// Range does not necessarily correspond to any consistent snapshot of the Map's
|
||||||
|
// contents: no key will be visited more than once, but if the value for any key
|
||||||
|
// is stored or deleted concurrently (including by f), Range may reflect any
|
||||||
|
// mapping for that key from any point during the Range call. Range does not
|
||||||
|
// block other methods on the receiver; even f itself may call any method on m.
|
||||||
|
//
|
||||||
|
// Range may be O(N) with the number of elements in the map even if f returns
|
||||||
|
// false after a constant number of calls.
|
||||||
|
func (m *TypedSyncMap[K, V]) Range(f func(key K, value V) bool) {
|
||||||
|
m.syncMap.Range(func(key, value any) bool {
|
||||||
|
return f(key.(K), value.(V))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store sets the value for a key.
|
||||||
|
func (m *TypedSyncMap[K, V]) Store(key K, value V) {
|
||||||
|
m.syncMap.Store(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap swaps the value for a key and returns the previous value if any. The loaded result reports whether the key was present.
|
||||||
|
func (m *TypedSyncMap[K, V]) Swap(key K, value V) (previous V, loaded bool) {
|
||||||
|
anyPrevious, loaded := m.syncMap.Swap(key, value)
|
||||||
|
if anyPrevious != nil {
|
||||||
|
previous = anyPrevious.(V)
|
||||||
|
}
|
||||||
|
return previous, loaded
|
||||||
|
}
|
@@ -18,8 +18,8 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
Version_x byte = 25
|
Version_x byte = 25
|
||||||
Version_y byte = 6
|
Version_y byte = 7
|
||||||
Version_z byte = 8
|
Version_z byte = 26
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@@ -42,6 +42,24 @@ func (e RCodeError) Error() string {
|
|||||||
return serial.Concat("rcode: ", uint16(e))
|
return serial.Concat("rcode: ", uint16(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (RCodeError) IP() net.IP {
|
||||||
|
panic("Calling IP() on a RCodeError.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (RCodeError) Domain() string {
|
||||||
|
panic("Calling Domain() on a RCodeError.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (RCodeError) Family() net.AddressFamily {
|
||||||
|
panic("Calling Family() on a RCodeError.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e RCodeError) String() string {
|
||||||
|
return e.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ net.Address = (*RCodeError)(nil)
|
||||||
|
|
||||||
func RCodeFromError(err error) uint16 {
|
func RCodeFromError(err error) uint16 {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return 0
|
return 0
|
||||||
|
3
go.mod
3
go.mod
@@ -3,7 +3,6 @@ module github.com/xtls/xray-core
|
|||||||
go 1.24
|
go 1.24
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0
|
|
||||||
github.com/cloudflare/circl v1.6.1
|
github.com/cloudflare/circl v1.6.1
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
|
||||||
github.com/golang/mock v1.7.0-rc.1
|
github.com/golang/mock v1.7.0-rc.1
|
||||||
@@ -20,7 +19,7 @@ require (
|
|||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
|
||||||
github.com/vishvananda/netlink v1.3.1
|
github.com/vishvananda/netlink v1.3.1
|
||||||
github.com/xtls/reality v0.0.0-20250722045654-4eaf7927f393
|
github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/crypto v0.40.0
|
golang.org/x/crypto v0.40.0
|
||||||
golang.org/x/net v0.42.0
|
golang.org/x/net v0.42.0
|
||||||
|
6
go.sum
6
go.sum
@@ -1,5 +1,3 @@
|
|||||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJSGpevP+8Pk5bANX7fJacO2w04aqLiC5I=
|
|
||||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
|
|
||||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||||
@@ -77,8 +75,8 @@ github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW
|
|||||||
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
||||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
github.com/xtls/reality v0.0.0-20250722045654-4eaf7927f393 h1:WcsH1BAZVgbKpyyiyjOnS8deJcQ7BmYa2iNEPqEy574=
|
github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7 h1:Ript0vN+nSO33+Vj4n0mgNY5M+oOxFQJdrJ1VnwTBO0=
|
||||||
github.com/xtls/reality v0.0.0-20250722045654-4eaf7927f393/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0=
|
github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0=
|
||||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
|
@@ -11,7 +11,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/cloudflare/circl/sign/mldsa/mldsa65"
|
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/common/net"
|
"github.com/xtls/xray-core/common/net"
|
||||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
@@ -413,6 +412,8 @@ type TLSConfig struct {
|
|||||||
MasterKeyLog string `json:"masterKeyLog"`
|
MasterKeyLog string `json:"masterKeyLog"`
|
||||||
ServerNameToVerify string `json:"serverNameToVerify"`
|
ServerNameToVerify string `json:"serverNameToVerify"`
|
||||||
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
|
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
|
||||||
|
ECHConfigList string `json:"echConfigList"`
|
||||||
|
ECHServerKeys string `json:"echServerKeys"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build implements Buildable.
|
// Build implements Buildable.
|
||||||
@@ -484,6 +485,16 @@ func (c *TLSConfig) Build() (proto.Message, error) {
|
|||||||
}
|
}
|
||||||
config.VerifyPeerCertInNames = c.VerifyPeerCertInNames
|
config.VerifyPeerCertInNames = c.VerifyPeerCertInNames
|
||||||
|
|
||||||
|
config.EchConfigList = c.ECHConfigList
|
||||||
|
|
||||||
|
if c.ECHServerKeys != "" {
|
||||||
|
EchPrivateKey, err := base64.StdEncoding.DecodeString(c.ECHServerKeys)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("invalid ECH Config", c.ECHServerKeys)
|
||||||
|
}
|
||||||
|
config.EchServerKeys = EchPrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,11 +624,13 @@ func (c *REALITYConfig) Build() (proto.Message, error) {
|
|||||||
config.ServerNames = c.ServerNames
|
config.ServerNames = c.ServerNames
|
||||||
config.MaxTimeDiff = c.MaxTimeDiff
|
config.MaxTimeDiff = c.MaxTimeDiff
|
||||||
|
|
||||||
if mldsa65Seed, err := base64.RawURLEncoding.DecodeString(c.Mldsa65Seed); err != nil || len(mldsa65Seed) != 32 {
|
if c.Mldsa65Seed != "" {
|
||||||
return nil, errors.New(`invalid "mldsa65Seed": `, c.Mldsa65Seed)
|
if c.Mldsa65Seed == c.PrivateKey {
|
||||||
} else {
|
return nil, errors.New(`"mldsa65Seed" and "privateKey" can not be the same value: `, c.Mldsa65Seed)
|
||||||
_, key := mldsa65.NewKeyFromSeed((*[32]byte)(mldsa65Seed))
|
}
|
||||||
config.Mldsa65Key = key.Bytes()
|
if config.Mldsa65Seed, err = base64.RawURLEncoding.DecodeString(c.Mldsa65Seed); err != nil || len(config.Mldsa65Seed) != 32 {
|
||||||
|
return nil, errors.New(`invalid "mldsa65Seed": `, c.Mldsa65Seed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.LimitFallbackUpload = new(reality.LimitFallback)
|
config.LimitFallbackUpload = new(reality.LimitFallback)
|
||||||
@@ -655,8 +668,10 @@ func (c *REALITYConfig) Build() (proto.Message, error) {
|
|||||||
if _, err = hex.Decode(config.ShortId, []byte(c.ShortId)); err != nil {
|
if _, err = hex.Decode(config.ShortId, []byte(c.ShortId)); err != nil {
|
||||||
return nil, errors.New(`invalid "shortId": `, c.ShortId)
|
return nil, errors.New(`invalid "shortId": `, c.ShortId)
|
||||||
}
|
}
|
||||||
if config.Mldsa65Verify, err = base64.RawURLEncoding.DecodeString(c.Mldsa65Verify); err != nil || len(config.Mldsa65Verify) != 1952 {
|
if c.Mldsa65Verify != "" {
|
||||||
return nil, errors.New(`invalid "mldsa65Verify": `, c.Mldsa65Verify)
|
if config.Mldsa65Verify, err = base64.RawURLEncoding.DecodeString(c.Mldsa65Verify); err != nil || len(config.Mldsa65Verify) != 1952 {
|
||||||
|
return nil, errors.New(`invalid "mldsa65Verify": `, c.Mldsa65Verify)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if c.SpiderX == "" {
|
if c.SpiderX == "" {
|
||||||
c.SpiderX = "/"
|
c.SpiderX = "/"
|
||||||
|
@@ -23,6 +23,8 @@ var CmdAPI = &base.Command{
|
|||||||
cmdRemoveOutbounds,
|
cmdRemoveOutbounds,
|
||||||
cmdListInbounds,
|
cmdListInbounds,
|
||||||
cmdListOutbounds,
|
cmdListOutbounds,
|
||||||
|
cmdAddInboundUsers,
|
||||||
|
cmdRemoveInboundUsers,
|
||||||
cmdInboundUser,
|
cmdInboundUser,
|
||||||
cmdInboundUserCount,
|
cmdInboundUserCount,
|
||||||
cmdAddRules,
|
cmdAddRules,
|
||||||
|
144
main/commands/all/api/inbound_user_add.go
Normal file
144
main/commands/all/api/inbound_user_add.go
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common/protocol"
|
||||||
|
|
||||||
|
handlerService "github.com/xtls/xray-core/app/proxyman/command"
|
||||||
|
cserial "github.com/xtls/xray-core/common/serial"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/core"
|
||||||
|
"github.com/xtls/xray-core/infra/conf"
|
||||||
|
"github.com/xtls/xray-core/infra/conf/serial"
|
||||||
|
"github.com/xtls/xray-core/proxy/shadowsocks"
|
||||||
|
"github.com/xtls/xray-core/proxy/shadowsocks_2022"
|
||||||
|
"github.com/xtls/xray-core/proxy/trojan"
|
||||||
|
vlessin "github.com/xtls/xray-core/proxy/vless/inbound"
|
||||||
|
vmessin "github.com/xtls/xray-core/proxy/vmess/inbound"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/main/commands/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdAddInboundUsers = &base.Command{
|
||||||
|
CustomFlags: true,
|
||||||
|
UsageLine: "{{.Exec}} api adu [--server=127.0.0.1:8080] <c1.json> [c2.json]...",
|
||||||
|
Short: "Add users to inbounds",
|
||||||
|
Long: `
|
||||||
|
Add users to inbounds.
|
||||||
|
Arguments:
|
||||||
|
-s, -server
|
||||||
|
The API server address. Default 127.0.0.1:8080
|
||||||
|
-t, -timeout
|
||||||
|
Timeout seconds to call API. Default 3
|
||||||
|
Example:
|
||||||
|
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 c1.json c2.json
|
||||||
|
`,
|
||||||
|
Run: executeAddInboundUsers,
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeAddInboundUsers(cmd *base.Command, args []string) {
|
||||||
|
setSharedFlags(cmd)
|
||||||
|
cmd.Flag.Parse(args)
|
||||||
|
unnamedArgs := cmd.Flag.Args()
|
||||||
|
inbs := extractInboundsConfig(unnamedArgs)
|
||||||
|
|
||||||
|
conn, ctx, close := dialAPIServer()
|
||||||
|
defer close()
|
||||||
|
client := handlerService.NewHandlerServiceClient(conn)
|
||||||
|
|
||||||
|
success := 0
|
||||||
|
for _, inb := range inbs {
|
||||||
|
success += executeInboundUserAction(ctx, client, inb, addInboundUserAction)
|
||||||
|
}
|
||||||
|
fmt.Println("Added", success, "user(s) in total.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func addInboundUserAction(ctx context.Context, client handlerService.HandlerServiceClient, tag string, user *protocol.User) error {
|
||||||
|
fmt.Println("add user:", user.Email)
|
||||||
|
_, err := client.AlterInbound(ctx, &handlerService.AlterInboundRequest{
|
||||||
|
Tag: tag,
|
||||||
|
Operation: cserial.ToTypedMessage(
|
||||||
|
&handlerService.AddUserOperation{
|
||||||
|
User: user,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractInboundUsers(inb *core.InboundHandlerConfig) []*protocol.User {
|
||||||
|
if inb == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
inst, err := inb.ProxySettings.GetInstance()
|
||||||
|
if err != nil || inst == nil {
|
||||||
|
fmt.Println("failed to get inbound instance:", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch ty := inst.(type) {
|
||||||
|
case *vmessin.Config:
|
||||||
|
return ty.User
|
||||||
|
case *vlessin.Config:
|
||||||
|
return ty.Clients
|
||||||
|
case *trojan.ServerConfig:
|
||||||
|
return ty.Users
|
||||||
|
case *shadowsocks.ServerConfig:
|
||||||
|
return ty.Users
|
||||||
|
case *shadowsocks_2022.MultiUserServerConfig:
|
||||||
|
return ty.Users
|
||||||
|
default:
|
||||||
|
fmt.Println("unsupported inbound type")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractInboundsConfig(unnamedArgs []string) []conf.InboundDetourConfig {
|
||||||
|
ins := make([]conf.InboundDetourConfig, 0)
|
||||||
|
for _, arg := range unnamedArgs {
|
||||||
|
r, err := loadArg(arg)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to load %s: %s", arg, err)
|
||||||
|
}
|
||||||
|
conf, err := serial.DecodeJSONConfig(r)
|
||||||
|
if err != nil {
|
||||||
|
base.Fatalf("failed to decode %s: %s", arg, err)
|
||||||
|
}
|
||||||
|
ins = append(ins, conf.InboundConfigs...)
|
||||||
|
}
|
||||||
|
return ins
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeInboundUserAction(ctx context.Context, client handlerService.HandlerServiceClient, inb conf.InboundDetourConfig, action func(ctx context.Context, client handlerService.HandlerServiceClient, tag string, user *protocol.User) error) int {
|
||||||
|
success := 0
|
||||||
|
|
||||||
|
tag := inb.Tag
|
||||||
|
if len(tag) < 1 {
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("processing inbound:", tag)
|
||||||
|
built, err := inb.Build()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("failed to build config:", err)
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
users := extractInboundUsers(built)
|
||||||
|
if users == nil {
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range users {
|
||||||
|
if len(user.Email) < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := action(ctx, client, inb.Tag, user); err == nil {
|
||||||
|
fmt.Println("result: ok")
|
||||||
|
success += 1
|
||||||
|
} else {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return success
|
||||||
|
}
|
62
main/commands/all/api/inbound_user_remove.go
Normal file
62
main/commands/all/api/inbound_user_remove.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
handlerService "github.com/xtls/xray-core/app/proxyman/command"
|
||||||
|
cserial "github.com/xtls/xray-core/common/serial"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/main/commands/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cmdRemoveInboundUsers = &base.Command{
|
||||||
|
CustomFlags: true,
|
||||||
|
UsageLine: "{{.Exec}} api rmu [--server=127.0.0.1:8080] -tag=tag <email1> [email2]...",
|
||||||
|
Short: "Remove users from inbounds",
|
||||||
|
Long: `
|
||||||
|
Remove users from inbounds.
|
||||||
|
Arguments:
|
||||||
|
-s, -server
|
||||||
|
The API server address. Default 127.0.0.1:8080
|
||||||
|
-t, -timeout
|
||||||
|
Timeout seconds to call API. Default 3
|
||||||
|
-tag
|
||||||
|
Inbound tag
|
||||||
|
Example:
|
||||||
|
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -tag="vless-in" "xray@love.com" ...
|
||||||
|
`,
|
||||||
|
Run: executeRemoveUsers,
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeRemoveUsers(cmd *base.Command, args []string) {
|
||||||
|
setSharedFlags(cmd)
|
||||||
|
var tag string
|
||||||
|
cmd.Flag.StringVar(&tag, "tag", "", "")
|
||||||
|
cmd.Flag.Parse(args)
|
||||||
|
emails := cmd.Flag.Args()
|
||||||
|
if len(tag) < 1 {
|
||||||
|
base.Fatalf("inbound tag not specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, ctx, close := dialAPIServer()
|
||||||
|
defer close()
|
||||||
|
client := handlerService.NewHandlerServiceClient(conn)
|
||||||
|
|
||||||
|
success := 0
|
||||||
|
for _, email := range emails {
|
||||||
|
fmt.Println("remove user:", email)
|
||||||
|
_, err := client.AlterInbound(ctx, &handlerService.AlterInboundRequest{
|
||||||
|
Tag: tag,
|
||||||
|
Operation: cserial.ToTypedMessage(
|
||||||
|
&handlerService.RemoveUserOperation{
|
||||||
|
Email: email,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
success += 1
|
||||||
|
} else {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println("Removed", success, "user(s) in total.")
|
||||||
|
}
|
@@ -1,25 +1,26 @@
|
|||||||
package tls
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/base64"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/OmarTariq612/goech"
|
"github.com/xtls/reality/hpke"
|
||||||
"github.com/cloudflare/circl/hpke"
|
|
||||||
"github.com/xtls/xray-core/common"
|
"github.com/xtls/xray-core/common"
|
||||||
"github.com/xtls/xray-core/main/commands/base"
|
"github.com/xtls/xray-core/main/commands/base"
|
||||||
|
"github.com/xtls/xray-core/transport/internet/tls"
|
||||||
|
"golang.org/x/crypto/cryptobyte"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cmdECH = &base.Command{
|
var cmdECH = &base.Command{
|
||||||
UsageLine: `{{.Exec}} tls ech [--serverName (string)] [--json]`,
|
UsageLine: `{{.Exec}} tls ech [--serverName (string)] [--pem] [-i "ECHServerKeys (base64.StdEncoding)"]`,
|
||||||
Short: `Generate TLS-ECH certificates`,
|
Short: `Generate TLS-ECH certificates`,
|
||||||
Long: `
|
Long: `
|
||||||
Generate TLS-ECH certificates.
|
Generate TLS-ECH certificates.
|
||||||
|
|
||||||
Set serverName to your custom string: {{.Exec}} tls ech --serverName (string)
|
Set serverName to your custom string: {{.Exec}} tls ech --serverName (string)
|
||||||
Generate into json format: {{.Exec}} tls ech --json
|
Generate into pem format: {{.Exec}} tls ech --pem
|
||||||
|
Restore ECHConfigs from ECHServerKeys: {{.Exec}} tls ech -i "ECHServerKeys (base64.StdEncoding)"
|
||||||
`, // Enable PQ signature schemes: {{.Exec}} tls ech --pq-signature-schemes-enabled
|
`, // Enable PQ signature schemes: {{.Exec}} tls ech --pq-signature-schemes-enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,43 +28,66 @@ func init() {
|
|||||||
cmdECH.Run = executeECH
|
cmdECH.Run = executeECH
|
||||||
}
|
}
|
||||||
|
|
||||||
var input_pqSignatureSchemesEnabled = cmdECH.Flag.Bool("pqSignatureSchemesEnabled", false, "")
|
var input_echServerKeys = cmdECH.Flag.String("i", "", "ECHServerKeys (base64.StdEncoding)")
|
||||||
|
|
||||||
|
// var input_pqSignatureSchemesEnabled = cmdECH.Flag.Bool("pqSignatureSchemesEnabled", false, "")
|
||||||
var input_serverName = cmdECH.Flag.String("serverName", "cloudflare-ech.com", "")
|
var input_serverName = cmdECH.Flag.String("serverName", "cloudflare-ech.com", "")
|
||||||
var input_json = cmdECH.Flag.Bool("json", false, "True == turn on json output")
|
var input_pem = cmdECH.Flag.Bool("pem", false, "True == turn on pem output")
|
||||||
|
|
||||||
func executeECH(cmd *base.Command, args []string) {
|
func executeECH(cmd *base.Command, args []string) {
|
||||||
var kem hpke.KEM
|
var kem uint16
|
||||||
|
|
||||||
if *input_pqSignatureSchemesEnabled {
|
// if *input_pqSignatureSchemesEnabled {
|
||||||
kem = hpke.KEM_X25519_KYBER768_DRAFT00
|
// kem = 0x30 // hpke.KEM_X25519_KYBER768_DRAFT00
|
||||||
} else {
|
// } else {
|
||||||
kem = hpke.KEM_X25519_HKDF_SHA256
|
kem = hpke.DHKEM_X25519_HKDF_SHA256
|
||||||
}
|
// }
|
||||||
|
|
||||||
echKeySet, err := goech.GenerateECHKeySet(0, *input_serverName, kem)
|
echConfig, priv, err := tls.GenerateECHKeySet(0, *input_serverName, kem)
|
||||||
common.Must(err)
|
common.Must(err)
|
||||||
|
|
||||||
configBuffer, _ := echKeySet.ECHConfig.MarshalBinary()
|
var configBuffer, keyBuffer []byte
|
||||||
keyBuffer, _ := echKeySet.MarshalBinary()
|
if *input_echServerKeys == "" {
|
||||||
|
configBytes, _ := tls.MarshalBinary(echConfig)
|
||||||
configPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer}))
|
var b cryptobyte.Builder
|
||||||
keyPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer}))
|
b.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
|
||||||
if *input_json {
|
child.AddBytes(configBytes)
|
||||||
jECHConfigs := map[string]interface{}{
|
})
|
||||||
"configs": strings.Split(strings.TrimSpace(string(configPEM)), "\n"),
|
configBuffer, _ = b.Bytes()
|
||||||
}
|
var b2 cryptobyte.Builder
|
||||||
jECHKey := map[string]interface{}{
|
b2.AddUint16(uint16(len(priv)))
|
||||||
"key": strings.Split(strings.TrimSpace(string(keyPEM)), "\n"),
|
b2.AddBytes(priv)
|
||||||
}
|
b2.AddUint16(uint16(len(configBytes)))
|
||||||
|
b2.AddBytes(configBytes)
|
||||||
for _, i := range []map[string]interface{}{jECHConfigs, jECHKey} {
|
keyBuffer, _ = b2.Bytes()
|
||||||
content, err := json.MarshalIndent(i, "", " ")
|
|
||||||
common.Must(err)
|
|
||||||
os.Stdout.Write(content)
|
|
||||||
os.Stdout.WriteString("\n")
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
keySetsByte, err := base64.StdEncoding.DecodeString(*input_echServerKeys)
|
||||||
|
if err != nil {
|
||||||
|
os.Stdout.WriteString("Failed to decode ECHServerKeys: " + err.Error() + "\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keyBuffer = keySetsByte
|
||||||
|
KeySets, err := tls.ConvertToGoECHKeys(keySetsByte)
|
||||||
|
if err != nil {
|
||||||
|
os.Stdout.WriteString("Failed to decode ECHServerKeys: " + err.Error() + "\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var b cryptobyte.Builder
|
||||||
|
for _, keySet := range KeySets {
|
||||||
|
b.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
|
||||||
|
child.AddBytes(keySet.Config)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
configBuffer, _ = b.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
if *input_pem {
|
||||||
|
configPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer}))
|
||||||
|
keyPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer}))
|
||||||
os.Stdout.WriteString(configPEM)
|
os.Stdout.WriteString(configPEM)
|
||||||
os.Stdout.WriteString(keyPEM)
|
os.Stdout.WriteString(keyPEM)
|
||||||
|
} else {
|
||||||
|
os.Stdout.WriteString("ECH config list: \n" + base64.StdEncoding.EncodeToString(configBuffer) + "\n")
|
||||||
|
os.Stdout.WriteString("ECH server keys: \n" + base64.StdEncoding.EncodeToString(keyBuffer) + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -122,13 +122,19 @@ func executePing(cmd *base.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func printCertificates(certs []*x509.Certificate) {
|
func printCertificates(certs []*x509.Certificate) {
|
||||||
|
var leaf *x509.Certificate
|
||||||
|
var length int
|
||||||
for _, cert := range certs {
|
for _, cert := range certs {
|
||||||
if len(cert.DNSNames) == 0 {
|
length += len(cert.Raw)
|
||||||
continue
|
if len(cert.DNSNames) != 0 {
|
||||||
|
leaf = cert
|
||||||
}
|
}
|
||||||
fmt.Println("Cert's signature algorithm: ", cert.SignatureAlgorithm.String())
|
}
|
||||||
fmt.Println("Cert's publicKey algorithm: ", cert.PublicKeyAlgorithm.String())
|
fmt.Println("Certificate chain's total length: ", length, "(certs count: "+strconv.Itoa(len(certs))+")")
|
||||||
fmt.Println("Cert's allowed domains: ", cert.DNSNames)
|
if leaf != nil {
|
||||||
|
fmt.Println("Cert's signature algorithm: ", leaf.SignatureAlgorithm.String())
|
||||||
|
fmt.Println("Cert's publicKey algorithm: ", leaf.PublicKeyAlgorithm.String())
|
||||||
|
fmt.Println("Cert's allowed domains: ", leaf.DNSNames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/common/session"
|
"github.com/xtls/xray-core/common/session"
|
||||||
"github.com/xtls/xray-core/common/signal"
|
"github.com/xtls/xray-core/common/signal"
|
||||||
"github.com/xtls/xray-core/common/task"
|
"github.com/xtls/xray-core/common/task"
|
||||||
|
"github.com/xtls/xray-core/common/utils"
|
||||||
"github.com/xtls/xray-core/core"
|
"github.com/xtls/xray-core/core"
|
||||||
"github.com/xtls/xray-core/features/dns"
|
"github.com/xtls/xray-core/features/dns"
|
||||||
"github.com/xtls/xray-core/features/policy"
|
"github.com/xtls/xray-core/features/policy"
|
||||||
@@ -202,7 +203,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
writer = buf.NewWriter(conn)
|
writer = buf.NewWriter(conn)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
writer = NewPacketWriter(conn, h, ctx, UDPOverride)
|
writer = NewPacketWriter(conn, h, ctx, UDPOverride, destination)
|
||||||
if h.config.Noises != nil {
|
if h.config.Noises != nil {
|
||||||
errors.LogDebug(ctx, "NOISE", h.config.Noises)
|
errors.LogDebug(ctx, "NOISE", h.config.Noises)
|
||||||
writer = &NoisePacketWriter{
|
writer = &NoisePacketWriter{
|
||||||
@@ -238,7 +239,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
|
|||||||
if destination.Network == net.Network_TCP {
|
if destination.Network == net.Network_TCP {
|
||||||
reader = buf.NewReader(conn)
|
reader = buf.NewReader(conn)
|
||||||
} else {
|
} else {
|
||||||
reader = NewPacketReader(conn, UDPOverride)
|
reader = NewPacketReader(conn, UDPOverride, destination)
|
||||||
}
|
}
|
||||||
if err := buf.Copy(reader, output, buf.UpdateActivity(timer)); err != nil {
|
if err := buf.Copy(reader, output, buf.UpdateActivity(timer)); err != nil {
|
||||||
return errors.New("failed to process response").Base(err)
|
return errors.New("failed to process response").Base(err)
|
||||||
@@ -273,7 +274,7 @@ func isTLSConn(conn stat.Connection) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPacketReader(conn net.Conn, UDPOverride net.Destination) buf.Reader {
|
func NewPacketReader(conn net.Conn, UDPOverride net.Destination, DialDest net.Destination) buf.Reader {
|
||||||
iConn := conn
|
iConn := conn
|
||||||
statConn, ok := iConn.(*stat.CounterConnection)
|
statConn, ok := iConn.(*stat.CounterConnection)
|
||||||
if ok {
|
if ok {
|
||||||
@@ -283,10 +284,19 @@ func NewPacketReader(conn net.Conn, UDPOverride net.Destination) buf.Reader {
|
|||||||
if statConn != nil {
|
if statConn != nil {
|
||||||
counter = statConn.ReadCounter
|
counter = statConn.ReadCounter
|
||||||
}
|
}
|
||||||
if c, ok := iConn.(*internet.PacketConnWrapper); ok && UDPOverride.Address == nil && UDPOverride.Port == 0 {
|
if c, ok := iConn.(*internet.PacketConnWrapper); ok {
|
||||||
|
isOverridden := false
|
||||||
|
if UDPOverride.Address != nil || UDPOverride.Port != 0 {
|
||||||
|
isOverridden = true
|
||||||
|
}
|
||||||
|
changedAddress, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
|
||||||
|
|
||||||
return &PacketReader{
|
return &PacketReader{
|
||||||
PacketConnWrapper: c,
|
PacketConnWrapper: c,
|
||||||
Counter: counter,
|
Counter: counter,
|
||||||
|
IsOverridden: isOverridden,
|
||||||
|
InitUnchangedAddr: DialDest.Address,
|
||||||
|
InitChangedAddr: net.ParseAddress(changedAddress),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &buf.PacketReader{Reader: conn}
|
return &buf.PacketReader{Reader: conn}
|
||||||
@@ -295,6 +305,9 @@ func NewPacketReader(conn net.Conn, UDPOverride net.Destination) buf.Reader {
|
|||||||
type PacketReader struct {
|
type PacketReader struct {
|
||||||
*internet.PacketConnWrapper
|
*internet.PacketConnWrapper
|
||||||
stats.Counter
|
stats.Counter
|
||||||
|
IsOverridden bool
|
||||||
|
InitUnchangedAddr net.Address
|
||||||
|
InitChangedAddr net.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||||
@@ -306,10 +319,18 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
b.Resize(0, int32(n))
|
b.Resize(0, int32(n))
|
||||||
b.UDP = &net.Destination{
|
// if udp dest addr is changed, we are unable to get the correct src addr
|
||||||
Address: net.IPAddress(d.(*net.UDPAddr).IP),
|
// so we don't attach src info to udp packet, break cone behavior, assuming the dial dest is the expected scr addr
|
||||||
Port: net.Port(d.(*net.UDPAddr).Port),
|
if !r.IsOverridden {
|
||||||
Network: net.Network_UDP,
|
address := net.IPAddress(d.(*net.UDPAddr).IP)
|
||||||
|
if r.InitChangedAddr == address {
|
||||||
|
address = r.InitUnchangedAddr
|
||||||
|
}
|
||||||
|
b.UDP = &net.Destination{
|
||||||
|
Address: address,
|
||||||
|
Port: net.Port(d.(*net.UDPAddr).Port),
|
||||||
|
Network: net.Network_UDP,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if r.Counter != nil {
|
if r.Counter != nil {
|
||||||
r.Counter.Add(int64(n))
|
r.Counter.Add(int64(n))
|
||||||
@@ -317,7 +338,8 @@ func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
|||||||
return buf.MultiBuffer{b}, nil
|
return buf.MultiBuffer{b}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPacketWriter(conn net.Conn, h *Handler, ctx context.Context, UDPOverride net.Destination) buf.Writer {
|
// DialDest means the dial target used in the dialer when creating conn
|
||||||
|
func NewPacketWriter(conn net.Conn, h *Handler, ctx context.Context, UDPOverride net.Destination, DialDest net.Destination) buf.Writer {
|
||||||
iConn := conn
|
iConn := conn
|
||||||
statConn, ok := iConn.(*stat.CounterConnection)
|
statConn, ok := iConn.(*stat.CounterConnection)
|
||||||
if ok {
|
if ok {
|
||||||
@@ -328,12 +350,20 @@ func NewPacketWriter(conn net.Conn, h *Handler, ctx context.Context, UDPOverride
|
|||||||
counter = statConn.WriteCounter
|
counter = statConn.WriteCounter
|
||||||
}
|
}
|
||||||
if c, ok := iConn.(*internet.PacketConnWrapper); ok {
|
if c, ok := iConn.(*internet.PacketConnWrapper); ok {
|
||||||
|
// If DialDest is a domain, it will be resolved in dialer
|
||||||
|
// check this behavior and add it to map
|
||||||
|
resolvedUDPAddr := utils.NewTypedSyncMap[string, net.Address]()
|
||||||
|
if DialDest.Address.Family().IsDomain() {
|
||||||
|
RemoteAddress, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
|
||||||
|
resolvedUDPAddr.Store(DialDest.Address.String(), net.ParseAddress(RemoteAddress))
|
||||||
|
}
|
||||||
return &PacketWriter{
|
return &PacketWriter{
|
||||||
PacketConnWrapper: c,
|
PacketConnWrapper: c,
|
||||||
Counter: counter,
|
Counter: counter,
|
||||||
Handler: h,
|
Handler: h,
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
UDPOverride: UDPOverride,
|
UDPOverride: UDPOverride,
|
||||||
|
resolvedUDPAddr: resolvedUDPAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -346,6 +376,12 @@ type PacketWriter struct {
|
|||||||
*Handler
|
*Handler
|
||||||
context.Context
|
context.Context
|
||||||
UDPOverride net.Destination
|
UDPOverride net.Destination
|
||||||
|
|
||||||
|
// Dest of udp packets might be a domain, we will resolve them to IP
|
||||||
|
// But resolver will return a random one if the domain has many IPs
|
||||||
|
// Resulting in these packets being sent to many different IPs randomly
|
||||||
|
// So, cache and keep the resolve result
|
||||||
|
resolvedUDPAddr *utils.TypedSyncMap[string, net.Address]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||||
@@ -364,10 +400,34 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
|||||||
if w.UDPOverride.Port != 0 {
|
if w.UDPOverride.Port != 0 {
|
||||||
b.UDP.Port = w.UDPOverride.Port
|
b.UDP.Port = w.UDPOverride.Port
|
||||||
}
|
}
|
||||||
if w.Handler.config.hasStrategy() && b.UDP.Address.Family().IsDomain() {
|
if b.UDP.Address.Family().IsDomain() {
|
||||||
ip := w.Handler.resolveIP(w.Context, b.UDP.Address.Domain(), nil)
|
if ip, ok := w.resolvedUDPAddr.Load(b.UDP.Address.Domain()); ok {
|
||||||
if ip != nil {
|
|
||||||
b.UDP.Address = ip
|
b.UDP.Address = ip
|
||||||
|
} else {
|
||||||
|
ShouldUseSystemResolver := true
|
||||||
|
if w.Handler.config.hasStrategy() {
|
||||||
|
ip = w.Handler.resolveIP(w.Context, b.UDP.Address.Domain(), nil)
|
||||||
|
if ip != nil {
|
||||||
|
ShouldUseSystemResolver = false
|
||||||
|
}
|
||||||
|
// drop packet if resolve failed when forceIP
|
||||||
|
if ip == nil && w.Handler.config.forceIP() {
|
||||||
|
b.Release()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ShouldUseSystemResolver {
|
||||||
|
udpAddr, err := net.ResolveUDPAddr("udp", b.UDP.NetAddr())
|
||||||
|
if err != nil {
|
||||||
|
b.Release()
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
ip = net.IPAddress(udpAddr.IP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ip != nil {
|
||||||
|
b.UDP.Address, _ = w.resolvedUDPAddr.LoadOrStore(b.UDP.Address.Domain(), ip)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
destAddr, _ := net.ResolveUDPAddr("udp", b.UDP.NetAddr())
|
destAddr, _ := net.ResolveUDPAddr("udp", b.UDP.NetAddr())
|
||||||
|
@@ -128,6 +128,7 @@ func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dis
|
|||||||
|
|
||||||
conn.Write(data.Bytes())
|
conn.Write(data.Bytes())
|
||||||
})
|
})
|
||||||
|
defer udpServer.RemoveRay()
|
||||||
|
|
||||||
inbound := session.InboundFromContext(ctx)
|
inbound := session.InboundFromContext(ctx)
|
||||||
var dest *net.Destination
|
var dest *net.Destination
|
||||||
|
@@ -245,13 +245,15 @@ func (s *Server) handleUDPPayload(ctx context.Context, conn stat.Connection, dis
|
|||||||
udpMessage, err := EncodeUDPPacket(request, payload.Bytes())
|
udpMessage, err := EncodeUDPPacket(request, payload.Bytes())
|
||||||
payload.Release()
|
payload.Release()
|
||||||
|
|
||||||
defer udpMessage.Release()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors.LogWarningInner(ctx, err, "failed to write UDP response")
|
errors.LogWarningInner(ctx, err, "failed to write UDP response")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
defer udpMessage.Release()
|
||||||
|
|
||||||
conn.Write(udpMessage.Bytes())
|
conn.Write(udpMessage.Bytes())
|
||||||
})
|
})
|
||||||
|
defer udpServer.RemoveRay()
|
||||||
|
|
||||||
inbound := session.InboundFromContext(ctx)
|
inbound := session.InboundFromContext(ctx)
|
||||||
if inbound != nil && inbound.Source.IsValid() {
|
if inbound != nil && inbound.Source.IsValid() {
|
||||||
|
@@ -259,6 +259,7 @@ func (s *Server) handleUDPPayload(ctx context.Context, clientReader *PacketReade
|
|||||||
errors.LogWarningInner(ctx, err, "failed to write response")
|
errors.LogWarningInner(ctx, err, "failed to write response")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
defer udpServer.RemoveRay()
|
||||||
|
|
||||||
inbound := session.InboundFromContext(ctx)
|
inbound := session.InboundFromContext(ctx)
|
||||||
user := inbound.User
|
user := inbound.User
|
||||||
|
@@ -206,7 +206,10 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
|
|||||||
|
|
||||||
first := buf.FromBytes(make([]byte, buf.Size))
|
first := buf.FromBytes(make([]byte, buf.Size))
|
||||||
first.Clear()
|
first.Clear()
|
||||||
firstLen, _ := first.ReadFrom(connection)
|
firstLen, errR := first.ReadFrom(connection)
|
||||||
|
if errR != nil {
|
||||||
|
return errR
|
||||||
|
}
|
||||||
errors.LogInfo(ctx, "firstLen = ", firstLen)
|
errors.LogInfo(ctx, "firstLen = ", firstLen)
|
||||||
|
|
||||||
reader := &buf.BufferedReader{
|
reader := &buf.BufferedReader{
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/cloudflare/circl/sign/mldsa/mldsa65"
|
||||||
"github.com/xtls/reality"
|
"github.com/xtls/reality"
|
||||||
"github.com/xtls/xray-core/common/errors"
|
"github.com/xtls/xray-core/common/errors"
|
||||||
"github.com/xtls/xray-core/transport/internet"
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
@@ -27,13 +28,15 @@ func (c *Config) GetREALITYConfig() *reality.Config {
|
|||||||
MaxClientVer: c.MaxClientVer,
|
MaxClientVer: c.MaxClientVer,
|
||||||
MaxTimeDiff: time.Duration(c.MaxTimeDiff) * time.Millisecond,
|
MaxTimeDiff: time.Duration(c.MaxTimeDiff) * time.Millisecond,
|
||||||
|
|
||||||
Mldsa65Key: c.Mldsa65Key,
|
|
||||||
|
|
||||||
NextProtos: nil, // should be nil
|
NextProtos: nil, // should be nil
|
||||||
SessionTicketsDisabled: true,
|
SessionTicketsDisabled: true,
|
||||||
|
|
||||||
KeyLogWriter: KeyLogWriterFromConfig(c),
|
KeyLogWriter: KeyLogWriterFromConfig(c),
|
||||||
}
|
}
|
||||||
|
if c.Mldsa65Seed != nil {
|
||||||
|
_, key := mldsa65.NewKeyFromSeed((*[32]byte)(c.Mldsa65Seed))
|
||||||
|
config.Mldsa65Key = key.Bytes()
|
||||||
|
}
|
||||||
if c.LimitFallbackUpload != nil {
|
if c.LimitFallbackUpload != nil {
|
||||||
config.LimitFallbackUpload.AfterBytes = c.LimitFallbackUpload.AfterBytes
|
config.LimitFallbackUpload.AfterBytes = c.LimitFallbackUpload.AfterBytes
|
||||||
config.LimitFallbackUpload.BytesPerSec = c.LimitFallbackUpload.BytesPerSec
|
config.LimitFallbackUpload.BytesPerSec = c.LimitFallbackUpload.BytesPerSec
|
||||||
|
@@ -35,7 +35,7 @@ type Config struct {
|
|||||||
MaxClientVer []byte `protobuf:"bytes,8,opt,name=max_client_ver,json=maxClientVer,proto3" json:"max_client_ver,omitempty"`
|
MaxClientVer []byte `protobuf:"bytes,8,opt,name=max_client_ver,json=maxClientVer,proto3" json:"max_client_ver,omitempty"`
|
||||||
MaxTimeDiff uint64 `protobuf:"varint,9,opt,name=max_time_diff,json=maxTimeDiff,proto3" json:"max_time_diff,omitempty"`
|
MaxTimeDiff uint64 `protobuf:"varint,9,opt,name=max_time_diff,json=maxTimeDiff,proto3" json:"max_time_diff,omitempty"`
|
||||||
ShortIds [][]byte `protobuf:"bytes,10,rep,name=short_ids,json=shortIds,proto3" json:"short_ids,omitempty"`
|
ShortIds [][]byte `protobuf:"bytes,10,rep,name=short_ids,json=shortIds,proto3" json:"short_ids,omitempty"`
|
||||||
Mldsa65Key []byte `protobuf:"bytes,11,opt,name=mldsa65_key,json=mldsa65Key,proto3" json:"mldsa65_key,omitempty"`
|
Mldsa65Seed []byte `protobuf:"bytes,11,opt,name=mldsa65_seed,json=mldsa65Seed,proto3" json:"mldsa65_seed,omitempty"`
|
||||||
LimitFallbackUpload *LimitFallback `protobuf:"bytes,12,opt,name=limit_fallback_upload,json=limitFallbackUpload,proto3" json:"limit_fallback_upload,omitempty"`
|
LimitFallbackUpload *LimitFallback `protobuf:"bytes,12,opt,name=limit_fallback_upload,json=limitFallbackUpload,proto3" json:"limit_fallback_upload,omitempty"`
|
||||||
LimitFallbackDownload *LimitFallback `protobuf:"bytes,13,opt,name=limit_fallback_download,json=limitFallbackDownload,proto3" json:"limit_fallback_download,omitempty"`
|
LimitFallbackDownload *LimitFallback `protobuf:"bytes,13,opt,name=limit_fallback_download,json=limitFallbackDownload,proto3" json:"limit_fallback_download,omitempty"`
|
||||||
Fingerprint string `protobuf:"bytes,21,opt,name=Fingerprint,proto3" json:"Fingerprint,omitempty"`
|
Fingerprint string `protobuf:"bytes,21,opt,name=Fingerprint,proto3" json:"Fingerprint,omitempty"`
|
||||||
@@ -148,9 +148,9 @@ func (x *Config) GetShortIds() [][]byte {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) GetMldsa65Key() []byte {
|
func (x *Config) GetMldsa65Seed() []byte {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Mldsa65Key
|
return x.Mldsa65Seed
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -293,7 +293,7 @@ var file_transport_internet_reality_config_proto_rawDesc = []byte{
|
|||||||
0x72, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x2f, 0x63, 0x6f, 0x6e,
|
0x72, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x2f, 0x63, 0x6f, 0x6e,
|
||||||
0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x78, 0x72, 0x61, 0x79, 0x2e,
|
0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1f, 0x78, 0x72, 0x61, 0x79, 0x2e,
|
||||||
0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
|
0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
|
||||||
0x65, 0x74, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x22, 0x96, 0x06, 0x0a, 0x06, 0x43,
|
0x65, 0x74, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x22, 0x98, 0x06, 0x0a, 0x06, 0x43,
|
||||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x68, 0x6f, 0x77, 0x18, 0x01, 0x20,
|
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x68, 0x6f, 0x77, 0x18, 0x01, 0x20,
|
||||||
0x01, 0x28, 0x08, 0x52, 0x04, 0x73, 0x68, 0x6f, 0x77, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73,
|
0x01, 0x28, 0x08, 0x52, 0x04, 0x73, 0x68, 0x6f, 0x77, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73,
|
||||||
0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a,
|
0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a,
|
||||||
@@ -312,55 +312,55 @@ var file_transport_internet_reality_config_proto_rawDesc = []byte{
|
|||||||
0x65, 0x5f, 0x64, 0x69, 0x66, 0x66, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61,
|
0x65, 0x5f, 0x64, 0x69, 0x66, 0x66, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61,
|
||||||
0x78, 0x54, 0x69, 0x6d, 0x65, 0x44, 0x69, 0x66, 0x66, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x68, 0x6f,
|
0x78, 0x54, 0x69, 0x6d, 0x65, 0x44, 0x69, 0x66, 0x66, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x68, 0x6f,
|
||||||
0x72, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x68,
|
0x72, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x68,
|
||||||
0x6f, 0x72, 0x74, 0x49, 0x64, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x6c, 0x64, 0x73, 0x61, 0x36,
|
0x6f, 0x72, 0x74, 0x49, 0x64, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x6c, 0x64, 0x73, 0x61, 0x36,
|
||||||
0x35, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6d, 0x6c, 0x64,
|
0x35, 0x5f, 0x73, 0x65, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x6d, 0x6c,
|
||||||
0x73, 0x61, 0x36, 0x35, 0x4b, 0x65, 0x79, 0x12, 0x62, 0x0a, 0x15, 0x6c, 0x69, 0x6d, 0x69, 0x74,
|
0x64, 0x73, 0x61, 0x36, 0x35, 0x53, 0x65, 0x65, 0x64, 0x12, 0x62, 0x0a, 0x15, 0x6c, 0x69, 0x6d,
|
||||||
0x5f, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64,
|
0x69, 0x74, 0x5f, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x75, 0x70, 0x6c, 0x6f,
|
||||||
0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72,
|
0x61, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
|
||||||
0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74,
|
0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
|
||||||
0x2e, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x2e, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x46, 0x61,
|
0x65, 0x74, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x2e, 0x4c, 0x69, 0x6d, 0x69, 0x74,
|
||||||
0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x13, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x46, 0x61, 0x6c,
|
0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x13, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x46,
|
||||||
0x6c, 0x62, 0x61, 0x63, 0x6b, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x66, 0x0a, 0x17, 0x6c,
|
0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x66, 0x0a,
|
||||||
0x69, 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x64, 0x6f,
|
0x17, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x5f,
|
||||||
0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x78,
|
0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e,
|
||||||
0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e,
|
|
||||||
0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x2e, 0x4c,
|
|
||||||
0x69, 0x6d, 0x69, 0x74, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x15, 0x6c, 0x69,
|
|
||||||
0x6d, 0x69, 0x74, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x44, 0x6f, 0x77, 0x6e, 0x6c,
|
|
||||||
0x6f, 0x61, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69,
|
|
||||||
0x6e, 0x74, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72,
|
|
||||||
0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f,
|
|
||||||
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x72, 0x76,
|
|
||||||
0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
|
|
||||||
0x5f, 0x6b, 0x65, 0x79, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c,
|
|
||||||
0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x69,
|
|
||||||
0x64, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x49, 0x64,
|
|
||||||
0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x6c, 0x64, 0x73, 0x61, 0x36, 0x35, 0x5f, 0x76, 0x65, 0x72, 0x69,
|
|
||||||
0x66, 0x79, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x6d, 0x6c, 0x64, 0x73, 0x61, 0x36,
|
|
||||||
0x35, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x70, 0x69, 0x64, 0x65,
|
|
||||||
0x72, 0x5f, 0x78, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x70, 0x69, 0x64, 0x65,
|
|
||||||
0x72, 0x58, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x70, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x79, 0x18, 0x1b,
|
|
||||||
0x20, 0x03, 0x28, 0x03, 0x52, 0x07, 0x73, 0x70, 0x69, 0x64, 0x65, 0x72, 0x59, 0x12, 0x24, 0x0a,
|
|
||||||
0x0e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f, 0x67, 0x18,
|
|
||||||
0x1f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79,
|
|
||||||
0x4c, 0x6f, 0x67, 0x22, 0x83, 0x01, 0x0a, 0x0d, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x46, 0x61, 0x6c,
|
|
||||||
0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x66, 0x74, 0x65, 0x72, 0x5f, 0x62,
|
|
||||||
0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x61, 0x66, 0x74, 0x65,
|
|
||||||
0x72, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f,
|
|
||||||
0x70, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x62,
|
|
||||||
0x79, 0x74, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x12, 0x2d, 0x0a, 0x13, 0x62, 0x75,
|
|
||||||
0x72, 0x73, 0x74, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x73, 0x65,
|
|
||||||
0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x62, 0x75, 0x72, 0x73, 0x74, 0x42, 0x79,
|
|
||||||
0x74, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x42, 0x7f, 0x0a, 0x23, 0x63, 0x6f, 0x6d,
|
|
||||||
0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e,
|
0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e,
|
||||||
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x79,
|
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x79,
|
||||||
0x50, 0x01, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78,
|
0x2e, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x15,
|
||||||
0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72,
|
0x6c, 0x69, 0x6d, 0x69, 0x74, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x44, 0x6f, 0x77,
|
||||||
0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74,
|
0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x46, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70,
|
||||||
0x2f, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x79, 0xaa, 0x02, 0x1f, 0x58, 0x72, 0x61, 0x79, 0x2e,
|
0x72, 0x69, 0x6e, 0x74, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x46, 0x69, 0x6e, 0x67,
|
||||||
0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e,
|
0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65,
|
||||||
0x65, 0x74, 0x2e, 0x52, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65,
|
||||||
0x6f, 0x33,
|
0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c,
|
||||||
|
0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75,
|
||||||
|
0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x68, 0x6f, 0x72, 0x74,
|
||||||
|
0x5f, 0x69, 0x64, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x73, 0x68, 0x6f, 0x72, 0x74,
|
||||||
|
0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x6c, 0x64, 0x73, 0x61, 0x36, 0x35, 0x5f, 0x76, 0x65,
|
||||||
|
0x72, 0x69, 0x66, 0x79, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x6d, 0x6c, 0x64, 0x73,
|
||||||
|
0x61, 0x36, 0x35, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x70, 0x69,
|
||||||
|
0x64, 0x65, 0x72, 0x5f, 0x78, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x70, 0x69,
|
||||||
|
0x64, 0x65, 0x72, 0x58, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x70, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x79,
|
||||||
|
0x18, 0x1b, 0x20, 0x03, 0x28, 0x03, 0x52, 0x07, 0x73, 0x70, 0x69, 0x64, 0x65, 0x72, 0x59, 0x12,
|
||||||
|
0x24, 0x0a, 0x0e, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x6f,
|
||||||
|
0x67, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b,
|
||||||
|
0x65, 0x79, 0x4c, 0x6f, 0x67, 0x22, 0x83, 0x01, 0x0a, 0x0d, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x46,
|
||||||
|
0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x66, 0x74, 0x65, 0x72,
|
||||||
|
0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x61, 0x66,
|
||||||
|
0x74, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x62, 0x79, 0x74, 0x65,
|
||||||
|
0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52,
|
||||||
|
0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x12, 0x2d, 0x0a, 0x13,
|
||||||
|
0x62, 0x75, 0x72, 0x73, 0x74, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f,
|
||||||
|
0x73, 0x65, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x62, 0x75, 0x72, 0x73, 0x74,
|
||||||
|
0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x42, 0x7f, 0x0a, 0x23, 0x63,
|
||||||
|
0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
|
||||||
|
0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x69,
|
||||||
|
0x74, 0x79, 0x50, 0x01, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
||||||
|
0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f,
|
||||||
|
0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
|
||||||
|
0x65, 0x74, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x79, 0xaa, 0x02, 0x1f, 0x58, 0x72, 0x61,
|
||||||
|
0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65,
|
||||||
|
0x72, 0x6e, 0x65, 0x74, 0x2e, 0x52, 0x65, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x62, 0x06, 0x70, 0x72,
|
||||||
|
0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@@ -18,7 +18,7 @@ message Config {
|
|||||||
uint64 max_time_diff = 9;
|
uint64 max_time_diff = 9;
|
||||||
repeated bytes short_ids = 10;
|
repeated bytes short_ids = 10;
|
||||||
|
|
||||||
bytes mldsa65_key = 11;
|
bytes mldsa65_seed = 11;
|
||||||
LimitFallback limit_fallback_upload = 12;
|
LimitFallback limit_fallback_upload = 12;
|
||||||
LimitFallback limit_fallback_download = 13;
|
LimitFallback limit_fallback_download = 13;
|
||||||
|
|
||||||
|
@@ -77,7 +77,8 @@ func (c *UConn) HandshakeAddress() net.Address {
|
|||||||
func (c *UConn) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
func (c *UConn) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||||
if c.Config.Show {
|
if c.Config.Show {
|
||||||
localAddr := c.LocalAddr().String()
|
localAddr := c.LocalAddr().String()
|
||||||
fmt.Printf("REALITY localAddr: %v\tis using X25519MLKEM768 for TLS' communication: %v\n", localAddr, c.HandshakeState.ServerHello.SelectedGroup == utls.X25519MLKEM768)
|
curveID := *(*utls.CurveID)(unsafe.Pointer(reflect.ValueOf(c).Elem().FieldByName("curveID").UnsafeAddr()))
|
||||||
|
fmt.Printf("REALITY localAddr: %v\tis using X25519MLKEM768 for TLS' communication: %v\n", localAddr, curveID == utls.X25519MLKEM768)
|
||||||
fmt.Printf("REALITY localAddr: %v\tis using ML-DSA-65 for cert's extra verification: %v\n", localAddr, len(c.Config.Mldsa65Verify) > 0)
|
fmt.Printf("REALITY localAddr: %v\tis using ML-DSA-65 for cert's extra verification: %v\n", localAddr, len(c.Config.Mldsa65Verify) > 0)
|
||||||
}
|
}
|
||||||
p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
|
p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
|
||||||
|
@@ -64,26 +64,6 @@ func applyOutboundSocketOptions(network string, address string, fd uintptr, conf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.TcpKeepAliveInterval > 0 || config.TcpKeepAliveIdle > 0 {
|
|
||||||
if config.TcpKeepAliveInterval > 0 {
|
|
||||||
if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, int(config.TcpKeepAliveInterval)); err != nil {
|
|
||||||
return errors.New("failed to set TCP_KEEPINTVL", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if config.TcpKeepAliveIdle > 0 {
|
|
||||||
if err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, int(config.TcpKeepAliveIdle)); err != nil {
|
|
||||||
return errors.New("failed to set TCP_KEEPIDLE", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1); err != nil {
|
|
||||||
return errors.New("failed to set SO_KEEPALIVE", err)
|
|
||||||
}
|
|
||||||
} else if config.TcpKeepAliveInterval < 0 || config.TcpKeepAliveIdle < 0 {
|
|
||||||
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 0); err != nil {
|
|
||||||
return errors.New("failed to unset SO_KEEPALIVE", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.TcpCongestion != "" {
|
if config.TcpCongestion != "" {
|
||||||
if err := syscall.SetsockoptString(int(fd), syscall.SOL_TCP, syscall.TCP_CONGESTION, config.TcpCongestion); err != nil {
|
if err := syscall.SetsockoptString(int(fd), syscall.SOL_TCP, syscall.TCP_CONGESTION, config.TcpCongestion); err != nil {
|
||||||
return errors.New("failed to set TCP_CONGESTION", err)
|
return errors.New("failed to set TCP_CONGESTION", err)
|
||||||
|
@@ -3,6 +3,7 @@ package internet
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
gonet "net"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -87,14 +88,34 @@ func (d *DefaultSystemDialer) Dial(ctx context.Context, src net.Address, dest ne
|
|||||||
Dest: destAddr,
|
Dest: destAddr,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
goStdKeepAlive := time.Duration(0)
|
// Chrome defaults
|
||||||
if sockopt != nil && (sockopt.TcpKeepAliveInterval != 0 || sockopt.TcpKeepAliveIdle != 0) {
|
keepAliveConfig := gonet.KeepAliveConfig{
|
||||||
goStdKeepAlive = time.Duration(-1)
|
Enable: true,
|
||||||
|
Idle: 45 * time.Second,
|
||||||
|
Interval: 45 * time.Second,
|
||||||
|
Count: -1,
|
||||||
|
}
|
||||||
|
keepAlive := time.Duration(0)
|
||||||
|
if sockopt != nil {
|
||||||
|
if sockopt.TcpKeepAliveIdle*sockopt.TcpKeepAliveInterval < 0 {
|
||||||
|
return nil, errors.New("invalid TcpKeepAliveIdle or TcpKeepAliveInterval value: ", sockopt.TcpKeepAliveIdle, " ", sockopt.TcpKeepAliveInterval)
|
||||||
|
}
|
||||||
|
if sockopt.TcpKeepAliveIdle < 0 || sockopt.TcpKeepAliveInterval < 0 {
|
||||||
|
keepAlive = -1
|
||||||
|
keepAliveConfig.Enable = false
|
||||||
|
}
|
||||||
|
if sockopt.TcpKeepAliveIdle > 0 {
|
||||||
|
keepAliveConfig.Idle = time.Duration(sockopt.TcpKeepAliveIdle) * time.Second
|
||||||
|
}
|
||||||
|
if sockopt.TcpKeepAliveInterval > 0 {
|
||||||
|
keepAliveConfig.Interval = time.Duration(sockopt.TcpKeepAliveInterval) * time.Second
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dialer := &net.Dialer{
|
dialer := &net.Dialer{
|
||||||
Timeout: time.Second * 16,
|
Timeout: time.Second * 16,
|
||||||
LocalAddr: resolveSrcAddr(dest.Network, src),
|
LocalAddr: resolveSrcAddr(dest.Network, src),
|
||||||
KeepAlive: goStdKeepAlive,
|
KeepAlive: keepAlive,
|
||||||
|
KeepAliveConfig: keepAliveConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
if sockopt != nil || len(d.controllers) > 0 {
|
if sockopt != nil || len(d.controllers) > 0 {
|
||||||
|
@@ -2,6 +2,7 @@ package internet
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
gonet "net"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -88,9 +89,25 @@ func (dl *DefaultListener) Listen(ctx context.Context, addr net.Addr, sockopt *S
|
|||||||
network = addr.Network()
|
network = addr.Network()
|
||||||
address = addr.String()
|
address = addr.String()
|
||||||
lc.Control = getControlFunc(ctx, sockopt, dl.controllers)
|
lc.Control = getControlFunc(ctx, sockopt, dl.controllers)
|
||||||
|
// default disable keepalive
|
||||||
|
lc.KeepAlive = -1
|
||||||
if sockopt != nil {
|
if sockopt != nil {
|
||||||
if sockopt.TcpKeepAliveInterval != 0 || sockopt.TcpKeepAliveIdle != 0 {
|
if sockopt.TcpKeepAliveIdle*sockopt.TcpKeepAliveInterval < 0 {
|
||||||
lc.KeepAlive = time.Duration(-1)
|
return nil, errors.New("invalid TcpKeepAliveIdle or TcpKeepAliveInterval value: ", sockopt.TcpKeepAliveIdle, " ", sockopt.TcpKeepAliveInterval)
|
||||||
|
}
|
||||||
|
lc.KeepAliveConfig = gonet.KeepAliveConfig{
|
||||||
|
Enable: false,
|
||||||
|
Idle: -1,
|
||||||
|
Interval: -1,
|
||||||
|
Count: -1,
|
||||||
|
}
|
||||||
|
if sockopt.TcpKeepAliveIdle > 0 {
|
||||||
|
lc.KeepAliveConfig.Enable = true
|
||||||
|
lc.KeepAliveConfig.Idle = time.Duration(sockopt.TcpKeepAliveIdle) * time.Second
|
||||||
|
}
|
||||||
|
if sockopt.TcpKeepAliveInterval > 0 {
|
||||||
|
lc.KeepAliveConfig.Enable = true
|
||||||
|
lc.KeepAliveConfig.Interval = time.Duration(sockopt.TcpKeepAliveInterval) * time.Second
|
||||||
}
|
}
|
||||||
if sockopt.TcpMptcp {
|
if sockopt.TcpMptcp {
|
||||||
lc.SetMultipathTCP(true)
|
lc.SetMultipathTCP(true)
|
||||||
|
@@ -444,6 +444,12 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
|
|||||||
config.KeyLogWriter = writer
|
config.KeyLogWriter = writer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(c.EchConfigList) > 0 || len(c.EchServerKeys) > 0 {
|
||||||
|
err := ApplyECH(c, config)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogError(context.Background(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
@@ -217,6 +217,8 @@ type Config struct {
|
|||||||
// @Document After allow_insecure (automatically), if the server's cert can't be verified by any of these names, pinned_peer_certificate_chain_sha256 will be tried.
|
// @Document After allow_insecure (automatically), if the server's cert can't be verified by any of these names, pinned_peer_certificate_chain_sha256 will be tried.
|
||||||
// @Critical
|
// @Critical
|
||||||
VerifyPeerCertInNames []string `protobuf:"bytes,17,rep,name=verify_peer_cert_in_names,json=verifyPeerCertInNames,proto3" json:"verify_peer_cert_in_names,omitempty"`
|
VerifyPeerCertInNames []string `protobuf:"bytes,17,rep,name=verify_peer_cert_in_names,json=verifyPeerCertInNames,proto3" json:"verify_peer_cert_in_names,omitempty"`
|
||||||
|
EchConfigList string `protobuf:"bytes,18,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"`
|
||||||
|
EchServerKeys []byte `protobuf:"bytes,19,opt,name=ech_server_keys,json=echServerKeys,proto3" json:"ech_server_keys,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Config) Reset() {
|
func (x *Config) Reset() {
|
||||||
@@ -361,6 +363,20 @@ func (x *Config) GetVerifyPeerCertInNames() []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetEchConfigList() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.EchConfigList
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GetEchServerKeys() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.EchServerKeys
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var File_transport_internet_tls_config_proto protoreflect.FileDescriptor
|
var File_transport_internet_tls_config_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_transport_internet_tls_config_proto_rawDesc = []byte{
|
var file_transport_internet_tls_config_proto_rawDesc = []byte{
|
||||||
@@ -392,7 +408,7 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{
|
|||||||
0x4e, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a,
|
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,
|
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,
|
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, 0x9a, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
|
0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xea, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
|
||||||
0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73,
|
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,
|
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, 0x4a, 0x0a, 0x0b, 0x63, 0x65,
|
0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x63, 0x65,
|
||||||
@@ -442,7 +458,12 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{
|
|||||||
0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f,
|
0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f,
|
||||||
0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15,
|
0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15,
|
||||||
0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x49, 0x6e,
|
0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x49, 0x6e,
|
||||||
0x4e, 0x61, 0x6d, 0x65, 0x73, 0x42, 0x73, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61,
|
0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x63, 0x68, 0x5f, 0x63, 0x6f, 0x6e,
|
||||||
|
0x66, 0x69, 0x67, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d,
|
||||||
|
0x65, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x26, 0x0a,
|
||||||
|
0x0f, 0x65, 0x63, 0x68, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x73,
|
||||||
|
0x18, 0x13, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x63, 0x68, 0x53, 0x65, 0x72, 0x76, 0x65,
|
||||||
|
0x72, 0x4b, 0x65, 0x79, 0x73, 0x42, 0x73, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61,
|
||||||
0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
|
0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
|
||||||
0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68,
|
0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68,
|
||||||
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79,
|
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79,
|
||||||
|
@@ -91,4 +91,8 @@ message Config {
|
|||||||
@Critical
|
@Critical
|
||||||
*/
|
*/
|
||||||
repeated string verify_peer_cert_in_names = 17;
|
repeated string verify_peer_cert_in_names = 17;
|
||||||
|
|
||||||
|
string ech_config_list = 18;
|
||||||
|
|
||||||
|
bytes ech_server_keys = 19;
|
||||||
}
|
}
|
||||||
|
367
transport/internet/tls/ech.go
Normal file
367
transport/internet/tls/ech.go
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/ecdh"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"github.com/xtls/reality"
|
||||||
|
"github.com/xtls/reality/hpke"
|
||||||
|
"github.com/xtls/xray-core/common/errors"
|
||||||
|
"github.com/xtls/xray-core/common/net"
|
||||||
|
"github.com/xtls/xray-core/common/utils"
|
||||||
|
"github.com/xtls/xray-core/transport/internet"
|
||||||
|
"golang.org/x/crypto/cryptobyte"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ApplyECH(c *Config, config *tls.Config) error {
|
||||||
|
var ECHConfig []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
nameToQuery := c.ServerName
|
||||||
|
var DNSServer string
|
||||||
|
|
||||||
|
// for client
|
||||||
|
if len(c.EchConfigList) != 0 {
|
||||||
|
// direct base64 config
|
||||||
|
if strings.Contains(c.EchConfigList, "://") {
|
||||||
|
// query config from dns
|
||||||
|
parts := strings.Split(c.EchConfigList, "+")
|
||||||
|
if len(parts) == 2 {
|
||||||
|
// parse ECH DNS server in format of "example.com+https://1.1.1.1/dns-query"
|
||||||
|
nameToQuery = parts[0]
|
||||||
|
DNSServer = parts[1]
|
||||||
|
} else if len(parts) == 1 {
|
||||||
|
// normal format
|
||||||
|
DNSServer = parts[0]
|
||||||
|
} else {
|
||||||
|
return errors.New("Invalid ECH DNS server format: ", c.EchConfigList)
|
||||||
|
}
|
||||||
|
if nameToQuery == "" {
|
||||||
|
return errors.New("Using DNS for ECH Config needs serverName or use Server format example.com+https://1.1.1.1/dns-query")
|
||||||
|
}
|
||||||
|
ECHConfig, err = QueryRecord(nameToQuery, DNSServer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ECHConfig, err = base64.StdEncoding.DecodeString(c.EchConfigList)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Failed to unmarshal ECHConfigList: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.EncryptedClientHelloConfigList = ECHConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// for server
|
||||||
|
if len(c.EchServerKeys) != 0 {
|
||||||
|
KeySets, err := ConvertToGoECHKeys(c.EchServerKeys)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Failed to unmarshal ECHKeySetList: ", err)
|
||||||
|
}
|
||||||
|
config.EncryptedClientHelloKeys = KeySets
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ECHConfigCache struct {
|
||||||
|
configRecord atomic.Pointer[echConfigRecord]
|
||||||
|
// updateLock is not for preventing concurrent read/write, but for preventing concurrent update
|
||||||
|
UpdateLock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
type echConfigRecord struct {
|
||||||
|
config []byte
|
||||||
|
expire time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
GlobalECHConfigCache = utils.NewTypedSyncMap[string, *ECHConfigCache]()
|
||||||
|
clientForECHDOH = utils.NewTypedSyncMap[string, *http.Client]()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update updates the ECH config for given domain and server.
|
||||||
|
// this method is concurrent safe, only one update request will be sent, others get the cache.
|
||||||
|
// if isLockedUpdate is true, it will not try to acquire the lock.
|
||||||
|
func (c *ECHConfigCache) Update(domain string, server string, isLockedUpdate bool) ([]byte, error) {
|
||||||
|
if !isLockedUpdate {
|
||||||
|
c.UpdateLock.Lock()
|
||||||
|
defer c.UpdateLock.Unlock()
|
||||||
|
}
|
||||||
|
// Double check cache after acquiring lock
|
||||||
|
configRecord := c.configRecord.Load()
|
||||||
|
if configRecord.expire.After(time.Now()) {
|
||||||
|
errors.LogDebug(context.Background(), "Cache hit for domain after double check: ", domain)
|
||||||
|
return configRecord.config, nil
|
||||||
|
}
|
||||||
|
// Query ECH config from DNS server
|
||||||
|
errors.LogDebug(context.Background(), "Trying to query ECH config for domain: ", domain, " with ECH server: ", server)
|
||||||
|
echConfig, ttl, err := dnsQuery(server, domain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
configRecord = &echConfigRecord{
|
||||||
|
config: echConfig,
|
||||||
|
expire: time.Now().Add(time.Duration(ttl) * time.Second),
|
||||||
|
}
|
||||||
|
c.configRecord.Store(configRecord)
|
||||||
|
return configRecord.config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryRecord returns the ECH config for given domain.
|
||||||
|
// If the record is not in cache or expired, it will query the DNS server and update the cache.
|
||||||
|
func QueryRecord(domain string, server string) ([]byte, error) {
|
||||||
|
echConfigCache, ok := GlobalECHConfigCache.Load(domain)
|
||||||
|
if !ok {
|
||||||
|
echConfigCache = &ECHConfigCache{}
|
||||||
|
echConfigCache.configRecord.Store(&echConfigRecord{})
|
||||||
|
echConfigCache, _ = GlobalECHConfigCache.LoadOrStore(domain, echConfigCache)
|
||||||
|
}
|
||||||
|
configRecord := echConfigCache.configRecord.Load()
|
||||||
|
if configRecord.expire.After(time.Now()) {
|
||||||
|
errors.LogDebug(context.Background(), "Cache hit for domain: ", domain)
|
||||||
|
return configRecord.config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If expire is zero value, it means we are in initial state, wait for the query to finish
|
||||||
|
// otherwise return old value immediately and update in a goroutine
|
||||||
|
// but if the cache is too old, wait for update
|
||||||
|
if configRecord.expire == (time.Time{}) || configRecord.expire.Add(time.Hour*6).Before(time.Now()) {
|
||||||
|
return echConfigCache.Update(domain, server, false)
|
||||||
|
} else {
|
||||||
|
// If someone already acquired the lock, it means it is updating, do not start another update goroutine
|
||||||
|
if echConfigCache.UpdateLock.TryLock() {
|
||||||
|
go func() {
|
||||||
|
defer echConfigCache.UpdateLock.Unlock()
|
||||||
|
echConfigCache.Update(domain, server, true)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return configRecord.config, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dnsQuery is the real func for sending type65 query for given domain to given DNS server.
|
||||||
|
// return ECH config, TTL and error
|
||||||
|
func dnsQuery(server string, domain string) ([]byte, uint32, error) {
|
||||||
|
m := new(dns.Msg)
|
||||||
|
var dnsResolve []byte
|
||||||
|
m.SetQuestion(dns.Fqdn(domain), dns.TypeHTTPS)
|
||||||
|
// for DOH server
|
||||||
|
if strings.HasPrefix(server, "https://") {
|
||||||
|
// always 0 in DOH
|
||||||
|
m.Id = 0
|
||||||
|
msg, err := m.Pack()
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
var client *http.Client
|
||||||
|
if client, _ = clientForECHDOH.Load(server); client == nil {
|
||||||
|
// All traffic sent by core should via xray's internet.DialSystem
|
||||||
|
// This involves the behavior of some Android VPN GUI clients
|
||||||
|
tr := &http.Transport{
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
dest, err := net.ParseDestination(network + ":" + addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn, err := internet.DialSystem(ctx, dest, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c := &http.Client{
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
Transport: tr,
|
||||||
|
}
|
||||||
|
client, _ = clientForECHDOH.LoadOrStore(server, c)
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("POST", server, bytes.NewReader(msg))
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/dns-message")
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return []byte{}, 0, errors.New("query failed with response code:", resp.StatusCode)
|
||||||
|
}
|
||||||
|
dnsResolve = respBody
|
||||||
|
} else if strings.HasPrefix(server, "udp://") { // for classic udp dns server
|
||||||
|
udpServerAddr := server[len("udp://"):]
|
||||||
|
// default port 53 if not specified
|
||||||
|
if !strings.Contains(udpServerAddr, ":") {
|
||||||
|
udpServerAddr = udpServerAddr + ":53"
|
||||||
|
}
|
||||||
|
dest, err := net.ParseDestination("udp" + ":" + udpServerAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, errors.New("failed to parse udp dns server ", udpServerAddr, " for ECH: ", err)
|
||||||
|
}
|
||||||
|
dnsTimeoutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
// use xray's internet.DialSystem as mentioned above
|
||||||
|
conn, err := internet.DialSystem(dnsTimeoutCtx, dest, nil)
|
||||||
|
defer func() {
|
||||||
|
err := conn.Close()
|
||||||
|
if err != nil {
|
||||||
|
errors.LogDebug(context.Background(), "Failed to close connection: ", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
msg, err := m.Pack()
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
conn.Write(msg)
|
||||||
|
udpResponse := make([]byte, 512)
|
||||||
|
_, err = conn.Read(udpResponse)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, err
|
||||||
|
}
|
||||||
|
dnsResolve = udpResponse
|
||||||
|
}
|
||||||
|
respMsg := new(dns.Msg)
|
||||||
|
err := respMsg.Unpack(dnsResolve)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, 0, errors.New("failed to unpack dns response for ECH: ", err)
|
||||||
|
}
|
||||||
|
if len(respMsg.Answer) > 0 {
|
||||||
|
for _, answer := range respMsg.Answer {
|
||||||
|
if https, ok := answer.(*dns.HTTPS); ok && https.Hdr.Name == dns.Fqdn(domain) {
|
||||||
|
for _, v := range https.Value {
|
||||||
|
if echConfig, ok := v.(*dns.SVCBECHConfig); ok {
|
||||||
|
errors.LogDebug(context.Background(), "Get ECH config:", echConfig.String(), " TTL:", respMsg.Answer[0].Header().Ttl)
|
||||||
|
return echConfig.ECH, answer.Header().Ttl, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []byte{}, 0, errors.New("no ech record found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// reference github.com/OmarTariq612/goech
|
||||||
|
func MarshalBinary(ech reality.EchConfig) ([]byte, error) {
|
||||||
|
var b cryptobyte.Builder
|
||||||
|
b.AddUint16(ech.Version)
|
||||||
|
b.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
|
||||||
|
child.AddUint8(ech.ConfigID)
|
||||||
|
child.AddUint16(ech.KemID)
|
||||||
|
child.AddUint16(uint16(len(ech.PublicKey)))
|
||||||
|
child.AddBytes(ech.PublicKey)
|
||||||
|
child.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
|
||||||
|
for _, cipherSuite := range ech.SymmetricCipherSuite {
|
||||||
|
child.AddUint16(cipherSuite.KDFID)
|
||||||
|
child.AddUint16(cipherSuite.AEADID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
child.AddUint8(ech.MaxNameLength)
|
||||||
|
child.AddUint8(uint8(len(ech.PublicName)))
|
||||||
|
child.AddBytes(ech.PublicName)
|
||||||
|
child.AddUint16LengthPrefixed(func(child *cryptobyte.Builder) {
|
||||||
|
for _, extention := range ech.Extensions {
|
||||||
|
child.AddUint16(extention.Type)
|
||||||
|
child.AddBytes(extention.Data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrInvalidLen = errors.New("goech: invalid length")
|
||||||
|
|
||||||
|
func ConvertToGoECHKeys(data []byte) ([]tls.EncryptedClientHelloKey, error) {
|
||||||
|
var keys []tls.EncryptedClientHelloKey
|
||||||
|
s := cryptobyte.String(data)
|
||||||
|
for !s.Empty() {
|
||||||
|
if len(s) < 2 {
|
||||||
|
return keys, ErrInvalidLen
|
||||||
|
}
|
||||||
|
keyLength := int(binary.BigEndian.Uint16(s[:2]))
|
||||||
|
if len(s) < keyLength+4 {
|
||||||
|
return keys, ErrInvalidLen
|
||||||
|
}
|
||||||
|
configLength := int(binary.BigEndian.Uint16(s[keyLength+2 : keyLength+4]))
|
||||||
|
if len(s) < 2+keyLength+2+configLength {
|
||||||
|
return keys, ErrInvalidLen
|
||||||
|
}
|
||||||
|
child := cryptobyte.String(s[:2+keyLength+2+configLength])
|
||||||
|
var (
|
||||||
|
sk, config cryptobyte.String
|
||||||
|
)
|
||||||
|
if !child.ReadUint16LengthPrefixed(&sk) || !child.ReadUint16LengthPrefixed(&config) || !child.Empty() {
|
||||||
|
return keys, ErrInvalidLen
|
||||||
|
}
|
||||||
|
if !s.Skip(2 + keyLength + 2 + configLength) {
|
||||||
|
return keys, ErrInvalidLen
|
||||||
|
}
|
||||||
|
keys = append(keys, tls.EncryptedClientHelloKey{
|
||||||
|
Config: config,
|
||||||
|
PrivateKey: sk,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExtensionEncryptedClientHello = 0xfe0d
|
||||||
|
const KDF_HKDF_SHA384 = 0x0002
|
||||||
|
const KDF_HKDF_SHA512 = 0x0003
|
||||||
|
|
||||||
|
func GenerateECHKeySet(configID uint8, domain string, kem uint16) (reality.EchConfig, []byte, error) {
|
||||||
|
config := reality.EchConfig{
|
||||||
|
Version: ExtensionEncryptedClientHello,
|
||||||
|
ConfigID: configID,
|
||||||
|
PublicName: []byte(domain),
|
||||||
|
KemID: kem,
|
||||||
|
SymmetricCipherSuite: []reality.EchCipher{
|
||||||
|
{KDFID: hpke.KDF_HKDF_SHA256, AEADID: hpke.AEAD_AES_128_GCM},
|
||||||
|
{KDFID: hpke.KDF_HKDF_SHA256, AEADID: hpke.AEAD_AES_256_GCM},
|
||||||
|
{KDFID: hpke.KDF_HKDF_SHA256, AEADID: hpke.AEAD_ChaCha20Poly1305},
|
||||||
|
{KDFID: KDF_HKDF_SHA384, AEADID: hpke.AEAD_AES_128_GCM},
|
||||||
|
{KDFID: KDF_HKDF_SHA384, AEADID: hpke.AEAD_AES_256_GCM},
|
||||||
|
{KDFID: KDF_HKDF_SHA384, AEADID: hpke.AEAD_ChaCha20Poly1305},
|
||||||
|
{KDFID: KDF_HKDF_SHA512, AEADID: hpke.AEAD_AES_128_GCM},
|
||||||
|
{KDFID: KDF_HKDF_SHA512, AEADID: hpke.AEAD_AES_256_GCM},
|
||||||
|
{KDFID: KDF_HKDF_SHA512, AEADID: hpke.AEAD_ChaCha20Poly1305},
|
||||||
|
},
|
||||||
|
MaxNameLength: 0,
|
||||||
|
Extensions: nil,
|
||||||
|
}
|
||||||
|
// if kem == hpke.DHKEM_X25519_HKDF_SHA256 {
|
||||||
|
curve := ecdh.X25519()
|
||||||
|
priv := make([]byte, 32) //x25519
|
||||||
|
_, err := io.ReadFull(rand.Reader, priv)
|
||||||
|
if err != nil {
|
||||||
|
return config, nil, err
|
||||||
|
}
|
||||||
|
privKey, _ := curve.NewPrivateKey(priv)
|
||||||
|
config.PublicKey = privKey.PublicKey().Bytes()
|
||||||
|
return config, priv, nil
|
||||||
|
// }
|
||||||
|
// TODO: add mlkem768 (former kyber768 draft00). The golang mlkem private key is 64 bytes seed?
|
||||||
|
}
|
43
transport/internet/tls/ech_test.go
Normal file
43
transport/internet/tls/ech_test.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package tls_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/xtls/xray-core/common"
|
||||||
|
. "github.com/xtls/xray-core/transport/internet/tls"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestECHDial(t *testing.T) {
|
||||||
|
config := &Config{
|
||||||
|
ServerName: "encryptedsni.com",
|
||||||
|
EchConfigList: "udp://1.1.1.1",
|
||||||
|
}
|
||||||
|
// test concurrent Dial(to test cache problem)
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
for range 10 {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
TLSConfig := config.GetTLSConfig()
|
||||||
|
TLSConfig.NextProtos = []string{"http/1.1"}
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: TLSConfig,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resp, err := client.Get("https://encryptedsni.com/cdn-cgi/trace")
|
||||||
|
common.Must(err)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
common.Must(err)
|
||||||
|
if !strings.Contains(string(body), "sni=encrypted") {
|
||||||
|
t.Error("ECH Dial success but SNI is not encrypted")
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
@@ -128,12 +128,13 @@ func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) ne
|
|||||||
|
|
||||||
func copyConfig(c *tls.Config) *utls.Config {
|
func copyConfig(c *tls.Config) *utls.Config {
|
||||||
return &utls.Config{
|
return &utls.Config{
|
||||||
Rand: c.Rand,
|
Rand: c.Rand,
|
||||||
RootCAs: c.RootCAs,
|
RootCAs: c.RootCAs,
|
||||||
ServerName: c.ServerName,
|
ServerName: c.ServerName,
|
||||||
InsecureSkipVerify: c.InsecureSkipVerify,
|
InsecureSkipVerify: c.InsecureSkipVerify,
|
||||||
VerifyPeerCertificate: c.VerifyPeerCertificate,
|
VerifyPeerCertificate: c.VerifyPeerCertificate,
|
||||||
KeyLogWriter: c.KeyLogWriter,
|
KeyLogWriter: c.KeyLogWriter,
|
||||||
|
EncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -44,6 +44,10 @@ func NewDispatcher(dispatcher routing.Dispatcher, callback ResponseCallback) *Di
|
|||||||
func (v *Dispatcher) RemoveRay() {
|
func (v *Dispatcher) RemoveRay() {
|
||||||
v.Lock()
|
v.Lock()
|
||||||
defer v.Unlock()
|
defer v.Unlock()
|
||||||
|
v.removeRay()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Dispatcher) removeRay() {
|
||||||
if v.conn != nil {
|
if v.conn != nil {
|
||||||
common.Interrupt(v.conn.link.Reader)
|
common.Interrupt(v.conn.link.Reader)
|
||||||
common.Close(v.conn.link.Writer)
|
common.Close(v.conn.link.Writer)
|
||||||
@@ -62,9 +66,15 @@ func (v *Dispatcher) getInboundRay(ctx context.Context, dest net.Destination) (*
|
|||||||
errors.LogInfo(ctx, "establishing new connection for ", dest)
|
errors.LogInfo(ctx, "establishing new connection for ", dest)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
entry := &connEntry{}
|
||||||
removeRay := func() {
|
removeRay := func() {
|
||||||
cancel()
|
v.Lock()
|
||||||
v.RemoveRay()
|
defer v.Unlock()
|
||||||
|
// sometimes the entry is already removed by others, don't close again
|
||||||
|
if entry == v.conn {
|
||||||
|
cancel()
|
||||||
|
v.removeRay()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
timer := signal.CancelAfterInactivity(ctx, removeRay, time.Minute)
|
timer := signal.CancelAfterInactivity(ctx, removeRay, time.Minute)
|
||||||
|
|
||||||
@@ -73,7 +83,7 @@ func (v *Dispatcher) getInboundRay(ctx context.Context, dest net.Destination) (*
|
|||||||
return nil, errors.New("failed to dispatch request to ", dest).Base(err)
|
return nil, errors.New("failed to dispatch request to ", dest).Base(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
entry := &connEntry{
|
*entry = connEntry{
|
||||||
link: link,
|
link: link,
|
||||||
timer: timer,
|
timer: timer,
|
||||||
cancel: removeRay,
|
cancel: removeRay,
|
||||||
|
@@ -40,6 +40,14 @@ func ListenUDP(ctx context.Context, address net.Address, port net.Port, streamSe
|
|||||||
opt(hub)
|
opt(hub)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if address.Family().IsDomain() && address.Domain() == "localhost" {
|
||||||
|
address = net.LocalHostIP
|
||||||
|
}
|
||||||
|
|
||||||
|
if address.Family().IsDomain() {
|
||||||
|
return nil, errors.New("domain address is not allowed for listening: ", address.Domain())
|
||||||
|
}
|
||||||
|
|
||||||
var sockopt *internet.SocketConfig
|
var sockopt *internet.SocketConfig
|
||||||
if streamSettings != nil {
|
if streamSettings != nil {
|
||||||
sockopt = streamSettings.SocketSettings
|
sockopt = streamSettings.SocketSettings
|
||||||
|
Reference in New Issue
Block a user