Improve OpenVPN & OpenConnect interoperability

This commit is contained in:
世界
2026-07-21 09:38:21 +08:00
parent 5cad5ad42d
commit 2ff294c4f4
47 changed files with 3915 additions and 440 deletions
+211 -32
View File
@@ -5,6 +5,7 @@ import (
"net"
"net/netip"
"slices"
"strconv"
"sync/atomic"
"time"
@@ -90,18 +91,16 @@ func NewServerEndpoint(ctx context.Context, router adapter.Router, logger log.Co
return nil, err
}
serverOptions.Context = loopContext
serverOptions.Authentication.Authenticator = authenticatorFromUsers(options.Users)
serverOptions.Authentication.DuplicateCN = options.DuplicateCN
if serverOptions.Mode == ovpn.ModeTLS {
serverOptions.Authentication.Authenticator = authenticatorFromUsers(options.Users)
serverOptions.Authentication.DuplicateCN = options.DuplicateCN
}
serverOptions.Logger = logger
serverEndpoint.serverOptions = serverOptions
udpTimeout := C.UDPTimeout
if options.UDPTimeout != 0 {
udpTimeout = time.Duration(options.UDPTimeout)
}
deviceRoutes := make([]ovpntransport.Route, 0, len(options.Address))
for _, prefix := range options.Address {
deviceRoutes = append(deviceRoutes, ovpntransport.Route{Prefix: prefix.Masked()})
}
device, err := ovpntransport.NewDevice(ovpntransport.DeviceOptions{
Context: ctx,
Logger: logger,
@@ -118,7 +117,6 @@ func NewServerEndpoint(ctx context.Context, router adapter.Router, logger log.Co
Configuration: ovpntransport.Configuration{
MTU: options.MTU,
Address: options.Address,
Routes: deviceRoutes,
Topology: options.Topology,
},
})
@@ -134,15 +132,18 @@ func NewServerEndpoint(ctx context.Context, router adapter.Router, logger log.Co
func validateServerAddresses(addresses []netip.Prefix) error {
var hasIPv4 bool
var hasIPv6 bool
for _, prefix := range addresses {
for addressIndex, prefix := range addresses {
if !prefix.IsValid() {
return E.New("server address[", addressIndex, "] is invalid")
}
if prefix.Addr().Is4() {
if hasIPv4 {
return E.New("multiple IPv4 OpenVPN server address pools are not supported")
return E.New("multiple IPv4 server address pools are not supported")
}
hasIPv4 = true
} else {
if hasIPv6 {
return E.New("multiple IPv6 OpenVPN server address pools are not supported")
return E.New("multiple IPv6 server address pools are not supported")
}
hasIPv6 = true
}
@@ -155,7 +156,7 @@ func validateServerTopology(topology string) error {
case "", "subnet", "p2p", "net30":
return nil
default:
return E.New("invalid OpenVPN topology ", topology, ", allowed values: subnet, p2p, net30")
return E.New("invalid topology ", topology, ", allowed values: subnet, p2p, net30")
}
}
@@ -258,11 +259,17 @@ func (s *ServerEndpoint) Start(stage adapter.StartStage) error {
}
func buildServerOptions(options option.OpenVPNServerEndpointOptions) (ovpn.ServerOptions, error) {
if len(options.Address) == 0 {
return ovpn.ServerOptions{}, E.New("missing OpenVPN server address")
mode := options.Mode
if mode == "" {
mode = ovpn.ModeTLS
}
if options.TLS == nil {
return ovpn.ServerOptions{}, E.New("missing `tls` options")
switch mode {
case ovpn.ModeTLS, ovpn.ModeStaticKey:
default:
return ovpn.ServerOptions{}, E.New("unsupported mode: ", mode, " (expected \"tls\" or \"static_key\")")
}
if len(options.Address) == 0 {
return ovpn.ServerOptions{}, E.New("missing server address")
}
err := validateServerAddresses(options.Address)
if err != nil {
@@ -279,7 +286,16 @@ func buildServerOptions(options option.OpenVPNServerEndpointOptions) (ovpn.Serve
switch protocol {
case N.NetworkTCP, N.NetworkUDP:
default:
return ovpn.ServerOptions{}, E.New("unsupported OpenVPN network: ", protocol)
return ovpn.ServerOptions{}, E.New("unsupported network: ", protocol)
}
if mode == ovpn.ModeStaticKey {
return buildStaticKeyServerOptions(options, protocol)
}
if options.TLS == nil {
return ovpn.ServerOptions{}, E.New("missing `tls` options")
}
if len(options.StaticKey) > 0 || options.StaticKeyPath != "" || options.KeyDirection != "" || options.Cipher != "" || options.Remote != "" || options.RemotePort != 0 || netip.Addr(options.PeerAddress).IsValid() || netip.Addr(options.PeerAddressIPv6).IsValid() {
return ovpn.ServerOptions{}, E.New("static-key server options require `mode: static_key`")
}
tlsOptions, keyDirection, err := buildServerTLSOptions(*options.TLS)
if err != nil {
@@ -295,29 +311,136 @@ func buildServerOptions(options option.OpenVPNServerEndpointOptions) (ovpn.Serve
MaxClients: options.MaxClients,
},
DataChannel: ovpn.ServerDataChannelOptions{
MTU: options.MTU,
Ciphers: []string(options.DataCiphers),
FallbackCipher: options.DataCiphersFallback,
Auth: options.Auth,
PacketHeadroom: ovpntransport.PacketHeadroom,
MTU: options.MTU,
MSSFix: options.MSSFix,
MSSFixDisabled: options.MSSFixDisabled,
MSSFixMode: options.MSSFixMode,
Ciphers: []string(options.DataCiphers),
FallbackCipher: options.DataCiphersFallback,
Auth: options.Auth,
ReplayWindow: options.ReplayWindow,
ReplayWindowTime: time.Duration(options.ReplayWindowTime),
PacketHeadroom: ovpntransport.PacketHeadroom,
},
TLS: tlsOptions,
Timing: ovpn.ServerTimingOptions{
RenegotiationInterval: time.Duration(options.RenegotiateInterval),
RenegotiationDisabled: options.RenegotiateDisabled,
RenegotiationBytes: options.RenegotiateBytes,
RenegotiationPackets: options.RenegotiatePackets,
HandWindow: time.Duration(options.HandshakeWindow),
PingInterval: time.Duration(options.PingInterval),
PingRestart: time.Duration(options.PingRestart),
},
}
applyServerPushOptions(&serverOptions, options)
err = applyServerPushOptions(&serverOptions, options)
if err != nil {
return ovpn.ServerOptions{}, err
}
return serverOptions, nil
}
func buildStaticKeyServerOptions(options option.OpenVPNServerEndpointOptions, protocol string) (ovpn.ServerOptions, error) {
if options.TLS != nil {
return ovpn.ServerOptions{}, E.New("`tls` options are not supported in `static_key` mode")
}
if len(options.Users) > 0 || options.DuplicateCN {
return ovpn.ServerOptions{}, E.New("user authentication is not supported in `static_key` mode")
}
if options.Push != nil {
return ovpn.ServerOptions{}, E.New("push options are not supported in `static_key` mode")
}
if options.RenegotiateInterval != 0 || options.RenegotiateDisabled || options.RenegotiateBytes != 0 || options.RenegotiatePackets != 0 || options.HandshakeWindow != 0 {
return ovpn.ServerOptions{}, E.New("TLS timing and renegotiation options are not supported in `static_key` mode")
}
if len(options.DataCiphers) > 0 || options.DataCiphersFallback != "" {
return ovpn.ServerOptions{}, E.New("`data_ciphers` and `data_ciphers_fallback` are not supported in `static_key` mode; use `cipher`")
}
staticKey, err := requiredMaterialSource("static_key", options.StaticKey, options.StaticKeyPath)
if err != nil {
return ovpn.ServerOptions{}, err
}
keyDirection, err := keyDirectionValue(options.KeyDirection)
if err != nil {
return ovpn.ServerOptions{}, err
}
vpnGateway := netip.Addr(options.PeerAddress)
if vpnGateway.IsValid() && !vpnGateway.Is4() {
return ovpn.ServerOptions{}, E.New("`peer_address` must be an IPv4 address")
}
vpnGatewayIPv6 := netip.Addr(options.PeerAddressIPv6)
if vpnGatewayIPv6.IsValid() && !vpnGatewayIPv6.Is6() {
return ovpn.ServerOptions{}, E.New("`peer_address_ipv6` must be an IPv6 address")
}
var hasIPv4 bool
var hasIPv6 bool
for _, address := range options.Address {
hasIPv4 = hasIPv4 || address.Addr().Is4()
hasIPv6 = hasIPv6 || address.Addr().Is6()
}
if hasIPv4 && !vpnGateway.IsValid() {
return ovpn.ServerOptions{}, E.New("missing `peer_address` for the IPv4 static-key tunnel")
}
if hasIPv6 && !vpnGatewayIPv6.IsValid() {
return ovpn.ServerOptions{}, E.New("missing `peer_address_ipv6` for the IPv6 static-key tunnel")
}
if vpnGateway.IsValid() && !hasIPv4 {
return ovpn.ServerOptions{}, E.New("`peer_address` requires an IPv4 tunnel `address` in `static_key` mode")
}
if vpnGatewayIPv6.IsValid() && !hasIPv6 {
return ovpn.ServerOptions{}, E.New("`peer_address_ipv6` requires an IPv6 tunnel `address` in `static_key` mode")
}
remoteAddress := ""
if protocol == N.NetworkUDP {
if options.Remote == "" || options.RemotePort == 0 {
return ovpn.ServerOptions{}, E.New("`remote` and `remote_port` are required for a UDP static-key server")
}
remoteAddress = net.JoinHostPort(options.Remote, strconv.Itoa(int(options.RemotePort)))
} else if options.Remote != "" || options.RemotePort != 0 {
return ovpn.ServerOptions{}, E.New("`remote` and `remote_port` are only used by a UDP static-key server")
}
topology := options.Topology
if topology == "" {
topology = "p2p"
}
return ovpn.ServerOptions{
Mode: ovpn.ModeStaticKey,
StaticKey: staticKey,
KeyDirection: keyDirection,
Transport: ovpn.ServerTransportOptions{
Protocol: protocol,
RemoteAddress: remoteAddress,
},
Resources: ovpn.ServerResourceOptions{MaxClients: options.MaxClients},
DataChannel: ovpn.ServerDataChannelOptions{
MTU: options.MTU,
MSSFix: options.MSSFix,
MSSFixDisabled: options.MSSFixDisabled,
MSSFixMode: options.MSSFixMode,
Cipher: options.Cipher,
Auth: options.Auth,
ReplayWindow: options.ReplayWindow,
ReplayWindowTime: time.Duration(options.ReplayWindowTime),
PacketHeadroom: ovpntransport.PacketHeadroom,
},
Timing: ovpn.ServerTimingOptions{
PingInterval: time.Duration(options.PingInterval),
PingRestart: time.Duration(options.PingRestart),
},
Tunnel: ovpn.ServerTunnelOptions{
Topology: topology,
LocalAddress: slices.Clone(options.Address),
VPNGateway: vpnGateway,
VPNGatewayIPv6: vpnGatewayIPv6,
},
}, nil
}
func buildServerTLSOptions(options option.OpenVPNInboundTLSOptions) (ovpn.ServerTLSOptions, int, error) {
switch options.VerifyClientCertificate {
case "", "require", "optional", "none":
default:
return ovpn.ServerTLSOptions{}, 0, E.New("invalid OpenVPN client certificate policy ", options.VerifyClientCertificate, ", allowed values: require, optional, none")
return ovpn.ServerTLSOptions{}, 0, E.New("invalid client certificate policy ", options.VerifyClientCertificate, ", allowed values: require, optional, none")
}
certificate, err := requiredMaterialSource("tls.certificate", options.Certificate, options.CertificatePath)
if err != nil {
@@ -327,16 +450,46 @@ func buildServerTLSOptions(options option.OpenVPNInboundTLSOptions) (ovpn.Server
if err != nil {
return ovpn.ServerTLSOptions{}, 0, err
}
certificateAuthority, err := requiredMaterialSource("tls.client_certificate", options.ClientCertificate, options.ClientCertificatePath)
certificateAuthority, err := materialSource("tls.client_certificate", options.ClientCertificate, options.ClientCertificatePath)
if err != nil {
return ovpn.ServerTLSOptions{}, 0, err
}
remoteCertificateTLS := options.RemoteCertificateTLS
switch remoteCertificateTLS {
case "", "server", "client", "none":
default:
return ovpn.ServerTLSOptions{}, 0, E.New("invalid `tls.remote_certificate_tls`: ", remoteCertificateTLS)
}
if options.RemoteCertificateEKU != "" && remoteCertificateTLS != "" {
return ovpn.ServerTLSOptions{}, 0, E.New("`tls.remote_certificate_eku` is conflict with `tls.remote_certificate_tls`")
}
if remoteCertificateTLS == "" && options.RemoteCertificateEKU == "" {
remoteCertificateTLS = "client"
} else if remoteCertificateTLS == "none" {
remoteCertificateTLS = ""
}
clientNameType := options.ClientNameType
if options.ClientName != "" && clientNameType == "" {
clientNameType = "name"
}
tlsOptions := ovpn.ServerTLSOptions{
CertificateAuthority: certificateAuthority,
Certificate: certificate,
Key: key,
VerifyClientCertificate: options.VerifyClientCertificate,
VerifyX509Name: options.ClientName,
VerifyX509Type: clientNameType,
PeerFingerprint: options.PeerFingerprint,
CRLVerify: options.CRLPath,
RemoteCertificateKU: options.RemoteCertificateKU,
RemoteCertificateEKU: options.RemoteCertificateEKU,
RemoteCertificateTLS: remoteCertificateTLS,
NSCertificateType: options.NSCertificateType,
CertificateProfile: options.CertificateProfile,
VersionMin: options.VersionMin,
VersionMax: options.VersionMax,
Cipher: options.Cipher,
Groups: options.Groups,
}
keyDirection := -1
controlWrap := options.ControlWrap
@@ -369,15 +522,15 @@ func buildServerTLSOptions(options option.OpenVPNInboundTLSOptions) (ovpn.Server
tlsOptions.CryptV2ForceCookie = controlWrap.ForceCookie
}
case "":
return ovpn.ServerTLSOptions{}, 0, E.New("missing OpenVPN control wrap type")
return ovpn.ServerTLSOptions{}, 0, E.New("missing control wrap type")
default:
return ovpn.ServerTLSOptions{}, 0, E.New("unknown OpenVPN control wrap type: ", controlWrap.Type)
return ovpn.ServerTLSOptions{}, 0, E.New("unknown control wrap type: ", controlWrap.Type)
}
}
return tlsOptions, keyDirection, nil
}
func applyServerPushOptions(serverOptions *ovpn.ServerOptions, options option.OpenVPNServerEndpointOptions) {
func applyServerPushOptions(serverOptions *ovpn.ServerOptions, options option.OpenVPNServerEndpointOptions) error {
topology := options.Topology
if topology == "" {
topology = "subnet"
@@ -399,10 +552,35 @@ func applyServerPushOptions(serverOptions *ovpn.ServerOptions, options option.Op
LocalAddress: localAddresses,
}
if options.Push == nil {
return
return nil
}
serverOptions.Push.Routes = slices.Clone(options.Push.Routes)
serverOptions.Push.DNS = slices.Clone(options.Push.DNS)
serverOptions.Push.SearchDomains = slices.Clone(options.Push.SearchDomains)
serverOptions.Push.DHCPOptions = slices.Clone(options.Push.DHCPOptions)
for serverIndex, server := range options.Push.DNSServers {
addresses := make([]netip.AddrPort, 0, len(server.Addresses))
for addressIndex, addressValue := range server.Addresses {
address, err := netip.ParseAddr(addressValue)
if err == nil {
addresses = append(addresses, netip.AddrPortFrom(address, 0))
continue
}
addressPort, addressPortErr := netip.ParseAddrPort(addressValue)
if addressPortErr != nil || addressPort.Port() == 0 {
return E.New("invalid push.dns_servers[", serverIndex, "].addresses[", addressIndex, "]: ", addressValue)
}
addresses = append(addresses, addressPort)
}
serverOptions.Push.DNSServers = append(serverOptions.Push.DNSServers, ovpn.TunnelDNSServer{
Priority: server.Priority,
Addresses: addresses,
ResolveDomains: slices.Clone(server.ResolveDomains),
DNSSEC: server.DNSSEC,
Transport: server.Transport,
SNI: server.SNI,
})
}
serverOptions.Push.BlockOutsideDNS = options.Push.BlockOutsideDNS
serverOptions.Push.PingInterval = time.Duration(options.Push.PingInterval)
serverOptions.Push.PingRestart = time.Duration(options.Push.PingRestart)
@@ -414,6 +592,7 @@ func applyServerPushOptions(serverOptions *ovpn.ServerOptions, options option.Op
serverOptions.Push.RedirectGatewayFlags = []string{"def1"}
}
}
return nil
}
func (s *ServerEndpoint) readLoop() {
@@ -424,7 +603,7 @@ func (s *ServerEndpoint) readLoop() {
if E.IsClosedOrCanceled(err) || s.loopContext.Err() != nil {
return
}
s.logger.Error(E.Cause(err, "OpenVPN server terminated"))
s.logger.Error(E.Cause(err, "server terminated"))
return
}
packetBuffers := make([]*buf.Buffer, len(serverPacketBuffers))
@@ -491,7 +670,7 @@ func (s *ServerEndpoint) NewDNSPacket(payload []byte, source M.Socksaddr, destin
func (s *ServerEndpoint) WritePackets(packets [][]byte) error {
if !s.started.Load() {
return E.New("OpenVPN server is not ready yet")
return E.New("endpoint is not ready yet")
}
packetBuffers := make([]*buf.Buffer, len(packets))
for i, packet := range packets {
@@ -547,7 +726,7 @@ func (s *ServerEndpoint) DialContext(ctx context.Context, network string, destin
s.logger.InfoContext(ctx, "outbound packet connection to ", destination)
}
if !s.started.Load() {
return nil, E.New("OpenVPN server is not ready yet")
return nil, E.New("endpoint is not ready yet")
}
if destination.IsDomain() {
destinationAddresses, err := s.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{})
@@ -565,7 +744,7 @@ func (s *ServerEndpoint) DialContext(ctx context.Context, network string, destin
func (s *ServerEndpoint) ListenPacketWithDestination(ctx context.Context, destination M.Socksaddr) (net.PacketConn, netip.Addr, error) {
s.logger.InfoContext(ctx, "outbound packet connection to ", destination)
if !s.started.Load() {
return nil, netip.Addr{}, E.New("OpenVPN server is not ready yet")
return nil, netip.Addr{}, E.New("endpoint is not ready yet")
}
if destination.IsDomain() {
destinationAddresses, err := s.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{})