Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions internal/actions/tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,11 @@ func TransportOptions() []SelectOption {
Value: string(config.TransportSlipstream),
Description: "High-performance DNS tunnel with TLS",
},
{
Label: "Slipstream Plus",
Value: string(config.TransportSlipstreamPlus),
Description: "Slipstream Plus with Turbo mode and backpressure (Fox-Fig fork)",
},
{
Label: "DNSTT",
Value: string(config.TransportDNSTT),
Expand Down
30 changes: 25 additions & 5 deletions internal/binary/binary.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ const (
BinarySSServer BinaryType = "ssserver"
BinaryMicrosocks BinaryType = "microsocks"
BinarySSHTunUser BinaryType = "sshtun-user"
BinaryVayDNSServer BinaryType = "vaydns-server"
BinaryVayDNSServer BinaryType = "vaydns-server"
BinarySlipstreamPlusServer BinaryType = "slipstream-plus-server"

// Client binaries (used in testing)
BinaryDNSTTClient BinaryType = "dnstt-client"
BinarySlipstreamClient BinaryType = "slipstream-client"
BinarySSLocal BinaryType = "sslocal"
BinaryVayDNSClient BinaryType = "vaydns-client"
BinaryDNSTTClient BinaryType = "dnstt-client"
BinarySlipstreamClient BinaryType = "slipstream-client"
BinarySSLocal BinaryType = "sslocal"
BinaryVayDNSClient BinaryType = "vaydns-client"
BinarySlipstreamPlusClient BinaryType = "slipstream-plus-client"
)

// BinaryDef defines how to obtain a binary.
Expand Down Expand Up @@ -126,6 +128,15 @@ var DefaultBinaries = map[BinaryType]BinaryDef{
"windows": {"amd64"},
},
},
BinarySlipstreamPlusServer: {
Type: BinarySlipstreamPlusServer,
EnvVar: "DNSTM_SLIPSTREAM_PLUS_SERVER_PATH",
URLPattern: "https://github.com/Fox-Fig/slipstream-rust-deploy/releases/latest/download/slipstream-server-{os}-{arch}",
PinnedVersion: "latest",
Platforms: map[string][]string{
"linux": {"amd64", "arm64"},
},
},

// Client binaries - pinned versions for testing only
BinaryDNSTTClient: {
Expand Down Expand Up @@ -174,6 +185,15 @@ var DefaultBinaries = map[BinaryType]BinaryDef{
"windows": {"amd64"},
},
},
BinarySlipstreamPlusClient: {
Type: BinarySlipstreamPlusClient,
EnvVar: "DNSTM_TEST_SLIPSTREAM_PLUS_CLIENT_PATH",
URLPattern: "https://github.com/Fox-Fig/slipstream-rust-deploy/releases/latest/download/slipstream-client-{os}-{arch}",
PinnedVersion: "latest", // Manual bump only
Platforms: map[string][]string{
"linux": {"amd64", "arm64"},
},
},
}

