chore(internal/providers): simplify OpenVPN config building
This commit is contained in:
@@ -1,5 +1,254 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/openvpn/parse"
|
||||
)
|
||||
|
||||
type OpenVPNProviderSettings struct {
|
||||
Ping int
|
||||
RemoteCertTLS bool
|
||||
Ciphers []string
|
||||
Auth string
|
||||
CA string
|
||||
CRLVerify string
|
||||
Cert string
|
||||
Key string
|
||||
RSAKey string
|
||||
TLSAuth string
|
||||
TLSCrypt string
|
||||
MssFix uint16
|
||||
FastIO bool
|
||||
AuthUserPass bool
|
||||
AuthToken bool
|
||||
Fragment uint16
|
||||
SndBuf uint32
|
||||
RcvBuf uint32
|
||||
// VerifyX509Name can be set to a custom name to verify against.
|
||||
// Note VerifyX509Type has to be set for it to be verified.
|
||||
// If it is left unset, the code will deduce a name to verify against
|
||||
// using the connection hostname and according to VerifyX509Type.
|
||||
VerifyX509Name string
|
||||
// VerifyX509Type can be "name-prefix", "name"
|
||||
VerifyX509Type string
|
||||
TLSCipher string
|
||||
TunMTU uint16
|
||||
TunMTUExtra uint16
|
||||
RenegDisabled bool
|
||||
RenegSec uint16
|
||||
KeyDirection string
|
||||
ExtraLines []string
|
||||
UDPLines []string
|
||||
IPv6Lines []string
|
||||
}
|
||||
|
||||
//nolint:gocognit,gocyclo
|
||||
func OpenVPNConfig(provider OpenVPNProviderSettings,
|
||||
connection models.Connection,
|
||||
settings settings.OpenVPN) []string {
|
||||
var lines openvpnConfigLines
|
||||
lines.add("client")
|
||||
lines.add("nobind")
|
||||
lines.add("tls-exit") // exit OpenVPN on a TLS error
|
||||
lines.add("auth-nocache") // do not cache auth credentials
|
||||
lines.add("mute-replay-warnings") // these are often ignored by some VPN providers
|
||||
lines.add("auth-retry", "nointeract") // retry authenticating without interaction
|
||||
lines.add("suppress-timestamps") // do not log timestamps, the Gluetun logger takes care of it
|
||||
lines.add("dev", settings.Interface)
|
||||
lines.add("verb", fmt.Sprint(*settings.Verbosity))
|
||||
lines.add("auth-user-pass", constants.OpenVPNAuthConf)
|
||||
lines.add("proto", connection.Protocol)
|
||||
lines.add("remote", connection.IP.String(), fmt.Sprint(connection.Port))
|
||||
|
||||
if !provider.AuthToken {
|
||||
lines.add("pull-filter", "ignore", `"auth-token"`) // prevent auth failed loops
|
||||
}
|
||||
|
||||
if provider.KeyDirection != "" {
|
||||
lines.add("key-direction", provider.KeyDirection)
|
||||
}
|
||||
|
||||
if provider.Ping > 0 {
|
||||
lines.add("ping", fmt.Sprint(provider.Ping))
|
||||
}
|
||||
|
||||
if provider.RenegDisabled {
|
||||
lines.add("reneg-sec", "0")
|
||||
} else if provider.RenegSec > 0 {
|
||||
lines.add("reneg-sec", fmt.Sprint(provider.RenegSec))
|
||||
}
|
||||
|
||||
if provider.RemoteCertTLS {
|
||||
// equivalent to older 'ns-cert-type' option
|
||||
lines.add("remote-cert-tls server")
|
||||
}
|
||||
|
||||
x509Type := provider.VerifyX509Type
|
||||
if x509Type != "" {
|
||||
x509Name := provider.VerifyX509Name
|
||||
if x509Name == "" {
|
||||
// find name from connection hostname depending on type
|
||||
switch x509Type {
|
||||
case "name":
|
||||
x509Name = connection.Hostname
|
||||
case "name-prefix":
|
||||
x509Name = strings.Split(connection.Hostname, ".")[0]
|
||||
default:
|
||||
panic(fmt.Sprintf("verify-x509-name type not supported: %q", x509Type))
|
||||
}
|
||||
}
|
||||
lines.add("verify-x509-name", x509Name, x509Type)
|
||||
}
|
||||
|
||||
if provider.TLSCipher != "" {
|
||||
lines.add("tls-cipher", provider.TLSCipher)
|
||||
}
|
||||
|
||||
if provider.FastIO {
|
||||
lines.add("fast-io")
|
||||
}
|
||||
|
||||
ciphers := defaultStringSlice(settings.Ciphers, provider.Ciphers)
|
||||
cipherLines := CipherLines(ciphers, settings.Version)
|
||||
lines.addLines(cipherLines)
|
||||
|
||||
auth := defaultString(*settings.Auth, provider.Auth)
|
||||
if auth != "" {
|
||||
lines.add("auth", auth)
|
||||
}
|
||||
|
||||
if provider.TunMTU > 0 {
|
||||
lines.add("tun-mtu", fmt.Sprint(provider.TunMTU))
|
||||
}
|
||||
|
||||
if provider.TunMTUExtra > 0 {
|
||||
lines.add("tun-mtu-extra", fmt.Sprint(provider.TunMTUExtra))
|
||||
}
|
||||
|
||||
mssFix := defaultUint16(*settings.MSSFix, provider.MssFix)
|
||||
if mssFix > 0 {
|
||||
lines.add("mssfix", fmt.Sprint(mssFix))
|
||||
}
|
||||
|
||||
if provider.SndBuf > 0 {
|
||||
lines.add("sndbuf", fmt.Sprint(provider.SndBuf))
|
||||
}
|
||||
|
||||
if provider.RcvBuf > 0 {
|
||||
lines.add("rcvbuf", fmt.Sprint(provider.RcvBuf))
|
||||
}
|
||||
|
||||
if connection.Protocol == constants.UDP {
|
||||
lines.add("explicit-exit-notify")
|
||||
}
|
||||
|
||||
if settings.ProcessUser != "root" {
|
||||
lines.add("user", settings.ProcessUser)
|
||||
lines.add("persist-tun")
|
||||
lines.add("persist-key")
|
||||
}
|
||||
|
||||
if *settings.IPv6 {
|
||||
lines.add("tun-ipv6")
|
||||
} else {
|
||||
lines.add("pull-filter", "ignore", `"route-ipv6"`)
|
||||
lines.add("pull-filter", "ignore", `"ifconfig-ipv6"`)
|
||||
lines.addLines(provider.IPv6Lines)
|
||||
}
|
||||
|
||||
if provider.CA != "" {
|
||||
lines.addLines(WrapOpenvpnCA(provider.CA))
|
||||
}
|
||||
if provider.CRLVerify != "" {
|
||||
lines.addLines(WrapOpenvpnCRLVerify(provider.CRLVerify))
|
||||
}
|
||||
if provider.Cert != "" {
|
||||
lines.addLines(WrapOpenvpnCert(provider.Cert))
|
||||
}
|
||||
if provider.Key != "" {
|
||||
lines.addLines(WrapOpenvpnKey(provider.Key))
|
||||
}
|
||||
if provider.RSAKey != "" {
|
||||
lines.addLines(WrapOpenvpnRSAKey(provider.RSAKey))
|
||||
}
|
||||
if provider.TLSAuth != "" {
|
||||
lines.addLines(WrapOpenvpnTLSAuth(provider.TLSAuth))
|
||||
}
|
||||
if provider.TLSCrypt != "" {
|
||||
lines.addLines(WrapOpenvpnTLSCrypt(provider.TLSCrypt))
|
||||
}
|
||||
|
||||
if *settings.ClientCrt != "" {
|
||||
certData, err := parse.ExtractCert([]byte(*settings.ClientCrt))
|
||||
panicOnError(err, "cannot extract client crt")
|
||||
lines.addLines(WrapOpenvpnCert(certData))
|
||||
}
|
||||
|
||||
if *settings.ClientKey != "" {
|
||||
keyData, err := parse.ExtractPrivateKey([]byte(*settings.ClientKey))
|
||||
panicOnError(err, "cannot extract client private key")
|
||||
lines.addLines(WrapOpenvpnKey(keyData))
|
||||
}
|
||||
|
||||
lines.addLines(provider.ExtraLines)
|
||||
|
||||
// Add a trailing empty line
|
||||
lines.add("")
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
type openvpnConfigLines []string
|
||||
|
||||
func (o *openvpnConfigLines) add(words ...string) {
|
||||
*o = append(*o, strings.Join(words, " "))
|
||||
}
|
||||
|
||||
func (o *openvpnConfigLines) addLines(lines []string) {
|
||||
for _, line := range lines {
|
||||
o.add(line)
|
||||
}
|
||||
}
|
||||
|
||||
func defaultString(value, defaultValue string) string {
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func defaultUint16(value, defaultValue uint16) uint16 {
|
||||
if value == 0 {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func defaultStringSlice(value, defaultValue []string) (
|
||||
result []string) {
|
||||
if len(value) > 0 {
|
||||
result = make([]string, len(value))
|
||||
copy(result, value)
|
||||
return result
|
||||
}
|
||||
result = make([]string, len(defaultValue))
|
||||
copy(result, defaultValue)
|
||||
return result
|
||||
}
|
||||
|
||||
func panicOnError(err error, context string) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
panicMessage := fmt.Sprintf("%s: %s", context, err)
|
||||
panic(panicMessage)
|
||||
}
|
||||
|
||||
func WrapOpenvpnCA(certificate string) (lines []string) {
|
||||
return []string{
|
||||
"<ca>",
|
||||
|
||||
Reference in New Issue
Block a user