From d7e0591576fb8d354413d0f6c9ad89a893880e16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20B=C3=B6hmke?= Date: Fri, 29 Nov 2019 19:53:17 +0100 Subject: [PATCH] added network config --- .gitignore | 3 +- go.mod | 5 ++ go.sum | 13 ++++ network.go | 171 +++++++++++++++++++++++++++++++++++++++++++++ network_test.go | 116 ++++++++++++++++++++++++++++++ resources/build.sh | 6 +- 6 files changed, 310 insertions(+), 4 deletions(-) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 network.go create mode 100644 network_test.go diff --git a/.gitignore b/.gitignore index 62c8935..ec4ecb3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.idea/ \ No newline at end of file +.idea/ +test_* \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7c61013 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module gitlab.com/bboehmke/raspi-alpine-builder + +go 1.13 + +require github.com/stretchr/testify v1.4.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a65f495 --- /dev/null +++ b/go.sum @@ -0,0 +1,13 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= +github.com/kataras/iris v11.1.1+incompatible/go.mod h1:ki9XPua5SyAJbIxDdsssxevgGrbpBmmvoQmo/A0IodY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/network.go b/network.go new file mode 100644 index 0000000..5553f33 --- /dev/null +++ b/network.go @@ -0,0 +1,171 @@ +package alpine_builder + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io/ioutil" + "os" + "regexp" + "strings" +) + +// network configuration +var networkConfig = "/data/etc/network/interfaces" +var networkInterface = "eth0" + +// regular expressions +var reSectionStart = regexp.MustCompile("^(iface|mapping|auto|allow-|source)") +var reIface = regexp.MustCompile("^iface (\\w+) inet (\\w+)") +var reStaticAddress = regexp.MustCompile("^(\\s*)address ([0-9.]+)") +var reStaticNetmask = regexp.MustCompile("^(\\s*)netmask ([0-9.]+)") +var reStaticGateway = regexp.MustCompile("^(\\s*)gateway ([0-9.]+)") + +// NetworkInfo represents the actual network configuration +type NetworkInfo struct { + IsStatic bool + Address string + Netmask string + Gateway string +} + +// GetNetworkInfo from config file +func GetNetworkInfo() (*NetworkInfo, error) { + // load config file + file, err := os.Open(networkConfig) + if err != nil { + return nil, fmt.Errorf("failed to open network config: %w", err) + } + defer file.Close() + + var info *NetworkInfo + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + + // check for section start + if reSectionStart.MatchString(line) { + infLine := reIface.FindStringSubmatch(line) + if infLine != nil { + // interface found + if infLine[1] == networkInterface { + info = &NetworkInfo{ + IsStatic: infLine[2] == "static", + } + } + } else { + // info set -> stop here + if info != nil { + break + } + } + + } else if info != nil { + // interface config + address := reStaticAddress.FindStringSubmatch(line) + if address != nil { + info.Address = address[2] + } + netmask := reStaticNetmask.FindStringSubmatch(line) + if netmask != nil { + info.Netmask = netmask[2] + } + gateway := reStaticGateway.FindStringSubmatch(line) + if gateway != nil { + info.Gateway = gateway[2] + } + } + } + if info != nil { + return info, nil + } + return nil, errors.New("invalid network config") +} + +// NetworkEnableDHCP configures the network to use DHCP client +func NetworkEnableDHCP() error { + // load config file + file, err := os.Open(networkConfig) + if err != nil { + return fmt.Errorf("failed to open network config: %w", err) + } + defer file.Close() + + var buffer bytes.Buffer + var infFound bool + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + + // check for section start + if reSectionStart.MatchString(line) { + infLine := reIface.FindStringSubmatch(line) + if infLine != nil { + // interface found + if infLine[1] == networkInterface { + infFound = true + buffer.WriteString(fmt.Sprintf("iface %s inet dhcp\n", networkInterface)) + continue + } + } else { + if infFound { + infFound = false + } + } + buffer.WriteString(line + "\n") + + } else if !infFound { + buffer.WriteString(line + "\n") + } + } + + // write config file + return ioutil.WriteFile(networkConfig, buffer.Bytes(), os.ModePerm) +} + +// NetworkSetStatic IP configuration +func NetworkSetStatic(address, netmask, gateway string) error { + // load config file + file, err := os.Open(networkConfig) + if err != nil { + return fmt.Errorf("failed to open network config: %w", err) + } + defer file.Close() + + var buffer bytes.Buffer + var infFound bool + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + + // check for section start + if reSectionStart.MatchString(line) { + infLine := reIface.FindStringSubmatch(line) + if infLine != nil { + // interface found + if infLine[1] == networkInterface { + infFound = true + buffer.WriteString(fmt.Sprintf("iface %s inet static\n", networkInterface)) + buffer.WriteString(fmt.Sprintf(" address %s\n", address)) + buffer.WriteString(fmt.Sprintf(" netmask %s\n", netmask)) + buffer.WriteString(fmt.Sprintf(" gateway %s\n", gateway)) + continue + } + } else { + if infFound { + infFound = false + } + } + buffer.WriteString(line + "\n") + + } else if !infFound { + buffer.WriteString(line + "\n") + } + } + + // write config file + return ioutil.WriteFile(networkConfig, buffer.Bytes(), os.ModePerm) +} diff --git a/network_test.go b/network_test.go new file mode 100644 index 0000000..ade6384 --- /dev/null +++ b/network_test.go @@ -0,0 +1,116 @@ +package alpine_builder + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func init() { + networkConfig = "test_interfaces" +} + +func TestGetNetworkInfo(t *testing.T) { + ass := assert.New(t) + + // file does not exist + _, err := GetNetworkInfo() + ass.EqualError(err, "failed to open network config: open test_interfaces: no such file or directory") + + // invalid config file + ass.NoError(ioutil.WriteFile(networkConfig, []byte(""), os.ModePerm)) + _, err = GetNetworkInfo() + ass.EqualError(err, "invalid network config") + + // dynamic config + ass.NoError(ioutil.WriteFile(networkConfig, []byte(`auto lo +iface lo inet loopback + +auto eth0 +iface eth0 inet dhcp`), os.ModePerm)) + info, err := GetNetworkInfo() + ass.NoError(err) + ass.False(info.IsStatic) + + // static config + ass.NoError(ioutil.WriteFile(networkConfig, []byte(`auto lo +iface lo inet loopback + +auto eth0 +iface eth0 inet static + address 1.2.3.4 + netmask 255.255.255.0 + gateway 4.3.2.1`), os.ModePerm)) + info, err = GetNetworkInfo() + ass.NoError(err) + ass.True(info.IsStatic) + ass.Equal("1.2.3.4", info.Address) + ass.Equal("255.255.255.0", info.Netmask) + ass.Equal("4.3.2.1", info.Gateway) + + _ = os.Remove(networkConfig) +} + +func TestNetworkEnableDHCP(t *testing.T) { + ass := assert.New(t) + + // file does not exist + _, err := GetNetworkInfo() + ass.EqualError(err, "failed to open network config: open test_interfaces: no such file or directory") + + ass.NoError(ioutil.WriteFile(networkConfig, []byte(`auto lo +iface lo inet loopback + +auto eth0 +iface eth0 inet static + address 1.2.3.4 + netmask 255.255.255.0 + gateway 4.3.2.1`), os.ModePerm)) + + ass.NoError(NetworkEnableDHCP()) + + data, err := ioutil.ReadFile(networkConfig) + ass.NoError(err) + + ass.Equal(`auto lo +iface lo inet loopback + +auto eth0 +iface eth0 inet dhcp +`, string(data)) + + _ = os.Remove(networkConfig) +} + +func TestNetworkSetStatic(t *testing.T) { + ass := assert.New(t) + + // file does not exist + _, err := GetNetworkInfo() + ass.EqualError(err, "failed to open network config: open test_interfaces: no such file or directory") + + ass.NoError(ioutil.WriteFile(networkConfig, []byte(`auto lo +iface lo inet loopback + +auto eth0 +iface eth0 inet dhcp`), os.ModePerm)) + + ass.NoError(NetworkSetStatic("1.2.3.4", "255.255.255.0", "4.3.2.1")) + + data, err := ioutil.ReadFile(networkConfig) + ass.NoError(err) + + ass.Equal(`auto lo +iface lo inet loopback + +auto eth0 +iface eth0 inet static + address 1.2.3.4 + netmask 255.255.255.0 + gateway 4.3.2.1 +`, string(data)) + + _ = os.Remove(networkConfig) +} diff --git a/resources/build.sh b/resources/build.sh index 9a1ed67..1664dba 100755 --- a/resources/build.sh +++ b/resources/build.sh @@ -99,7 +99,7 @@ EOF # prepare network chroot_exec rc-update add networking default -ln -fs /data/etc/interfaces ${ROOTFS_PATH}/etc/network/interfaces +ln -fs /data/etc/network/interfaces ${ROOTFS_PATH}/etc/network/interfaces # run local before network -> local brings up the interface sed -i '/^\tneed/ s/$/ local/' ${ROOTFS_PATH}/etc/init.d/networking @@ -200,8 +200,8 @@ root_pw=\$(mkpasswd -m sha-512 -s "${DEFAULT_ROOT_PASSWORD}") echo "root:\${root_pw}:0:0:::::" > /data/etc/shadow # interface -if [ ! -f /data/etc/interfaces ]; then -cat > /data/etc/interfaces < /data/etc/network/interfaces <