Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Create missing target directories before bind mounting #162

Merged
merged 6 commits into from
May 20, 2024
Merged
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
6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 14 additions & 16 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,23 @@
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
};

outputs = {
self,
flake-utils,
nixpkgs,
}:
flake-utils.lib.eachDefaultSystem
(
system: let
outputs =
{
self,
flake-utils,
nixpkgs,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
in rec {
in
rec {
packages.gont = pkgs.buildGoModule {
name = "gont";
src = ./.;
vendorHash = "sha256-QOh1jBR7FL/fKFmJv7wGxuCghRLR3DV/0TzXd+bUFP0=";
buildInputs = with pkgs; [
libpcap
];
buildInputs = with pkgs; [ libpcap ];
doCheck = false;
};

Expand All @@ -38,12 +38,10 @@
packages.gont
];

inputsFrom = [
packages.gont
];
inputsFrom = [ packages.gont ];
};

formatter = nixpkgs.alejandra;
formatter = nixpkgs.nixfmt-rfc-style;
}
);
}
5 changes: 5 additions & 0 deletions internal/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ package utils
import (
"math/rand"
"os"
"path/filepath"
)

func Touch(path string) error {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}

f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL, 0o444)
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion pkg/base_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func (n *Network) AddNode(name string, opts ...Option) (*BaseNode, error) {
}
}

// Create mount point dirs
// Create mount point directories
for _, ed := range node.EmptyDirs {
path := filepath.Join(basePath, "files", ed)

Expand Down
16 changes: 16 additions & 0 deletions pkg/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

g "cunicu.li/gont/v2/pkg"
co "cunicu.li/gont/v2/pkg/options/cmd"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -152,3 +153,18 @@ func TestCmdContext(t *testing.T) {
require.True(t, ws.Signaled())
require.Equal(t, syscall.SIGKILL, ws.Signal())
}

func TestIProute2Files(t *testing.T) {
n, err := g.NewNetwork("")
require.NoError(t, err, "Failed to create network")
defer n.Close()

beep, err := n.AddHost("beep")
require.Nil(t, err)

cmd := beep.Command("ip", "addr")
out, err := cmd.CombinedOutput()
assert.Nil(t, err)

t.Logf("Output: %s", out)
}
28 changes: 28 additions & 0 deletions pkg/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
package gont

import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"syscall"

"cunicu.li/gont/v2/internal/execvpe"
"cunicu.li/gont/v2/internal/utils"
"golang.org/x/sys/unix"
)

