mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-08-06 05:45:14 +03:00
Add OpenVPN, TrustTunnel, Sudoku, inbound managers. Fixes
This commit is contained in:
284
transport/openvpn/tunnel.go
Normal file
284
transport/openvpn/tunnel.go
Normal file
@@ -0,0 +1,284 @@
|
||||
package openvpn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/tls"
|
||||
)
|
||||
|
||||
type TunnelOptions struct {
|
||||
Dialer N.Dialer
|
||||
Servers []option.ServerOptions
|
||||
TLSConfig tls.Config
|
||||
Config *ClientConfig
|
||||
UDPTimeout time.Duration
|
||||
ReconnectDelay time.Duration
|
||||
PingInterval time.Duration
|
||||
}
|
||||
|
||||
type Tunnel struct {
|
||||
ctx context.Context
|
||||
logger logger.ContextLogger
|
||||
options TunnelOptions
|
||||
device Device
|
||||
client *Client
|
||||
mtu uint32
|
||||
serverIndex int
|
||||
|
||||
await chan struct{}
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewTunnel(ctx context.Context, logger logger.ContextLogger, options TunnelOptions) (*Tunnel, error) {
|
||||
if options.ReconnectDelay == 0 {
|
||||
options.ReconnectDelay = 5 * time.Second
|
||||
}
|
||||
return &Tunnel{
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
options: options,
|
||||
await: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *Tunnel) Start() error {
|
||||
go func() {
|
||||
defer close(t.await)
|
||||
client, err := t.getClient()
|
||||
if err != nil {
|
||||
t.logger.Error("OpenVPN connect: ", err)
|
||||
return
|
||||
}
|
||||
t.mtu = 1500
|
||||
if client.push.MTU > 0 {
|
||||
t.mtu = client.push.MTU
|
||||
}
|
||||
deviceOptions := DeviceOptions{
|
||||
Context: t.ctx,
|
||||
Logger: t.logger,
|
||||
UDPTimeout: t.options.UDPTimeout,
|
||||
MTU: t.mtu,
|
||||
Address: client.push.Prefixes,
|
||||
}
|
||||
device, err := NewDevice(deviceOptions)
|
||||
if err != nil {
|
||||
client.Close()
|
||||
t.logger.Error("create OpenVPN device: ", err)
|
||||
return
|
||||
}
|
||||
t.device = device
|
||||
if err := device.Start(); err != nil {
|
||||
client.Close()
|
||||
t.logger.Error("start OpenVPN device: ", err)
|
||||
return
|
||||
}
|
||||
t.maintainTunnel()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tunnel) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
if !destination.Addr.IsValid() {
|
||||
return nil, E.Cause(os.ErrInvalid, "invalid non-IP destination")
|
||||
}
|
||||
return t.device.DialContext(ctx, network, destination)
|
||||
}
|
||||
|
||||
func (t *Tunnel) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
if !destination.Addr.IsValid() {
|
||||
return nil, E.Cause(os.ErrInvalid, "invalid non-IP destination")
|
||||
}
|
||||
return t.device.ListenPacket(ctx, destination)
|
||||
}
|
||||
|
||||
func (t *Tunnel) Close() error {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.client != nil {
|
||||
t.client.Close()
|
||||
t.client = nil
|
||||
}
|
||||
if t.device != nil {
|
||||
return t.device.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tunnel) isTunnelInitialized(ctx context.Context) error {
|
||||
select {
|
||||
case <-t.await:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
if t.device == nil {
|
||||
return E.New("endpoint not initialized")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tunnel) maintainTunnel() {
|
||||
go func() {
|
||||
bufs := make([][]byte, 1)
|
||||
bufs[0] = make([]byte, t.mtu)
|
||||
sizes := make([]int, 1)
|
||||
for t.ctx.Err() == nil {
|
||||
_, err := t.device.Read(bufs, sizes, 0)
|
||||
if err != nil {
|
||||
if t.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
client, err := t.getClient()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err := client.WriteIPPacket(t.ctx, bufs[0][:sizes[0]]); err != nil {
|
||||
if t.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
for t.ctx.Err() == nil {
|
||||
client, err := t.getClient()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
packet, err := client.ReadIPPacket(t.ctx)
|
||||
if err != nil {
|
||||
if t.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
if ok := t.closeClient(client); ok {
|
||||
t.logger.ErrorContext(t.ctx, fmt.Errorf("connection lost: %v", err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
if bytes.Equal(packet, pingPayload) {
|
||||
continue
|
||||
}
|
||||
if _, err := t.device.Write([][]byte{packet}, 0); err != nil {
|
||||
if t.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
pingInterval := t.options.PingInterval
|
||||
if pingInterval == 0 && t.client != nil && t.client.push.Ping > 0 {
|
||||
pingInterval = time.Duration(t.client.push.Ping) * time.Second
|
||||
}
|
||||
if pingInterval > 0 {
|
||||
go func() {
|
||||
ticker := time.NewTicker(pingInterval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
client, err := t.getClient()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
client.WriteIPPacket(t.ctx, pingPayload)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
<-t.ctx.Done()
|
||||
}
|
||||
|
||||
func (t *Tunnel) getClient() (*Client, error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.ctx.Err() != nil {
|
||||
return nil, t.ctx.Err()
|
||||
}
|
||||
if t.client != nil {
|
||||
return t.client, nil
|
||||
}
|
||||
timer := time.NewTimer(0)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
t.logger.InfoContext(t.ctx, "connecting to OpenVPN server")
|
||||
client, err := t.connect()
|
||||
if err != nil {
|
||||
t.logger.ErrorContext(t.ctx, fmt.Errorf("connect failed: %v", err))
|
||||
timer.Reset(t.options.ReconnectDelay)
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return nil, t.ctx.Err()
|
||||
case <-timer.C:
|
||||
}
|
||||
continue
|
||||
}
|
||||
t.client = client
|
||||
t.logger.InfoContext(t.ctx, "connected to OpenVPN server")
|
||||
return client, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tunnel) closeClient(client *Client) bool {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if client == t.client {
|
||||
t.client.Close()
|
||||
t.client = nil
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Tunnel) connect() (*Client, error) {
|
||||
config := t.options.Config
|
||||
server := t.options.Servers[t.serverIndex].Build()
|
||||
t.serverIndex = (t.serverIndex + 1) % len(t.options.Servers)
|
||||
connectCtx, cancel := context.WithTimeout(t.ctx, t.options.ReconnectDelay)
|
||||
defer cancel()
|
||||
var conn net.Conn
|
||||
var err error
|
||||
if config.Proto == ProtoTCP {
|
||||
conn, err = t.options.Dialer.DialContext(connectCtx, N.NetworkTCP, server)
|
||||
} else {
|
||||
conn, err = t.options.Dialer.DialContext(connectCtx, N.NetworkUDP, server)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dial openvpn server: %w", err)
|
||||
}
|
||||
var packetIO PacketIO
|
||||
if config.Proto == ProtoTCP {
|
||||
packetIO = NewTCPPacketIO(conn)
|
||||
} else {
|
||||
packetIO = NewDatagramPacketIO(conn)
|
||||
}
|
||||
client, err := NewClient(config, packetIO, t.options.TLSConfig)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
_, err = client.Handshake(connectCtx)
|
||||
if err != nil {
|
||||
client.Close()
|
||||
return nil, fmt.Errorf("openvpn handshake: %w", err)
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
var pingPayload = []byte{
|
||||
0x2a, 0x18, 0x7b, 0xf3, 0x64, 0x1e, 0xb4, 0xcb,
|
||||
0x07, 0xed, 0x2d, 0x0a, 0x98, 0x1f, 0xc7, 0x48,
|
||||
}
|
||||
Reference in New Issue
Block a user