mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-03 22:18:39 +00:00
all: add CLI flags for no config start
This commit adds the ability to start `ctrld` without config file. All necessary information can be provided via command line flags, either in base64 encoded config or launch arguments.
This commit is contained in:
committed by
Cuong Manh Le
parent
30fefe7ab9
commit
b93970ccfd
104
cmd/ctrld/cli.go
104
cmd/ctrld/cli.go
@@ -1,11 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/kardianos/service"
|
||||
@@ -38,6 +42,7 @@ func initCLI() {
|
||||
`verbose log output, "-v" means query logging enabled, "-vv" means debug level logging enabled`,
|
||||
)
|
||||
|
||||
basicModeFlags := []string{"listen", "primary_upstream", "secondary_upstream", "domains", "log"}
|
||||
runCmd := &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "Run the DNS proxy server",
|
||||
@@ -49,14 +54,18 @@ func initCLI() {
|
||||
if configPath != "" {
|
||||
v.SetConfigFile(configPath)
|
||||
}
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
||||
writeConfigFile()
|
||||
defaultConfigWritten = true
|
||||
} else {
|
||||
log.Fatalf("failed to decode config file: %v", err)
|
||||
noConfigStart := func() bool {
|
||||
for _, flagName := range basicModeFlags {
|
||||
if cmd.Flags().Lookup(flagName).Changed {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}()
|
||||
|
||||
readConfigFile(!noConfigStart && configBase64 == "")
|
||||
readBase64Config()
|
||||
processNoConfigFlags(noConfigStart)
|
||||
if err := v.Unmarshal(&cfg); err != nil {
|
||||
log.Fatalf("failed to unmarshal config: %v", err)
|
||||
}
|
||||
@@ -106,6 +115,12 @@ func initCLI() {
|
||||
}
|
||||
runCmd.Flags().BoolVarP(&daemon, "daemon", "d", false, "Run as daemon")
|
||||
runCmd.Flags().StringVarP(&configPath, "config", "c", "", "Path to config file")
|
||||
runCmd.Flags().StringVarP(&configBase64, "base64_config", "", "", "base64 encoded config")
|
||||
runCmd.Flags().StringVarP(&listenAddress, "listen", "", "", "listener address and port, in format: address:port")
|
||||
runCmd.Flags().StringVarP(&primaryUpstream, "primary_upstream", "", "", "primary upstream endpoint")
|
||||
runCmd.Flags().StringVarP(&secondaryUpstream, "secondary_upstream", "", "", "secondary upstream endpoint")
|
||||
runCmd.Flags().StringSliceVarP(&domains, "domains", "", nil, "list of domain to apply in a split DNS policy")
|
||||
runCmd.Flags().StringVarP(&logPath, "log", "", "", "path to log file")
|
||||
|
||||
rootCmd.AddCommand(runCmd)
|
||||
|
||||
@@ -125,3 +140,78 @@ func writeConfigFile() {
|
||||
log.Printf("failed to write config file: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func readConfigFile(configWritten bool) {
|
||||
err := v.ReadInConfig()
|
||||
if err == nil || !configWritten {
|
||||
return
|
||||
}
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
||||
writeConfigFile()
|
||||
defaultConfigWritten = true
|
||||
return
|
||||
}
|
||||
log.Fatalf("failed to decode config file: %v", err)
|
||||
}
|
||||
|
||||
func readBase64Config() {
|
||||
if configBase64 == "" {
|
||||
return
|
||||
}
|
||||
configStr, err := base64.StdEncoding.DecodeString(configBase64)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid base64 config: %v", err)
|
||||
}
|
||||
if err := v.ReadConfig(bytes.NewReader(configStr)); err != nil {
|
||||
log.Fatalf("failed to read base64 config: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func processNoConfigFlags(noConfigStart bool) {
|
||||
if !noConfigStart {
|
||||
return
|
||||
}
|
||||
if listenAddress == "" || primaryUpstream == "" {
|
||||
log.Fatal(`"listen" and "primary_upstream" flags must be set in no config mode`)
|
||||
}
|
||||
host, portStr, err := net.SplitHostPort(listenAddress)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid listener address: %v", err)
|
||||
}
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid port number: %v", err)
|
||||
}
|
||||
lc := &ctrld.ListenerConfig{
|
||||
IP: host,
|
||||
Port: port,
|
||||
}
|
||||
v.Set("listener", map[string]*ctrld.ListenerConfig{
|
||||
"0": lc,
|
||||
})
|
||||
|
||||
upstream := map[string]*ctrld.UpstreamConfig{
|
||||
"0": {
|
||||
Name: primaryUpstream,
|
||||
Endpoint: primaryUpstream,
|
||||
Type: ctrld.ResolverTypeDOH,
|
||||
},
|
||||
}
|
||||
if secondaryUpstream != "" {
|
||||
upstream["1"] = &ctrld.UpstreamConfig{
|
||||
Name: secondaryUpstream,
|
||||
Endpoint: secondaryUpstream,
|
||||
Type: ctrld.ResolverTypeLegacy,
|
||||
}
|
||||
rules := make([]ctrld.Rule, 0, len(domains))
|
||||
for _, domain := range domains {
|
||||
rules = append(rules, ctrld.Rule{domain: []string{"upstream.1"}})
|
||||
}
|
||||
lc.Policy = &ctrld.ListenerPolicyConfig{Name: "My Policy", Rules: rules}
|
||||
}
|
||||
v.Set("upstream", upstream)
|
||||
|
||||
if logPath != "" {
|
||||
v.Set("service", ctrld.ServiceConfig{LogLevel: "debug", LogPath: logPath})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,16 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
configPath string
|
||||
daemon bool
|
||||
cfg ctrld.Config
|
||||
verbose int
|
||||
configPath string
|
||||
configBase64 string
|
||||
daemon bool
|
||||
listenAddress string
|
||||
primaryUpstream string
|
||||
secondaryUpstream string
|
||||
domains []string
|
||||
logPath string
|
||||
cfg ctrld.Config
|
||||
verbose int
|
||||
|
||||
bootstrapDNS = "76.76.2.0"
|
||||
|
||||
|
||||
14
config.go
14
config.go
@@ -40,14 +40,14 @@ func InitConfig(v *viper.Viper, name string) {
|
||||
"0": {
|
||||
BootstrapIP: "76.76.2.11",
|
||||
Name: "Control D - Anti-Malware",
|
||||
Type: "doh",
|
||||
Type: ResolverTypeDOH,
|
||||
Endpoint: "https://freedns.controld.com/p1",
|
||||
Timeout: 5000,
|
||||
},
|
||||
"1": {
|
||||
BootstrapIP: "76.76.2.11",
|
||||
Name: "Control D - No Ads",
|
||||
Type: "doq",
|
||||
Type: ResolverTypeDOQ,
|
||||
Endpoint: "p2.freedns.controld.com",
|
||||
Timeout: 3000,
|
||||
},
|
||||
@@ -139,9 +139,9 @@ func (uc *UpstreamConfig) Init() {
|
||||
// For now, only DoH upstream is supported.
|
||||
func (uc *UpstreamConfig) SetupTransport() {
|
||||
switch uc.Type {
|
||||
case resolverTypeDOH:
|
||||
case ResolverTypeDOH:
|
||||
uc.setupDOHTransport()
|
||||
case resolverTypeDOH3:
|
||||
case ResolverTypeDOH3:
|
||||
uc.setupDOH3Transport()
|
||||
}
|
||||
}
|
||||
@@ -231,11 +231,11 @@ func validateDnsRcode(fl validator.FieldLevel) bool {
|
||||
|
||||
func defaultPortFor(typ string) string {
|
||||
switch typ {
|
||||
case resolverTypeDOH, resolverTypeDOH3:
|
||||
case ResolverTypeDOH, ResolverTypeDOH3:
|
||||
return "443"
|
||||
case resolverTypeDOQ, resolverTypeDOT:
|
||||
case ResolverTypeDOQ, ResolverTypeDOT:
|
||||
return "853"
|
||||
case resolverTypeLegacy:
|
||||
case ResolverTypeLegacy:
|
||||
return "53"
|
||||
}
|
||||
return "53"
|
||||
|
||||
79
docs/basic_mode.md
Normal file
79
docs/basic_mode.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# basic mode
|
||||
|
||||
`ctrld` can operate in `basic` mode, which requires no configuration file. All necessary information is provided
|
||||
via command line flags, and be translated to corresponding config. `ctrld` will start with that config but do not
|
||||
write anything to disk.
|
||||
|
||||
## Base64 encoded config
|
||||
|
||||
`ctrld` can read a base64 encoded config via command line flag:
|
||||
|
||||
```shell
|
||||
ctrld run --base64_config="CltsaXN0ZW5lcl0KCiAgW2xpc3RlbmVyLjBdCiAgICBpcCA9ICIxMjcuMC4wLjEiCiAgICBwb3J0ID0gNTMKICAgIHJlc3RyaWN0ZWQgPSBmYWxzZQoKW25ldHdvcmtdCgogIFtuZXR3b3JrLjBdCiAgICBjaWRycyA9IFsiMC4wLjAuMC8wIl0KICAgIG5hbWUgPSAiTmV0d29yayAwIgoKW3Vwc3RyZWFtXQoKICBbdXBzdHJlYW0uMF0KICAgIGJvb3RzdHJhcF9pcCA9ICI3Ni43Ni4yLjExIgogICAgZW5kcG9pbnQgPSAiaHR0cHM6Ly9mcmVlZG5zLmNvbnRyb2xkLmNvbS9wMSIKICAgIG5hbWUgPSAiQ29udHJvbCBEIC0gQW50aS1NYWx3YXJlIgogICAgdGltZW91dCA9IDUwMDAKICAgIHR5cGUgPSAiZG9oIgoKICBbdXBzdHJlYW0uMV0KICAgIGJvb3RzdHJhcF9pcCA9ICI3Ni43Ni4yLjExIgogICAgZW5kcG9pbnQgPSAicDIuZnJlZWRucy5jb250cm9sZC5jb20iCiAgICBuYW1lID0gIkNvbnRyb2wgRCAtIE5vIEFkcyIKICAgIHRpbWVvdXQgPSAzMDAwCiAgICB0eXBlID0gImRvcSIK"
|
||||
```
|
||||
|
||||
## Launch arguments
|
||||
|
||||
A set of arguments can be provided via command line flags.
|
||||
|
||||
```shell
|
||||
$ ctrld run --help
|
||||
Run the DNS proxy server
|
||||
|
||||
Usage:
|
||||
ctrld run [flags]
|
||||
|
||||
Flags:
|
||||
--base64_config string base64 encoded config
|
||||
-c, --config string Path to config file
|
||||
-d, --daemon Run as daemon
|
||||
--domains strings list of domain to apply in a split DNS policy
|
||||
-h, --help help for run
|
||||
--listen string listener address and port, in format: address:port
|
||||
--log string path to log file
|
||||
--primary_upstream string primary upstream endpoint
|
||||
--secondary_upstream string secondary upstream endpoint
|
||||
|
||||
Global Flags:
|
||||
-v, --verbose count verbose log output, "-v" means query logging enabled, "-vv" means debug level logging enabled
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```shell
|
||||
ctrld run --listen=127.0.0.1:53 --primary_upstream=https://freedns.controld.com/p2 --secondary_upstream=8.8.8.8:53 --domains=*.company.int,*.net --log /path/to/log.log
|
||||
```
|
||||
|
||||
Above command will be translated roughly to this config:
|
||||
|
||||
```toml
|
||||
[service]
|
||||
log_level = "debug"
|
||||
log_path = "/path/to/log.log"
|
||||
|
||||
[network.0]
|
||||
name = "Network 0"
|
||||
cidrs = ["0.0.0.0/0"]
|
||||
|
||||
[upstream.0]
|
||||
name = "https://freedns.controld.com/p2"
|
||||
endpoint = "https://freedns.controld.com/p2"
|
||||
type = "doh"
|
||||
|
||||
[upstream.1]
|
||||
name = "8.8.8.8:53"
|
||||
endpoint = "8.8.8.8:53"
|
||||
type = "legacy"
|
||||
|
||||
[listener.0]
|
||||
ip = "127.0.0.1"
|
||||
port = 53
|
||||
|
||||
[listener.0.policy]
|
||||
rules = [
|
||||
{"*.company.int" = ["upstream.1"]},
|
||||
{"*.net" = ["upstream.1"]},
|
||||
]
|
||||
```
|
||||
|
||||
`secondary_upstream`, `domains`, and `log` flags are optional.
|
||||
2
doh.go
2
doh.go
@@ -14,7 +14,7 @@ import (
|
||||
func newDohResolver(uc *UpstreamConfig) *dohResolver {
|
||||
r := &dohResolver{
|
||||
endpoint: uc.Endpoint,
|
||||
isDoH3: uc.Type == resolverTypeDOH3,
|
||||
isDoH3: uc.Type == ResolverTypeDOH3,
|
||||
transport: uc.transport,
|
||||
http3RoundTripper: uc.http3RoundTripper,
|
||||
}
|
||||
|
||||
22
resolver.go
22
resolver.go
@@ -11,12 +11,12 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
resolverTypeDOH = "doh"
|
||||
resolverTypeDOH3 = "doh3"
|
||||
resolverTypeDOT = "dot"
|
||||
resolverTypeDOQ = "doq"
|
||||
resolverTypeOS = "os"
|
||||
resolverTypeLegacy = "legacy"
|
||||
ResolverTypeDOH = "doh"
|
||||
ResolverTypeDOH3 = "doh3"
|
||||
ResolverTypeDOT = "dot"
|
||||
ResolverTypeDOQ = "doq"
|
||||
ResolverTypeOS = "os"
|
||||
ResolverTypeLegacy = "legacy"
|
||||
)
|
||||
|
||||
var bootstrapDNS = "76.76.2.0"
|
||||
@@ -34,15 +34,15 @@ var errUnknownResolver = errors.New("unknown resolver")
|
||||
func NewResolver(uc *UpstreamConfig) (Resolver, error) {
|
||||
typ, endpoint := uc.Type, uc.Endpoint
|
||||
switch typ {
|
||||
case resolverTypeDOH, resolverTypeDOH3:
|
||||
case ResolverTypeDOH, ResolverTypeDOH3:
|
||||
return newDohResolver(uc), nil
|
||||
case resolverTypeDOT:
|
||||
case ResolverTypeDOT:
|
||||
return &dotResolver{uc: uc}, nil
|
||||
case resolverTypeDOQ:
|
||||
case ResolverTypeDOQ:
|
||||
return &doqResolver{uc: uc}, nil
|
||||
case resolverTypeOS:
|
||||
case ResolverTypeOS:
|
||||
return &osResolver{}, nil
|
||||
case resolverTypeLegacy:
|
||||
case ResolverTypeLegacy:
|
||||
return &legacyResolver{endpoint: endpoint}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %s", errUnknownResolver, typ)
|
||||
|
||||
Reference in New Issue
Block a user