Refactor OpenVPN and OpenConnect endpoints
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
openconnecttransport "github.com/sagernet/sing-box/transport/openconnect"
|
||||
openvpntransport "github.com/sagernet/sing-box/transport/openvpn"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/gtcpip/header"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type endpointUDPNATDevice struct {
|
||||
start func() error
|
||||
writeInboundBuffers func([]*buf.Buffer) error
|
||||
setPacketWriter func(func([]*buf.Buffer) error)
|
||||
close func() error
|
||||
}
|
||||
|
||||
type endpointUDPNATPacket struct {
|
||||
session *endpointUDPNATSession
|
||||
destination M.Socksaddr
|
||||
payload []byte
|
||||
}
|
||||
|
||||
type endpointUDPNATSession struct {
|
||||
id uint64
|
||||
conn N.PacketConn
|
||||
closed chan struct{}
|
||||
}
|
||||
|
||||
type endpointUDPNATHandler struct {
|
||||
nextSessionID atomic.Uint64
|
||||
packets chan endpointUDPNATPacket
|
||||
}
|
||||
|
||||
func (h *endpointUDPNATHandler) JudgeFlow(uint8, netip.AddrPort, netip.AddrPort, []byte) tun.FlowVerdict {
|
||||
return tun.FlowVerdict{Action: tun.ActionAccept}
|
||||
}
|
||||
|
||||
func (h *endpointUDPNATHandler) NewConnectionEx(_ context.Context, conn net.Conn, _ M.Socksaddr, _ M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||
err := conn.Close()
|
||||
if onClose != nil {
|
||||
onClose(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *endpointUDPNATHandler) NewPacketConnectionEx(_ context.Context, conn N.PacketConn, _ M.Socksaddr, _ M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||
session := &endpointUDPNATSession{
|
||||
id: h.nextSessionID.Add(1),
|
||||
conn: conn,
|
||||
closed: make(chan struct{}),
|
||||
}
|
||||
go func() {
|
||||
defer close(session.closed)
|
||||
for {
|
||||
packetBuffer := buf.NewPacket()
|
||||
destination, err := conn.ReadPacket(packetBuffer)
|
||||
if err != nil {
|
||||
packetBuffer.Release()
|
||||
if onClose != nil {
|
||||
onClose(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
payload := append([]byte(nil), packetBuffer.Bytes()...)
|
||||
packetBuffer.Release()
|
||||
h.packets <- endpointUDPNATPacket{
|
||||
session: session,
|
||||
destination: destination,
|
||||
payload: payload,
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func TestOpenVPNEndpointUDPNATDataPlane(t *testing.T) {
|
||||
testEndpointUDPNATDataPlane(t, func(ctx context.Context, handler tun.Handler) (endpointUDPNATDevice, error) {
|
||||
device, err := openvpntransport.NewDevice(openvpntransport.DeviceOptions{
|
||||
Context: ctx,
|
||||
Logger: logger.NOP(),
|
||||
Handler: handler,
|
||||
UDPTimeout: time.Minute,
|
||||
UDPMapping: tun.NATMappingAddressAndPortDependent,
|
||||
UDPFiltering: tun.NATFilteringAddressAndPortDependent,
|
||||
UDPNATMax: 1,
|
||||
MTU: 1500,
|
||||
Configuration: openvpntransport.Configuration{
|
||||
MTU: 1500,
|
||||
Address: []netip.Prefix{netip.MustParsePrefix("10.8.0.1/24")},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return endpointUDPNATDevice{}, err
|
||||
}
|
||||
return endpointUDPNATDevice{
|
||||
start: device.Start,
|
||||
writeInboundBuffers: device.WriteInboundBuffers,
|
||||
setPacketWriter: func(writer func([]*buf.Buffer) error) {
|
||||
device.SetPacketWriter(openvpntransport.PacketWriter(writer))
|
||||
},
|
||||
close: device.Close,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestOpenConnectEndpointUDPNATDataPlane(t *testing.T) {
|
||||
testEndpointUDPNATDataPlane(t, func(ctx context.Context, handler tun.Handler) (endpointUDPNATDevice, error) {
|
||||
device, err := openconnecttransport.NewDevice(openconnecttransport.DeviceOptions{
|
||||
Context: ctx,
|
||||
Logger: logger.NOP(),
|
||||
Handler: handler,
|
||||
UDPTimeout: time.Minute,
|
||||
UDPMapping: tun.NATMappingAddressAndPortDependent,
|
||||
UDPFiltering: tun.NATFilteringAddressAndPortDependent,
|
||||
UDPNATMax: 1,
|
||||
MTU: 1500,
|
||||
Configuration: openconnecttransport.Configuration{
|
||||
MTU: 1500,
|
||||
Addresses: []netip.Prefix{netip.MustParsePrefix("10.8.0.1/24")},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return endpointUDPNATDevice{}, err
|
||||
}
|
||||
return endpointUDPNATDevice{
|
||||
start: device.Start,
|
||||
writeInboundBuffers: device.WriteInboundBuffers,
|
||||
setPacketWriter: func(writer func([]*buf.Buffer) error) {
|
||||
device.SetPacketWriter(openconnecttransport.PacketWriter(writer))
|
||||
},
|
||||
close: device.Close,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func testEndpointUDPNATDataPlane(t *testing.T, newDevice func(context.Context, tun.Handler) (endpointUDPNATDevice, error)) {
|
||||
t.Helper()
|
||||
if !tun.WithGVisor {
|
||||
t.Skip("requires gVisor")
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
handler := &endpointUDPNATHandler{packets: make(chan endpointUDPNATPacket, 4)}
|
||||
device, err := newDevice(ctx, handler)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, device.close())
|
||||
})
|
||||
outboundPackets := make(chan []byte, 4)
|
||||
device.setPacketWriter(func(packetBuffers []*buf.Buffer) error {
|
||||
for _, packetBuffer := range packetBuffers {
|
||||
payload, isUDP := endpointUDPPayload(packetBuffer.Bytes())
|
||||
packetBuffer.Release()
|
||||
if isUDP {
|
||||
outboundPackets <- payload
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
source := netip.MustParseAddrPort("10.8.0.2:40000")
|
||||
firstDestination := netip.MustParseAddrPort("192.0.2.1:5001")
|
||||
secondDestination := netip.MustParseAddrPort("192.0.2.1:5002")
|
||||
writeEndpointUDPPacket(t, device, source, firstDestination, []byte("before-start"))
|
||||
require.NoError(t, device.start())
|
||||
writeEndpointUDPPacket(t, device, source, firstDestination, []byte("request-one"))
|
||||
firstPacket := waitEndpointUDPNATPacket(t, handler.packets)
|
||||
require.Equal(t, M.SocksaddrFromNetIP(firstDestination), firstPacket.destination)
|
||||
require.Equal(t, []byte("request-one"), firstPacket.payload)
|
||||
|
||||
require.NoError(t, firstPacket.session.conn.WritePacket(buf.As([]byte("blocked")), M.SocksaddrFromNetIP(secondDestination)))
|
||||
require.NoError(t, firstPacket.session.conn.WritePacket(buf.As([]byte("allowed-one")), M.SocksaddrFromNetIP(firstDestination)))
|
||||
require.Equal(t, []byte("allowed-one"), waitEndpointUDPResponse(t, outboundPackets))
|
||||
|
||||
writeEndpointUDPPacket(t, device, source, secondDestination, []byte("request-two"))
|
||||
secondPacket := waitEndpointUDPNATPacket(t, handler.packets)
|
||||
require.Equal(t, M.SocksaddrFromNetIP(secondDestination), secondPacket.destination)
|
||||
require.Equal(t, []byte("request-two"), secondPacket.payload)
|
||||
require.NotEqual(t, firstPacket.session.id, secondPacket.session.id)
|
||||
select {
|
||||
case <-firstPacket.session.closed:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("first UDP NAT session was not evicted at max size")
|
||||
}
|
||||
|
||||
require.NoError(t, secondPacket.session.conn.WritePacket(buf.As([]byte("allowed-two")), M.SocksaddrFromNetIP(secondDestination)))
|
||||
require.Equal(t, []byte("allowed-two"), waitEndpointUDPResponse(t, outboundPackets))
|
||||
}
|
||||
|
||||
func writeEndpointUDPPacket(t *testing.T, device endpointUDPNATDevice, source netip.AddrPort, destination netip.AddrPort, payload []byte) {
|
||||
t.Helper()
|
||||
packet := make([]byte, header.IPv4MinimumSize+header.UDPMinimumSize+len(payload))
|
||||
ipHeader := header.IPv4(packet)
|
||||
ipHeader.Encode(&header.IPv4Fields{
|
||||
TotalLength: uint16(len(packet)),
|
||||
TTL: 64,
|
||||
Protocol: uint8(header.UDPProtocolNumber),
|
||||
SrcAddr: source.Addr(),
|
||||
DstAddr: destination.Addr(),
|
||||
})
|
||||
ipHeader.SetChecksum(^ipHeader.CalculateChecksum())
|
||||
udpHeader := header.UDP(packet[header.IPv4MinimumSize:])
|
||||
udpHeader.Encode(&header.UDPFields{
|
||||
SrcPort: source.Port(),
|
||||
DstPort: destination.Port(),
|
||||
Length: uint16(header.UDPMinimumSize + len(payload)),
|
||||
})
|
||||
copy(udpHeader.Payload(), payload)
|
||||
packetBuffer := buf.As(packet)
|
||||
require.NoError(t, device.writeInboundBuffers([]*buf.Buffer{packetBuffer}))
|
||||
packetBuffer.Release()
|
||||
}
|
||||
|
||||
func endpointUDPPayload(packet []byte) ([]byte, bool) {
|
||||
if len(packet) < header.IPv4MinimumSize {
|
||||
return nil, false
|
||||
}
|
||||
ipHeader := header.IPv4(packet)
|
||||
if !ipHeader.IsValid(len(packet)) || ipHeader.Protocol() != uint8(header.UDPProtocolNumber) {
|
||||
return nil, false
|
||||
}
|
||||
udpPayload := ipHeader.Payload()
|
||||
if len(udpPayload) < header.UDPMinimumSize {
|
||||
return nil, false
|
||||
}
|
||||
udpHeader := header.UDP(udpPayload)
|
||||
return append([]byte(nil), udpHeader.Payload()...), true
|
||||
}
|
||||
|
||||
func waitEndpointUDPNATPacket(t *testing.T, packets <-chan endpointUDPNATPacket) endpointUDPNATPacket {
|
||||
t.Helper()
|
||||
select {
|
||||
case packet := <-packets:
|
||||
return packet
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("timed out waiting for UDP NAT packet")
|
||||
return endpointUDPNATPacket{}
|
||||
}
|
||||
}
|
||||
|
||||
func waitEndpointUDPResponse(t *testing.T, packets <-chan []byte) []byte {
|
||||
t.Helper()
|
||||
select {
|
||||
case packet := <-packets:
|
||||
return packet
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("timed out waiting for UDP response")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -12,10 +12,11 @@ require (
|
||||
github.com/gofrs/uuid/v5 v5.4.0
|
||||
github.com/opencontainers/image-spec v1.1.0
|
||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4
|
||||
github.com/sagernet/sing v0.8.12-0.20260717023913-84ab32b56cb8
|
||||
github.com/sagernet/sing v0.8.12-0.20260717153536-4f1ed45a99a5
|
||||
github.com/sagernet/sing-quic v0.6.4-0.20260709034545-e23afe1172dc
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||
github.com/sagernet/sing-tun v0.8.12-0.20260717024008-39eed1f6361d
|
||||
github.com/spyzhov/ajson v0.9.4
|
||||
github.com/stretchr/testify v1.11.1
|
||||
go.uber.org/goleak v1.3.0
|
||||
@@ -152,15 +153,14 @@ require (
|
||||
github.com/sagernet/nftables v0.3.0-mod.4 // indirect
|
||||
github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3 // indirect
|
||||
github.com/sagernet/sing-mux v0.3.5 // indirect
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260717061548-458a8732933e // indirect
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260717055507-7e569eca5e4d // indirect
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260717081856-cf2c71a71aba // indirect
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260718013246-3cd8a7b83247 // indirect
|
||||
github.com/sagernet/sing-shadowtls v0.2.1 // indirect
|
||||
github.com/sagernet/sing-snell v0.0.0-20260710094516-a4e97ee24beb // indirect
|
||||
github.com/sagernet/sing-tun v0.8.12-0.20260717024008-39eed1f6361d // indirect
|
||||
github.com/sagernet/sing-usbip v0.0.0-20260616101517-efb91521eddb // indirect
|
||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 // indirect
|
||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1 // indirect
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260717024045-1edfbb9ee544 // indirect
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260717155615-b353b93d194a // indirect
|
||||
github.com/sagernet/wireguard-go v0.0.5-0.20260717024847-6f5e8b1947ae // indirect
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect
|
||||
github.com/smallstep/pkcs7 v0.1.1 // indirect
|
||||
|
||||
+8
-8
@@ -298,16 +298,16 @@ github.com/sagernet/nftables v0.3.0-mod.4 h1:vnOtcDYeSXv2e5RoRuGH0lrpttQFJ8iC4IC
|
||||
github.com/sagernet/nftables v0.3.0-mod.4/go.mod h1:8kslHG4VvYNihcco+i6uxIX7qbT8A56T0y5q7U44ZaQ=
|
||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o=
|
||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
|
||||
github.com/sagernet/sing v0.8.12-0.20260717023913-84ab32b56cb8 h1:dyRIj+MZ2rc9JVzJoG04jxu+MpvHrLIZLJr0QjNAMGg=
|
||||
github.com/sagernet/sing v0.8.12-0.20260717023913-84ab32b56cb8/go.mod h1:olXxWQNqRW/l2Q6JI3b2Qmz8iQnIFlOeeH8bx6JhgUA=
|
||||
github.com/sagernet/sing v0.8.12-0.20260717153536-4f1ed45a99a5 h1:WyL7xI7h+mNF6we/arqVNDEB+0iK2SwJRjim8rGydDM=
|
||||
github.com/sagernet/sing v0.8.12-0.20260717153536-4f1ed45a99a5/go.mod h1:olXxWQNqRW/l2Q6JI3b2Qmz8iQnIFlOeeH8bx6JhgUA=
|
||||
github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3 h1:3y6++yIa8XlDhxPkpR4p+7RUHVY2KTP9CPIGnWmOlO8=
|
||||
github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3/go.mod h1:XEqEDYRCAYLaoPjZ1ifVWJg5iWAJHL2gOAXe/PM28Cg=
|
||||
github.com/sagernet/sing-mux v0.3.5 h1:RHnhVEc+SFqkrK4xMygYjDwwLhzp2Bj3lztSukONfhI=
|
||||
github.com/sagernet/sing-mux v0.3.5/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260717061548-458a8732933e h1:Kgcf16uKnxBNJMsR8MaWlORtOV2/qi+6yH830+b7Yfc=
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260717061548-458a8732933e/go.mod h1:EIzh5HtImfQJxPKXFwS9lyMnmMy4aCQCx7ntQ4u41Gs=
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260717055507-7e569eca5e4d h1:KGvybsWqE+Qkd9Ns2AzrrBSyNfbBJ7IZZqgj+oWa6SM=
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260717055507-7e569eca5e4d/go.mod h1:CmTGnS5ijVSqFQV1dTq4WvFLUoz7bk9xasBPsX8NcYo=
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260717081856-cf2c71a71aba h1:S87Ej/jFssn0qhPF1ExF0YIV0USfGZF2If6kSjGLPt8=
|
||||
github.com/sagernet/sing-openconnect v0.0.0-20260717081856-cf2c71a71aba/go.mod h1:EIzh5HtImfQJxPKXFwS9lyMnmMy4aCQCx7ntQ4u41Gs=
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260718013246-3cd8a7b83247 h1:IfZqHohaWz13eqc6SAUHkmP9xhMpMBDIShl7OtgRd5Y=
|
||||
github.com/sagernet/sing-openvpn v0.0.0-20260718013246-3cd8a7b83247/go.mod h1:CmTGnS5ijVSqFQV1dTq4WvFLUoz7bk9xasBPsX8NcYo=
|
||||
github.com/sagernet/sing-quic v0.6.4-0.20260709034545-e23afe1172dc h1:zdc0fj4JdAdgAmQIoh7ZF+B/wPTEF2X75lYDqTmvlaw=
|
||||
github.com/sagernet/sing-quic v0.6.4-0.20260709034545-e23afe1172dc/go.mod h1:9k+dzGsWMttUGldBzq3dU792YHXzW6NgfbOGltnXq+0=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
||||
@@ -326,8 +326,8 @@ github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkV
|
||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
|
||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=
|
||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8=
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260717024045-1edfbb9ee544 h1:j2tab0dGHutfclhwZxrkSDMXwGXtozIo5BV4DgwS+1Q=
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260717024045-1edfbb9ee544/go.mod h1:p8Ms8FbGlwQJyHb862XmdShTS50fFJ8C71VdO6xvWyk=
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260717155615-b353b93d194a h1:mORXldIuzgU8Bk6n9KpOmLv1rV7Ta8us+lZ9JSv2eBw=
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260717155615-b353b93d194a/go.mod h1:p8Ms8FbGlwQJyHb862XmdShTS50fFJ8C71VdO6xvWyk=
|
||||
github.com/sagernet/wireguard-go v0.0.5-0.20260717024847-6f5e8b1947ae h1:GmxlXWnRmeNfPE1tWXRZIFgKJd5BH5okoDHKZkkI5bw=
|
||||
github.com/sagernet/wireguard-go v0.0.5-0.20260717024847-6f5e8b1947ae/go.mod h1:hEqi4y5czEg6LYtX2Bpjg+lV0b/J1n+5rA885Z66Mx0=
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json/badoption"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
@@ -125,6 +126,8 @@ func TestOpenConnectDockerInterop(t *testing.T) {
|
||||
require.Nil(subtest, status.AuthForm)
|
||||
err := exchangeOpenConnectTCPEcho(endpoint, 256*1024, 30*time.Second)
|
||||
require.NoError(subtest, err)
|
||||
err = exchangeOpenConnectUDPEcho(endpoint, 1400, 30*time.Second)
|
||||
require.NoError(subtest, err)
|
||||
})
|
||||
|
||||
t.Run("interactive_password_auth", func(subtest *testing.T) {
|
||||
@@ -174,11 +177,15 @@ func TestOpenConnectDockerInterop(t *testing.T) {
|
||||
|
||||
func openConnectInstanceOptions(server string, certificateAuthorityPath string, username string, password string) option.Options {
|
||||
endpointOptions := option.OpenConnectEndpointOptions{
|
||||
Server: server,
|
||||
Flavor: "anyconnect",
|
||||
Username: username,
|
||||
Password: password,
|
||||
NoUDP: true,
|
||||
Server: server,
|
||||
Flavor: "anyconnect",
|
||||
Username: username,
|
||||
Password: password,
|
||||
NoUDP: true,
|
||||
UDPTimeout: badoption.Duration(time.Minute),
|
||||
UDPMapping: option.UDPNATBehaviorAddressDependent,
|
||||
UDPFiltering: option.UDPNATBehaviorAddressAndPortDependent,
|
||||
UDPNATMax: 128,
|
||||
TLS: option.OpenConnectTLSOptions{
|
||||
CertificateAuthorityPath: certificateAuthorityPath,
|
||||
},
|
||||
@@ -318,6 +325,38 @@ func exchangeOpenConnectTCPEcho(endpoint adapter.OpenConnectEndpoint, payloadSiz
|
||||
return nil
|
||||
}
|
||||
|
||||
func exchangeOpenConnectUDPEcho(endpoint adapter.OpenConnectEndpoint, payloadSize int, timeout time.Duration) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
conn, err := endpoint.DialContext(ctx, N.NetworkUDP, M.ParseSocksaddrHostPort(openConnectTunnelAddress, openConnectEchoPort))
|
||||
if err != nil {
|
||||
return E.Cause(err, "dial ocserv tunnel UDP echo")
|
||||
}
|
||||
defer conn.Close()
|
||||
err = conn.SetDeadline(time.Now().Add(timeout))
|
||||
if err != nil {
|
||||
return E.Cause(err, "set ocserv tunnel UDP echo deadline")
|
||||
}
|
||||
payload := make([]byte, payloadSize)
|
||||
_, err = rand.Read(payload)
|
||||
if err != nil {
|
||||
return E.Cause(err, "generate ocserv tunnel UDP echo payload")
|
||||
}
|
||||
_, err = conn.Write(payload)
|
||||
if err != nil {
|
||||
return E.Cause(err, "write ocserv tunnel UDP echo payload")
|
||||
}
|
||||
response := make([]byte, payloadSize+1)
|
||||
responseLength, err := conn.Read(response)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read ocserv tunnel UDP echo payload")
|
||||
}
|
||||
if !bytes.Equal(response[:responseLength], payload) {
|
||||
return E.New("ocserv tunnel UDP echo payload mismatch")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForOpenConnectTCPEcho(t *testing.T, endpoint adapter.OpenConnectEndpoint, timeout time.Duration) {
|
||||
t.Helper()
|
||||
deadline := time.Now().Add(timeout)
|
||||
|
||||
+13
-3
@@ -150,10 +150,16 @@ func runOpenVPNSelfToSelf(t *testing.T, testCase openVPNSelfCase) {
|
||||
ClientCertificatePath: certificates.caPath,
|
||||
},
|
||||
}
|
||||
serverOptions.UDPMapping = option.UDPNATBehaviorAddressDependent
|
||||
serverOptions.UDPFiltering = option.UDPNATBehaviorAddressAndPortDependent
|
||||
serverOptions.UDPNATMax = 128
|
||||
clientOptions := newOpenVPNTLSClientOptions(testCase.protocol, openVPNPort, certificates.caPath, certificates.clientCertPath, certificates.clientKeyPath)
|
||||
clientOptions.UDPMapping = option.UDPNATBehaviorAddressDependent
|
||||
clientOptions.UDPFiltering = option.UDPNATBehaviorAddressAndPortDependent
|
||||
clientOptions.UDPNATMax = 128
|
||||
if testCase.tlsCrypt {
|
||||
tlsCryptKeyPath := writeOpenVPNStaticKeyFile(t, createOpenVPNStaticKey(t))
|
||||
serverOptions.TLS.ControlWrap = &option.OpenVPNControlWrapOptions{
|
||||
serverOptions.TLS.ControlWrap = &option.OpenVPNInboundControlWrapOptions{
|
||||
Type: "tls_crypt",
|
||||
KeyPath: tlsCryptKeyPath,
|
||||
}
|
||||
@@ -538,8 +544,12 @@ func TestOpenVPNClientReconnectSelfToSelf(t *testing.T) {
|
||||
KeyPath: certificates.serverKeyPath,
|
||||
ClientCertificatePath: certificates.caPath,
|
||||
},
|
||||
KeepaliveInterval: badoption.Duration(time.Second),
|
||||
KeepaliveTimeout: badoption.Duration(2 * time.Second),
|
||||
PingInterval: badoption.Duration(time.Second),
|
||||
PingRestart: badoption.Duration(4 * time.Second),
|
||||
Push: &option.OpenVPNPushOptions{
|
||||
PingInterval: badoption.Duration(time.Second),
|
||||
PingRestart: badoption.Duration(2 * time.Second),
|
||||
},
|
||||
Users: []auth.User{
|
||||
{
|
||||
Username: openVPNTLSUsername,
|
||||
|
||||
+15
-5
@@ -12,11 +12,21 @@ def echo(connection):
|
||||
connection.sendall(data)
|
||||
|
||||
|
||||
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
listener.bind(("0.0.0.0", 18080))
|
||||
listener.listen()
|
||||
def echo_udp(connection):
|
||||
while True:
|
||||
data, address = connection.recvfrom(65536)
|
||||
connection.sendto(data, address)
|
||||
|
||||
|
||||
udp_listener = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
udp_listener.bind(("0.0.0.0", 18080))
|
||||
threading.Thread(target=echo_udp, args=(udp_listener,), daemon=True).start()
|
||||
|
||||
tcp_listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
tcp_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
tcp_listener.bind(("0.0.0.0", 18080))
|
||||
tcp_listener.listen()
|
||||
print("openconnect echo ready", flush=True)
|
||||
while True:
|
||||
accepted, _ = listener.accept()
|
||||
accepted, _ = tcp_listener.accept()
|
||||
threading.Thread(target=echo, args=(accepted,), daemon=True).start()
|
||||
|
||||
Reference in New Issue
Block a user