func init() {
Expand Down
13 changes: 13 additions & 0 deletions internal/clientcfg/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ func Generate(tunnel *config.TunnelConfig, backend *config.BackendConfig, opts G
}
cfg.Transport.PubKey = pubKey

case config.TransportSlipstreamPlus:
if !opts.NoCert {
certPath := filepath.Join(tunnelDir, "cert.pem")
if tunnel.SlipstreamPlus != nil && tunnel.SlipstreamPlus.Cert != "" {
certPath = tunnel.SlipstreamPlus.Cert
}
certPEM, err := os.ReadFile(certPath)
if err != nil {
return nil, fmt.Errorf("failed to read certificate: %w", err)
}
cfg.Transport.Cert = string(certPEM)
}

case config.TransportVayDNS:
pubKeyPath := filepath.Join(tunnelDir, "server.pub")
pubKey, err := keys.ReadPublicKey(pubKeyPath)
Expand Down
11 changes: 11 additions & 0 deletions internal/config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@ func (c *Config) ApplyDefaults() {
t.DNSTT.MTU = 1232
}
}
if t.Transport == TransportSlipstreamPlus {
if t.SlipstreamPlus == nil {
t.SlipstreamPlus = &SlipstreamPlusConfig{}
}
if t.SlipstreamPlus.MaxConnections == 0 {
t.SlipstreamPlus.MaxConnections = 256
}
if t.SlipstreamPlus.IdleTimeoutSeconds == 0 {
t.SlipstreamPlus.IdleTimeoutSeconds = 60
}
}
if t.Transport == TransportVayDNS {
if t.VayDNS == nil {
t.VayDNS = &VayDNSConfig{}
Expand Down
44 changes: 32 additions & 12 deletions internal/config/tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@ package config
type TransportType string

const (
TransportSlipstream TransportType = "slipstream"
TransportDNSTT TransportType = "dnstt"
TransportVayDNS TransportType = "vaydns"
TransportSlipstream TransportType = "slipstream"
TransportSlipstreamPlus TransportType = "slipstream-plus"
TransportDNSTT TransportType = "dnstt"
TransportVayDNS TransportType = "vaydns"
)

// TunnelConfig configures a DNS tunnel.
type TunnelConfig struct {
Tag string `json:"tag"`
Enabled *bool `json:"enabled,omitempty"`
Transport TransportType `json:"transport"`
Backend string `json:"backend"`
Domain string `json:"domain"`
Port int `json:"port,omitempty"`
Slipstream *SlipstreamConfig `json:"slipstream,omitempty"`
DNSTT *DNSTTConfig `json:"dnstt,omitempty"`
VayDNS *VayDNSConfig `json:"vaydns,omitempty"`
Tag string `json:"tag"`
Enabled *bool `json:"enabled,omitempty"`
Transport TransportType `json:"transport"`
Backend string `json:"backend"`
Domain string `json:"domain"`
Port int `json:"port,omitempty"`
Slipstream *SlipstreamConfig `json:"slipstream,omitempty"`
SlipstreamPlus *SlipstreamPlusConfig `json:"slipstream_plus,omitempty"`
DNSTT *DNSTTConfig `json:"dnstt,omitempty"`
VayDNS *VayDNSConfig `json:"vaydns,omitempty"`
}

// SlipstreamConfig holds Slipstream-specific configuration.
Expand All @@ -28,6 +30,16 @@ type SlipstreamConfig struct {
Key string `json:"key,omitempty"`
}

// SlipstreamPlusConfig holds Slipstream Plus-specific configuration.
type SlipstreamPlusConfig struct {
Cert string `json:"cert,omitempty"`
Key string `json:"key,omitempty"`
MaxConnections int `json:"max_connections,omitempty"`
IdleTimeoutSeconds int `json:"idle_timeout_seconds,omitempty"`
Fallback string `json:"fallback,omitempty"`
ResetSeed string `json:"reset_seed,omitempty"`
}

// DNSTTConfig holds DNSTT-specific configuration.
type DNSTTConfig struct {
MTU int `json:"mtu,omitempty"`
Expand Down Expand Up @@ -116,6 +128,11 @@ func (t *TunnelConfig) IsSlipstream() bool {
return t.Transport == TransportSlipstream
}

// IsSlipstreamPlus returns true if this is a Slipstream Plus tunnel.
func (t *TunnelConfig) IsSlipstreamPlus() bool {
return t.Transport == TransportSlipstreamPlus
}

// IsDNSTT returns true if this is a DNSTT tunnel.
func (t *TunnelConfig) IsDNSTT() bool {
return t.Transport == TransportDNSTT
Expand All @@ -130,6 +147,7 @@ func (t *TunnelConfig) IsVayDNS() bool {
func GetTransportTypes() []TransportType {
return []TransportType{
TransportSlipstream,
TransportSlipstreamPlus,
TransportDNSTT,
TransportVayDNS,
}
Expand All @@ -140,6 +158,8 @@ func GetTransportTypeDisplayName(t TransportType) string {
switch t {
case TransportSlipstream:
return "Slipstream"
case TransportSlipstreamPlus:
return "Slipstream Plus"
case TransportDNSTT:
return "DNSTT"
case TransportVayDNS:
Expand Down
18 changes: 17 additions & 1 deletion internal/config/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package config

import (
"fmt"
"net"
"regexp"
"time"
)
Expand Down Expand Up @@ -109,7 +110,7 @@ func (c *Config) validateTunnels() error {
return fmt.Errorf("tunnel '%s': transport is required", t.Tag)
}

if t.Transport != TransportSlipstream && t.Transport != TransportDNSTT && t.Transport != TransportVayDNS {
if t.Transport != TransportSlipstream && t.Transport != TransportSlipstreamPlus && t.Transport != TransportDNSTT && t.Transport != TransportVayDNS {
return fmt.Errorf("tunnel '%s': unknown transport %s", t.Tag, t.Transport)
}

Expand Down Expand Up @@ -152,6 +153,21 @@ func (c *Config) validateTunnels() error {
usedDomains[t.Domain] = t.Tag
}

// Validate Slipstream Plus-specific config
if t.Transport == TransportSlipstreamPlus && t.SlipstreamPlus != nil {
if t.SlipstreamPlus.MaxConnections < 0 {
return fmt.Errorf("tunnel '%s': slipstream_plus.max_connections must not be negative", t.Tag)
}
if t.SlipstreamPlus.IdleTimeoutSeconds < 0 {
return fmt.Errorf("tunnel '%s': slipstream_plus.idle_timeout_seconds must not be negative", t.Tag)
}
if t.SlipstreamPlus.Fallback != "" {
if _, _, err := net.SplitHostPort(t.SlipstreamPlus.Fallback); err != nil {
return fmt.Errorf("tunnel '%s': slipstream_plus.fallback must be host:port: %w", t.Tag, err)
}
}
}

// Validate DNSTT-specific config
if t.Transport == TransportDNSTT && t.DNSTT != nil {
if t.DNSTT.MTU != 0 && (t.DNSTT.MTU < 512 || t.DNSTT.MTU > 1400) {
Expand Down
42 changes: 41 additions & 1 deletion internal/handlers/config_load.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ func HandleConfigLoad(ctx *actions.Context) error {
for _, tunnel := range newCfg.Tunnels {
ctx.Output.Printf("\n %s (%s):\n", tunnel.Tag, tunnel.Domain)
tunnelDir := filepath.Join(config.TunnelsDir, tunnel.Tag)
if tunnel.Transport == config.TransportSlipstream {
if tunnel.Transport == config.TransportSlipstream || tunnel.Transport == config.TransportSlipstreamPlus {
certPath := filepath.Join(tunnelDir, "cert.pem")
keyPath := filepath.Join(tunnelDir, "key.pem")
fingerprint, err := certs.ReadCertificateFingerprint(certPath)
Expand Down Expand Up @@ -279,6 +279,46 @@ func ensureTunnelService(ctx *actions.Context, tunnelCfg *config.TunnelConfig, c
tunnelCfg.Slipstream.Key = certInfo.KeyPath
ctx.Output.Status(fmt.Sprintf("Generated certificate for %s", tunnelCfg.Domain))
}
} else if tunnelCfg.Transport == config.TransportSlipstreamPlus {
if tunnelCfg.SlipstreamPlus == nil {
tunnelCfg.SlipstreamPlus = &config.SlipstreamPlusConfig{}
}
certProvided := tunnelCfg.SlipstreamPlus.Cert != ""
keyProvided := tunnelCfg.SlipstreamPlus.Key != ""
if certProvided || keyProvided {
if !certProvided || !keyProvided {
return fmt.Errorf("both cert and key paths must be provided for tunnel %s", tunnelCfg.Tag)
}
if _, err := os.Stat(tunnelCfg.SlipstreamPlus.Cert); err != nil {
return fmt.Errorf("certificate file not found: %s", tunnelCfg.SlipstreamPlus.Cert)
}
canRead, err := system.CanDnstmUserReadFile(tunnelCfg.SlipstreamPlus.Cert)
if err != nil {
return fmt.Errorf("failed to check certificate permissions: %w", err)
}
if !canRead {
return fmt.Errorf("dnstm user cannot read certificate file: %s", tunnelCfg.SlipstreamPlus.Cert)
}
if _, err := os.Stat(tunnelCfg.SlipstreamPlus.Key); err != nil {
return fmt.Errorf("key file not found: %s", tunnelCfg.SlipstreamPlus.Key)
}
canRead, err = system.CanDnstmUserReadFile(tunnelCfg.SlipstreamPlus.Key)
if err != nil {
return fmt.Errorf("failed to check key permissions: %w", err)
}
if !canRead {
return fmt.Errorf("dnstm user cannot read key file: %s", tunnelCfg.SlipstreamPlus.Key)
}
ctx.Output.Status(fmt.Sprintf("Using provided certificate for %s", tunnelCfg.Domain))
} else {
certInfo, err := certs.GetOrCreateInDir(tunnelDir, tunnelCfg.Domain)
if err != nil {
return fmt.Errorf("failed to generate certificate: %w", err)
}
tunnelCfg.SlipstreamPlus.Cert = certInfo.CertPath
tunnelCfg.SlipstreamPlus.Key = certInfo.KeyPath
ctx.Output.Status(fmt.Sprintf("Generated certificate for %s", tunnelCfg.Domain))
}
} else if tunnelCfg.Transport == config.TransportDNSTT {
// Initialize DNSTT config if nil
if tunnelCfg.DNSTT == nil {
Expand Down
23 changes: 21 additions & 2 deletions internal/handlers/tunnel_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func addTunnelInteractive(ctx *actions.Context, cfg *config.Config) error {
{Label: "VayDNS", Value: string(config.TransportVayDNS)},
{Label: "DNSTT", Value: string(config.TransportDNSTT)},
{Label: "Slipstream", Value: string(config.TransportSlipstream)},
{Label: "Slipstream Plus", Value: string(config.TransportSlipstreamPlus)},
},
})
if err != nil {
Expand Down Expand Up @@ -335,6 +336,9 @@ func addTunnelInteractive(ctx *actions.Context, cfg *config.Config) error {
RecordType: vaydnsRecordType,
}
}
if tunnelCfg.Transport == config.TransportSlipstreamPlus {
tunnelCfg.SlipstreamPlus = &config.SlipstreamPlusConfig{}
}

// Allocate port
port := cfg.AllocateNextPort()
Expand All @@ -358,8 +362,8 @@ func addTunnelNonInteractive(ctx *actions.Context, cfg *config.Config) error {
transportType := config.TransportType(transportStr)

// Validate transport type
if transportType != config.TransportSlipstream && transportType != config.TransportDNSTT && transportType != config.TransportVayDNS {
return fmt.Errorf("invalid transport type: %s (must be slipstream, dnstt, or vaydns)", transportType)
if transportType != config.TransportSlipstream && transportType != config.TransportSlipstreamPlus && transportType != config.TransportDNSTT && transportType != config.TransportVayDNS {
return fmt.Errorf("invalid transport type: %s (must be slipstream, slipstream-plus, dnstt, or vaydns)", transportType)
}

// Validate backend exists and is compatible
Expand Down Expand Up @@ -452,6 +456,9 @@ func addTunnelNonInteractive(ctx *actions.Context, cfg *config.Config) error {
}
tunnelCfg.VayDNS = v
}
if transportType == config.TransportSlipstreamPlus {
tunnelCfg.SlipstreamPlus = &config.SlipstreamPlusConfig{}
}

// Allocate port
if port == 0 {
Expand Down Expand Up @@ -586,6 +593,18 @@ func createTunnel(ctx *actions.Context, tunnelCfg *config.TunnelConfig, cfg *con
Key: certInfo.KeyPath,
}
ctx.Output.Status("TLS certificate ready")
} else if tunnelCfg.Transport == config.TransportSlipstreamPlus {
certInfo, err := certs.GetOrCreateInDir(tunnelDir, tunnelCfg.Domain)
if err != nil {
return fmt.Errorf("failed to generate certificate: %w", err)
}
fingerprint = certInfo.Fingerprint
if tunnelCfg.SlipstreamPlus == nil {
tunnelCfg.SlipstreamPlus = &config.SlipstreamPlusConfig{}
}
tunnelCfg.SlipstreamPlus.Cert = certInfo.CertPath
tunnelCfg.SlipstreamPlus.Key = certInfo.KeyPath
ctx.Output.Status("TLS certificate ready")
} else if tunnelCfg.Transport == config.TransportDNSTT {
keyInfo, err := keys.GetOrCreateInDir(tunnelDir)
if err != nil {
Expand Down
10 changes: 8 additions & 2 deletions internal/handlers/tunnel_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,14 @@ func HandleTunnelStatus(ctx *actions.Context) error {

// Show certificate/key info based on transport type
tunnelDir := filepath.Join(config.TunnelsDir, tunnelCfg.Tag)
if tunnelCfg.Transport == config.TransportSlipstream {
if tunnelCfg.Transport == config.TransportSlipstream || tunnelCfg.Transport == config.TransportSlipstreamPlus {
certPath := filepath.Join(tunnelDir, "cert.pem")
if tunnelCfg.Slipstream != nil && tunnelCfg.Slipstream.Cert != "" {
certPath = tunnelCfg.Slipstream.Cert
}
if tunnelCfg.SlipstreamPlus != nil && tunnelCfg.SlipstreamPlus.Cert != "" {
certPath = tunnelCfg.SlipstreamPlus.Cert
}
fingerprint, err := certs.ReadCertificateFingerprint(certPath)
if err == nil {
certSection := actions.InfoSection{
Expand Down Expand Up @@ -147,11 +150,14 @@ func HandleTunnelStatus(ctx *actions.Context) error {
ctx.Output.Println()
ctx.Output.Println(tunnel.GetFormattedInfo())

if tunnelCfg.Transport == config.TransportSlipstream {
if tunnelCfg.Transport == config.TransportSlipstream || tunnelCfg.Transport == config.TransportSlipstreamPlus {
certPath := filepath.Join(tunnelDir, "cert.pem")
if tunnelCfg.Slipstream != nil && tunnelCfg.Slipstream.Cert != "" {
certPath = tunnelCfg.Slipstream.Cert
}
if tunnelCfg.SlipstreamPlus != nil && tunnelCfg.SlipstreamPlus.Cert != "" {
certPath = tunnelCfg.SlipstreamPlus.Cert
}
fingerprint, err := certs.ReadCertificateFingerprint(certPath)
if err == nil {
ctx.Output.Println("Certificate Fingerprint:")
Expand Down
1 change: 1 addition & 0 deletions internal/installer/uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func PerformFullUninstall(output actions.OutputWriter, isInteractive bool) error
"/usr/local/bin/ssserver",
"/usr/local/bin/sshtun-user",
"/usr/local/bin/vaydns-server",
"/usr/local/bin/slipstream-plus-server",
"/usr/local/bin/microsocks",
}
for _, bin := range binaries {
Expand Down
Loading
Loading