Expand Down Expand Up @@ -120,6 +122,32 @@ func setupBindMounts(basePath string) error {
src := filepath.Join(filesRootPath, path)
tgt := filepath.Join("/", path)

srcInfo, err := os.Stat(src)
if err != nil {
return fmt.Errorf("failed to stat source: %s: %w", src, err)
}

// Create non-existing targets
if _, err := os.Stat(tgt); errors.Is(err, os.ErrNotExist) { //nolint:nestif
var tgtDir string
if srcInfo.IsDir() {
tgtDir = tgt
} else {
tgtDir = filepath.Dir(tgt)
}

// Create target if it does not exist yet
if srcInfo.IsDir() {
if err := os.MkdirAll(tgtDir, 0o755); err != nil {
return fmt.Errorf("failed to create directory: %s: %w", path, err)
}
} else {
if err := utils.Touch(tgt); err != nil {
return fmt.Errorf("failed to create empty file: %s: %w", tgt, err)
}
}
}

if err := syscall.Mount(src, tgt, "", syscall.MS_BIND, ""); err != nil {
return fmt.Errorf("failed to mount: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/nat_http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func TestGetMyIP(t *testing.T) {
require.NoError(t, err, "Failed to setup default route")

outp := &bytes.Buffer{}
_, err = client.Run("curl", "-sk", "--connect-timeout", 1000, "https://server",
_, err = client.Run("curl", "--silent", "--insecure", "--connect-timeout", 1000, "https://server",
co.Stdout(outp))
require.NoError(t, err, "Request failed")

Expand Down
103 changes: 101 additions & 2 deletions pkg/network_files.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
package gont

import (
"bufio"
"fmt"
"io"
"net"
"os"
"path/filepath"
"slices"
"strings"

"cunicu.li/gont/v2/internal/utils"
)

// IPv4loopback is the IPv4 loopback address (127.0.0.1)
Expand Down Expand Up @@ -90,10 +94,105 @@ func (n *Network) WriteHostsFile(f io.Writer) error {
}

func (n *Network) GenerateConfigFiles() error {
return n.GenerateIProute2Files()
if err := n.generateIProute2Files(); err != nil {
return err
}

// We need to patch /etc/nsswitch.conf here
// to avoid using systemd-resolved here as its
// broken from within network namesapces
if err := n.patchNSSConfFile(); err != nil {
return err
}

// We also need to hide the NSCD socket as glibc
// will otherwise use NSCD and hence again the resolve
// NSS module provided by systemd-resolved.
if err := n.hideNSCDSocket(); err != nil {
return err
}

return nil
}

func (n *Network) hideNSCDSocket() error {
fn := filepath.Join(n.VarPath, "files/var/run/nscd/socket")

// We hide the NSCD socket here by bind mounting
// an empty file over its location.
return utils.Touch(fn)
}

func readNSSwitchConfig(fn string) (map[string][]string, error) {
f, err := os.Open(fn)
if err != nil {
return nil, err
}

m := map[string][]string{}
s := bufio.NewScanner(f)

for s.Scan() {
line := s.Text()

if strings.HasPrefix(line, "#") {
continue // Skip comments
}

cols := strings.Split(line, ":")

db := cols[0]
srcs := cols[1:]

m[db] = srcs
}

if err := s.Err(); err != nil {
return nil, err
}

return nil, nil
}

func writeNSSwitchConfig(fn string, config map[string][]string) error {
f, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
return err
}
defer f.Close()

if _, err = fmt.Fprintln(f, "# Gont's patched nsswitch.conf"); err != nil {
return err
}

for db, srcs := range config {
if _, err := fmt.Fprintf(f, "%s: %s\n", db, strings.Join(srcs, " ")); err != nil {
return err
}
}

return err
}

func (n *Network) patchNSSConfFile() error {
cfg, err := readNSSwitchConfig("/etc/nsswitch.conf")
if err != nil {
return fmt.Errorf("failed to read nsswitch.conf: %w", err)
}

for db := range cfg {
if db == "hosts" {
cfg[db] = slices.DeleteFunc(cfg[db], func(src string) bool {
return !strings.HasPrefix(src, "resolve") && !strings.HasPrefix(src, "mymachines") && !strings.HasPrefix(src, "myhostname")
})
}
}

fn := filepath.Join(n.VarPath, "files/etc/nsswitch.conf")
return writeNSSwitchConfig(fn, cfg)
}

func (n *Network) GenerateIProute2Files() error {
func (n *Network) generateIProute2Files() error {
fn := filepath.Join(n.VarPath, "files/etc/iproute2/group")
if err := os.MkdirAll(filepath.Dir(fn), 0o755); err != nil {
return err
Expand Down
8 changes: 7 additions & 1 deletion pkg/options/cmd/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,17 @@ func (e Env) ApplyExecCmd(c *exec.Cmd) {
c.Env = append(c.Env, string(e))
}

// EnvVar appends a key-value paired environment variable
// EnvVar appends a key-value paired environment variable.
func EnvVar(k, v string) Env {
return Env(fmt.Sprintf("%s=%s", k, v))
}

// PassEnv forward environment variables from the main
// Gont process to the invoked sub-processes.
func PassEnv(k string) Env {
return EnvVar(k, os.Getenv(k))
}

// Envs appends additional environment variables.
type Envs []string

Expand Down