Skip to content

Commit bbc174e

Browse files
feat: add dns redirection (#105)
* feat: add dns redirection * add debug logss * comment out debug logs * initial fix for race condtion * refactor race condition fix * add logging * add 2nd redirection rule * add real 2nd & 3rd redirection rule * remove 2nd redirection rule(host) * remove commented out code * improve docs * minor fix * minor fix * improve docs * improve docs * improve docs * refactor * add boolean flag for stub resolved * add boolean flag for stub resolved * refactor * gofmt * refactor * refactor
1 parent 1fab11a commit bbc174e

File tree

6 files changed

+116
-43
lines changed

6 files changed

+116
-43
lines changed

app/app.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@ import (
1515

1616
// Config holds all configuration for the CLI
1717
type Config struct {
18-
Config serpent.YAMLConfigPath `yaml:"-"`
19-
AllowListStrings serpent.StringArray `yaml:"allowlist"` // From config file
20-
AllowStrings serpent.StringArray `yaml:"-"` // From CLI flags only
21-
LogLevel serpent.String `yaml:"log_level"`
22-
LogDir serpent.String `yaml:"log_dir"`
23-
ProxyPort serpent.Int64 `yaml:"proxy_port"`
24-
PprofEnabled serpent.Bool `yaml:"pprof_enabled"`
25-
PprofPort serpent.Int64 `yaml:"pprof_port"`
18+
Config serpent.YAMLConfigPath `yaml:"-"`
19+
AllowListStrings serpent.StringArray `yaml:"allowlist"` // From config file
20+
AllowStrings serpent.StringArray `yaml:"-"` // From CLI flags only
21+
LogLevel serpent.String `yaml:"log_level"`
22+
LogDir serpent.String `yaml:"log_dir"`
23+
ProxyPort serpent.Int64 `yaml:"proxy_port"`
24+
PprofEnabled serpent.Bool `yaml:"pprof_enabled"`
25+
PprofPort serpent.Int64 `yaml:"pprof_port"`
26+
ConfigureDNSForLocalStubResolver serpent.Bool `yaml:"configure_dns_for_local_stub_resolver"`
2627
}
2728

2829
func isChild() bool {

app/child.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ func RunChild(logger *slog.Logger, args []string) error {
6767
}
6868
logger.Info("child networking is successfully configured")
6969

70+
if os.Getenv("CONFIGURE_DNS_FOR_LOCAL_STUB_RESOLVER") == "true" {
71+
err = jail.ConfigureDNSForLocalStubResolver()
72+
if err != nil {
73+
return fmt.Errorf("failed to configure DNS in namespace: %v", err)
74+
}
75+
logger.Info("DNS in namespace is configured successfully")
76+
}
77+
7078
// Program to run
7179
bin := args[0]
7280
args = args[1:]

app/parent.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,15 @@ func RunParent(ctx context.Context, logger *slog.Logger, args []string, config C
7272

7373
// Create jailer with cert path from TLS setup
7474
jailer, err := jail.NewLinuxJail(jail.Config{
75-
Logger: logger,
76-
HttpProxyPort: int(config.ProxyPort.Value()),
77-
Username: username,
78-
Uid: uid,
79-
Gid: gid,
80-
HomeDir: homeDir,
81-
ConfigDir: configDir,
82-
CACertPath: caCertPath,
75+
Logger: logger,
76+
HttpProxyPort: int(config.ProxyPort.Value()),
77+
Username: username,
78+
Uid: uid,
79+
Gid: gid,
80+
HomeDir: homeDir,
81+
ConfigDir: configDir,
82+
CACertPath: caCertPath,
83+
ConfigureDNSForLocalStubResolver: config.ConfigureDNSForLocalStubResolver.Value(),
8384
})
8485
if err != nil {
8586
return fmt.Errorf("failed to create jailer: %v", err)

cli/cli.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@ func BaseCommand() *serpent.Command {
108108
Value: &config.PprofPort,
109109
YAML: "pprof_port",
110110
},
111+
{
112+
Flag: "configure-dns-for-local-stub-resolver",
113+
Env: "BOUNDARY_CONFIGURE_DNS_FOR_LOCAL_STUB_RESOLVER",
114+
Description: "Configure DNS for local stub resolver (e.g., systemd-resolved). Only needed when /etc/resolv.conf contains nameserver 127.0.0.53.",
115+
Value: &config.ConfigureDNSForLocalStubResolver,
116+
YAML: "configure_dns_for_local_stub_resolver",
117+
},
111118
},
112119
Handler: func(inv *serpent.Invocation) error {
113120
args := inv.Args

jail/jail.go

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,41 +18,44 @@ type Jailer interface {
1818
}
1919

2020
type Config struct {
21-
Logger *slog.Logger
22-
HttpProxyPort int
23-
Username string
24-
Uid int
25-
Gid int
26-
HomeDir string
27-
ConfigDir string
28-
CACertPath string
21+
Logger *slog.Logger
22+
HttpProxyPort int
23+
Username string
24+
Uid int
25+
Gid int
26+
HomeDir string
27+
ConfigDir string
28+
CACertPath string
29+
ConfigureDNSForLocalStubResolver bool
2930
}
3031

3132
// LinuxJail implements Jailer using Linux network namespaces
3233
type LinuxJail struct {
33-
logger *slog.Logger
34-
vethHostName string // Host-side veth interface name for iptables rules
35-
vethJailName string // Jail-side veth interface name for iptables rules
36-
commandEnv []string
37-
httpProxyPort int
38-
configDir string
39-
caCertPath string
40-
homeDir string
41-
username string
42-
uid int
43-
gid int
34+
logger *slog.Logger
35+
vethHostName string // Host-side veth interface name for iptables rules
36+
vethJailName string // Jail-side veth interface name for iptables rules
37+
commandEnv []string
38+
httpProxyPort int
39+
configDir string
40+
caCertPath string
41+
homeDir string
42+
username string
43+
uid int
44+
gid int
45+
configureDNSForLocalStubResolver bool
4446
}
4547

4648
func NewLinuxJail(config Config) (*LinuxJail, error) {
4749
return &LinuxJail{
48-
logger: config.Logger,
49-
httpProxyPort: config.HttpProxyPort,
50-
configDir: config.ConfigDir,
51-
caCertPath: config.CACertPath,
52-
homeDir: config.HomeDir,
53-
username: config.Username,
54-
uid: config.Uid,
55-
gid: config.Gid,
50+
logger: config.Logger,
51+
httpProxyPort: config.HttpProxyPort,
52+
configDir: config.ConfigDir,
53+
caCertPath: config.CACertPath,
54+
homeDir: config.HomeDir,
55+
username: config.Username,
56+
uid: config.Uid,
57+
gid: config.Gid,
58+
configureDNSForLocalStubResolver: config.ConfigureDNSForLocalStubResolver,
5659
}, nil
5760
}
5861

@@ -81,6 +84,9 @@ func (l *LinuxJail) Command(command []string) *exec.Cmd {
8184
cmd.Env = l.commandEnv
8285
cmd.Env = append(cmd.Env, "CHILD=true")
8386
cmd.Env = append(cmd.Env, fmt.Sprintf("VETH_JAIL_NAME=%v", l.vethJailName))
87+
if l.configureDNSForLocalStubResolver {
88+
cmd.Env = append(cmd.Env, "CONFIGURE_DNS_FOR_LOCAL_STUB_RESOLVER=true")
89+
}
8490
cmd.Stderr = os.Stderr
8591
cmd.Stdout = os.Stdout
8692
cmd.Stdin = os.Stdin

jail/local_stub_resolver.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package jail
2+
3+
import (
4+
"os/exec"
5+
6+
"golang.org/x/sys/unix"
7+
)
8+
9+
// ConfigureDNSForLocalStubResolver configures DNS redirection from the network namespace
10+
// to the host's local stub resolver. This function should only be called when the host
11+
// runs a local stub resolver such as systemd-resolved, and /etc/resolv.conf contains
12+
// "nameserver 127.0.0.53" (listening on localhost). It redirects DNS requests from the
13+
// namespace to the host by setting up iptables NAT rules. Additionally, /etc/systemd/resolved.conf
14+
// should be configured with DNSStubListener=yes and DNSStubListenerExtra=192.168.100.1:53
15+
// to listen on the additional server address.
16+
// NOTE: it's called inside network namespace.
17+
func ConfigureDNSForLocalStubResolver() error {
18+
runner := newCommandRunner([]*command{
19+
// Redirect all DNS queries inside the namespace to the host DNS listener.
20+
// Needed because systemd-resolved listens on a host-side IP, not inside the namespace.
21+
{
22+
"Redirect DNS queries (DNAT 53 → host DNS)",
23+
exec.Command("iptables", "-t", "nat", "-A", "OUTPUT", "-p", "udp", "--dport", "53", "-j", "DNAT", "--to-destination", "192.168.100.1:53"),
24+
[]uintptr{uintptr(unix.CAP_NET_ADMIN)},
25+
},
26+
// Rewrite the SOURCE IP of redirected DNS packets.
27+
// Required because DNS queries originating as 127.0.0.1 inside the namespace
28+
// must not leave the namespace with a loopback source (kernel drops them).
29+
// SNAT ensures packets arrive at systemd-resolved with a valid, routable source.
30+
{
31+
"Fix DNS source IP (SNAT 127.0.0.x → 192.168.100.2)",
32+
exec.Command("iptables", "-t", "nat", "-A", "POSTROUTING", "-p", "udp", "--dport", "53", "-d", "192.168.100.1", "-j", "SNAT", "--to-source", "192.168.100.2"),
33+
[]uintptr{uintptr(unix.CAP_NET_ADMIN)},
34+
},
35+
// Allow packets destined for 127.0.0.0/8 to go through routing and NAT.
36+
// Without this, DNS queries to 127.0.0.53 never hit iptables OUTPUT
37+
// and cannot be redirected to the host.
38+
{
39+
"Allow loopback-destined traffic to pass through NAT (route_localnet)",
40+
// TODO(yevhenii): consider replacing with specific interfaces instead of all
41+
exec.Command("sysctl", "-w", "net.ipv4.conf.all.route_localnet=1"),
42+
[]uintptr{uintptr(unix.CAP_NET_ADMIN)},
43+
},
44+
})
45+
if err := runner.run(); err != nil {
46+
return err
47+
}
48+
49+
return nil
50+
}

0 commit comments

Comments
 (0)