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
3 changes: 3 additions & 0 deletions .github/contributors.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,6 @@ users:
magic-peach:
name: Akanksha Trehun
email: akankshatrehun@gmail.com
alimx07:
name: Ali Mohamed
email: amx746@gmail.com
13 changes: 7 additions & 6 deletions pkg/unikontainers/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,13 @@ type VMM interface {
}

type NetDevParams struct {
IP string // The veth device IP
Mask string // The veth device mask
Gateway string // The veth device gateway
MAC string // The MAC address of the guest network device
TapDev string // The tap device name
MTU int // The MTU value of the tap device
IP string // The veth device IP
Mask string // The veth device mask
Gateway string // The veth device gateway
MAC string // The MAC address of the guest network device
TapDev string // The tap device name
MTU int // The MTU value of the tap device
DNSServer string // The nameserver of the container, empty if there is none
}

type BlockDevParams struct {
Expand Down
12 changes: 9 additions & 3 deletions pkg/unikontainers/unikernels/unikraft.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (
const UnikraftUnikernel string = "unikraft"
const UnikraftCompatVersion string = "0.16.1"

const defaultDNSServer string = "8.8.8.8"

var ErrUndefinedVersion = errors.New("version is undefined, using default version")
var ErrVersionParsing = errors.New("failed to parse provided version, using default version")

Expand Down Expand Up @@ -107,10 +109,14 @@ func (u *Unikraft) Init(data types.UnikernelParams) error {
u.Monitor = data.Monitor
u.Command = strings.Join(data.CmdLine, " ")

return u.configureUnikraftArgs(data.Rootfs.Type, data.Net.IP, data.Net.Gateway, data.Net.Mask)
return u.configureUnikraftArgs(data.Rootfs.Type, data.Net.IP, data.Net.Gateway, data.Net.Mask, data.Net.DNSServer)
}

func (u *Unikraft) configureUnikraftArgs(rootFsType, ethDeviceIP, ethDeviceGateway, ethDeviceMask string) error {
func (u *Unikraft) configureUnikraftArgs(rootFsType, ethDeviceIP, ethDeviceGateway, ethDeviceMask, dnsServer string) error {
if dnsServer == "" {
dnsServer = defaultDNSServer
}

setCompatArgs := func() {
u.Net.Address = "netdev.ipv4_addr=" + ethDeviceIP
u.Net.Gateway = "netdev.ipv4_gw_addr=" + ethDeviceGateway
Expand All @@ -125,7 +131,7 @@ func (u *Unikraft) configureUnikraftArgs(rootFsType, ethDeviceIP, ethDeviceGatew
}

setCurrentArgs := func() {
u.Net.Address = "netdev.ip=" + ethDeviceIP + "/24:" + ethDeviceGateway + ":8.8.8.8"
u.Net.Address = "netdev.ip=" + ethDeviceIP + "/24:" + ethDeviceGateway + ":" + dnsServer
switch rootFsType {
case "initrd":
// TODO: This needs better handling. We need to revisit this
Expand Down
5 changes: 3 additions & 2 deletions pkg/unikontainers/unikontainers.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ func (u *Unikontainer) SetRunningState() error {

// SetupNet creates the sandbox's network device (tap) in the current network
// namespace and returns its parameters; uid and gid own the tap device.
func SetupNet(networkType string, uid, gid uint32) (types.NetDevParams, error) {
func SetupNet(networkType string, mounts []specs.Mount, uid, gid uint32) (types.NetDevParams, error) {
uniklog.WithField("network type", networkType).Debug("Retrieved network type")
netArgs := types.NetDevParams{}
netManager, err := network.NewNetworkManager(networkType)
Expand All @@ -292,6 +292,7 @@ func SetupNet(networkType string, uid, gid uint32) (types.NetDevParams, error) {
// virtual ethernet interface inside the namespace
netArgs.MAC = networkInfo.EthDevice.MAC
netArgs.MTU = networkInfo.EthDevice.MTU
netArgs.DNSServer = getDNSServer(mounts)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to move this to InitisalSetup so it can also take effect in libcontainer. The monitorSpec struct can be updated to store the value of DNS so it can be then read from the urunc reexec and urunc monitor processes.

}

return netArgs, nil
Expand Down Expand Up @@ -618,7 +619,7 @@ func (u *Unikontainer) Exec(metrics m.Writer) error {
}

// handle network
netArgs, err := SetupNet(u.getNetworkType(), u.Spec.Process.User.UID, u.Spec.Process.User.GID)
netArgs, err := SetupNet(u.getNetworkType(), u.Spec.Mounts, u.Spec.Process.User.UID, u.Spec.Process.User.GID)
if err != nil {
uniklog.Errorf("failed to setup network: %v", err)
return err
Expand Down
33 changes: 33 additions & 0 deletions pkg/unikontainers/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"encoding/json"
"fmt"
"io"
"net"
"os"
"os/exec"
"path/filepath"
Expand Down Expand Up @@ -388,3 +389,35 @@ func executeHook(hook specs.Hook, state []byte) error {

return nil
}

func getDNSServer(mounts []specs.Mount) string {
resolvConf := ""
for _, mount := range mounts {
if filepath.Clean(mount.Destination) == "/etc/resolv.conf" {
resolvConf = mount.Source
break
}
}
if resolvConf == "" {
return ""
}

data, err := os.ReadFile(resolvConf)
if err != nil {
uniklog.Warnf("Failed to read %s: %v", resolvConf, err)
return ""
}

for _, line := range strings.Split(string(data), "\n") {
fields := strings.Fields(line)
if len(fields) < 2 || fields[0] != "nameserver" {
continue
}
addr := net.ParseIP(fields[1])
if addr != nil && addr.To4() != nil && !addr.IsLoopback() {
return addr.String()
}
}

return ""
}
77 changes: 77 additions & 0 deletions pkg/unikontainers/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,3 +340,80 @@ func TestLoadSpec(t *testing.T) {
assert.Contains(t, err.Error(), "failed to parse specification json", "Expected specific error message")
})
}

func TestGetDNSServer(t *testing.T) {
tests := []struct {
name string
content string
expected string
}{
{
name: "single nameserver",
content: "nameserver 10.96.0.10\n",
expected: "10.96.0.10",
},
{
name: "first nameserver is used",
content: "search svc.cluster.local\nnameserver 10.96.0.10\nnameserver 8.8.4.4\noptions ndots:5\n",
expected: "10.96.0.10",
},
{
name: "comments are ignored",
content: "# nameserver 1.1.1.1\n\n nameserver\t192.168.1.1 \n",
expected: "192.168.1.1",
},
{
name: "loopback nameserver is skipped",
content: "nameserver 127.0.0.11\nnameserver 1.1.1.1\n",
expected: "1.1.1.1",
},
{
name: "IPv6 nameserver is skipped",
content: "nameserver fd00::1\nnameserver 1.1.1.1\n",
expected: "1.1.1.1",
},
{
name: "invalid entries are skipped",
content: "nameserver\nnameserver not-an-ip\nnameserver 1.1.1.1\n",
expected: "1.1.1.1",
},
{
name: "no usable nameserver",
content: "search svc.cluster.local\nnameserver 127.0.0.53\n",
expected: "",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
resolvConf := filepath.Join(t.TempDir(), "resolv.conf")
err := os.WriteFile(resolvConf, []byte(tc.content), 0600)
assert.NoError(t, err)
mounts := []specs.Mount{
{Destination: "/etc/hostname", Source: "/dummy/hostname"},
{Destination: "/etc/resolv.conf", Source: resolvConf},
}

assert.Equal(t, tc.expected, getDNSServer(mounts))
})
}

t.Run("no resolv.conf mount", func(t *testing.T) {
t.Parallel()
mounts := []specs.Mount{
{Destination: "/etc/hostname", Source: "/dummy/hostname"},
}

assert.Equal(t, "", getDNSServer(mounts))
})

t.Run("missing resolv.conf file", func(t *testing.T) {
t.Parallel()
mounts := []specs.Mount{
{Destination: "/etc/resolv.conf", Source: filepath.Join(t.TempDir(), "resolv.conf")},
}

assert.Equal(t, "", getDNSServer(mounts))
})
}
17 changes: 17 additions & 0 deletions tests/e2e/test_cases.go
Original file line number Diff line number Diff line change
Expand Up @@ -1241,5 +1241,22 @@ func dockerTestCases() []containerTestArgs {
Skippable: false,
TestFunc: namespaceTest,
},
{
Image: "harbor.nbfc.io/nubificus/urunc/dns-test-qemu-unikraft-initrd:latest",
Name: "Qemu-unikraft-dns-external",
Devmapper: false,
Seccomp: true,
UID: 0,
GID: 0,
Groups: []int64{},
Memory: "",
Cli: "",
Volumes: []containerVolume{},
StaticNet: false,
SideContainers: []string{},
Skippable: true,
ExpectOut: "github.com OK",
TestFunc: matchTest,
},
}
}
Loading