Compare commits

...

6 Commits

10 changed files with 239 additions and 11 deletions

1
.gitignore vendored Normal file

@ -0,0 +1 @@
.idea/

104
README.md Normal file

@ -0,0 +1,104 @@
# ifupdown
[![GoDoc](https://godoc.org/git.tcp.direct/kayos/ifupdown?status.svg)](https://godoc.org/git.tcp.direct/kayos/ifupdown)
golang library for working with network interfaces defined via [ifupdown](https://manpages.debian.org/jessie/ifupdown/interfaces.5.en.html).
commonly recognized as `/etc/network/interfaces`.
## library
### features
- [x] read+parse interfaces file
- [x] write interfaces file
- [x] validate interfaces file (basic)
- [ ] validate interfaces file (thorough)
- [x] translate interfaces file to JSON
- [x] translate JSON to interfaces file
## cmd
- `ifup2json` - translate interfaces file to JSON
- `json2ifup` - translate JSON to interfaces file
### example usage
<details>
<summary>ifup2json</summary>
`cat /etc/network/interfaces | ifup2json`
```json
{
"eth2": {
"name": "eth2",
"auto": true,
"address": "192.168.69.5",
"netmask": "////AA==",
"gateway": "192.168.69.1",
"config": 3,
"version": 1,
"hooks": {
"pre_up": [
"echo yeet"
],
"post_down": [
"echo yeeted"
]
}
},
"lo": {
"name": "lo",
"auto": true,
"config": 1,
"version": 1,
"hooks": {}
},
"ns3": {
"name": "ns3",
"auto": true,
"address": "10.9.0.6",
"netmask": "////AAAAAAAAAAAAAAAAAA==",
"config": 3,
"version": 1,
"hooks": {
"pre_up": [
"ip link add dev ns3 type wireguard"
],
"post_up": [
"wg setconf ns3 /etc/wireguard/ns3.conf"
]
}
}
}
```
</details>
<details>
<summary>json2ifup</summary>
`cat /etc/network/interfaces | ifup2json | json2ifup`
```
auto eth2
iface eth2 inet static
address 192.168.69.5
netmask 255.255.255.0
gateway 192.168.69.1
pre-up echo yeet
post-down echo yeeted
auto lo
iface lo inet loopback
auto ns3
iface ns3 inet static
address 10.9.0.6
netmask 255.255.255.0
pre-up ip link add dev ns3 type wireguard
post-up wg setconf ns3 /etc/wireguard/ns3.conf
```
</details>

68
cmd/json2ifup/main.go Normal file

@ -0,0 +1,68 @@
package main
import (
"bytes"
"encoding/json"
"errors"
"io"
"os"
iface "git.tcp.direct/kayos/ifupdown"
)
func main() {
var ifaces = make(iface.Interfaces)
buf := &bytes.Buffer{}
switch {
case len(os.Args) < 2:
var empty = 0
for {
n, err := buf.ReadFrom(os.Stdin)
if errors.Is(err, io.EOF) {
break
}
if err != nil {
panic(err)
}
if n == 0 {
empty++
}
if empty > 100 {
break
}
}
default:
f, err := os.Open(os.Args[1])
if err != nil {
println(err.Error())
return
}
_, _ = buf.ReadFrom(f)
}
err := json.Unmarshal(buf.Bytes(), &ifaces)
if err != nil {
println(err.Error())
// println("input received: " + string(buf.Bytes()))
return
}
for name, netif := range ifaces {
if netif == nil {
delete(ifaces, name)
continue
}
if netif.Name != name {
panic("name mismatch")
}
if err = netif.Validate(); err != nil {
println(name + ": skip due to error: " + err.Error())
delete(ifaces, name)
continue
}
}
if _, err = os.Stdout.WriteString(ifaces.String()); err != nil {
panic(err)
}
}

@ -1,4 +1,4 @@
package iface
package ifupdown
import "errors"

@ -1,8 +1,11 @@
package iface
package ifupdown
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"io"
"strings"
"sync"
@ -11,12 +14,43 @@ import (
type Interfaces map[string]*NetworkInterface
type poolGroup struct {
Buffers pool.BufferFactory
Strs pool.StringFactory
func (i Interfaces) buf() *pool.Buffer {
buf := pools.Buffers.Get()
for _, iface := range i {
err := iface.write(func(s string) { buf.MustWrite([]byte(s)) })
if err != nil && !errors.Is(err, io.EOF) {
panic(err)
}
buf.MustWrite([]byte("\n"))
}
return buf
}
var pools = poolGroup{Buffers: pool.NewBufferFactory(), Strs: pool.NewStringFactory()}
func (i Interfaces) Read(p []byte) (int, error) {
buf := i.buf()
defer pools.Buffers.MustPut(buf)
return buf.Read(p)
}
func (i Interfaces) String() string {
buf := i.buf()
defer pools.Buffers.MustPut(buf)
return buf.String()
}
func (i Interfaces) UnmarshalJSON(data []byte) error {
var ifaces map[string]*NetworkInterface
if err := json.Unmarshal(data, &ifaces); err != nil {
return err
}
for name, iface := range ifaces {
iface.Name = name
iface.allocated = true
i[name] = iface
}
return nil
}
type MultiParser struct {
Interfaces map[string]*NetworkInterface

@ -1,4 +1,4 @@
package iface
package ifupdown
import (
"testing"
@ -19,7 +19,8 @@ iface eth0 inet dhcp
t.Fatalf("Write failed: %v", err)
}
err = mp.Parse()
var ifaces Interfaces
ifaces, err = mp.Parse()
if err != nil {
t.Fatalf("Expected nil error, got: %v", err)
}
@ -28,6 +29,10 @@ iface eth0 inet dhcp
t.Fatalf("Expected 2 interfaces, got: %d", len(mp.Interfaces))
}
if len(mp.Interfaces) != len(ifaces) {
t.Fatalf("Expected %d interfaces, got: %d", len(mp.Interfaces), len(ifaces))
}
loIface, ok := mp.Interfaces["lo"]
if !ok || loIface.Name != "lo" {
t.Fatalf("Expected to find interface 'lo', got: %+v", loIface)
@ -37,6 +42,11 @@ iface eth0 inet dhcp
if !ok || eth0Iface.Name != "eth0" {
t.Fatalf("Expected to find interface 'eth0', got: %+v", eth0Iface)
}
for _, iface := range ifaces {
if err = iface.Validate(); err != nil {
t.Fatalf("Expected nil error, got: %v", err)
}
}
}
// Add more test functions here to check other aspects and edge cases.

@ -1,4 +1,4 @@
package iface
package ifupdown
import (
"bufio"
@ -110,9 +110,10 @@ func (iface *NetworkInterface) allocate() {
if iface.RWMutex == nil {
iface.RWMutex = &sync.RWMutex{}
}
iface.Lock()
iface.Lock() // gets unlocked in other methods
iface.dirty = true
iface.allocated = true
}
func (iface *NetworkInterface) err() error {

@ -1,4 +1,4 @@
package iface
package ifupdown
import (
"bufio"

10
util.go Normal file

@ -0,0 +1,10 @@
package ifupdown
import "git.tcp.direct/kayos/common/pool"
type poolGroup struct {
Buffers pool.BufferFactory
Strs pool.StringFactory
}
var pools = poolGroup{Buffers: pool.NewBufferFactory(), Strs: pool.NewStringFactory()}