mirror of
https://github.com/netbirdio/netbird.git
synced 2025-08-18 11:00:06 +02:00
8
client/iface/freebsd/errors.go
Normal file
8
client/iface/freebsd/errors.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package freebsd
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrDoesNotExist = errors.New("does not exist")
|
||||
ErrNameDoesNotMatch = errors.New("name does not match")
|
||||
)
|
108
client/iface/freebsd/iface.go
Normal file
108
client/iface/freebsd/iface.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package freebsd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type iface struct {
|
||||
Name string
|
||||
MTU int
|
||||
Group string
|
||||
IPAddrs []string
|
||||
}
|
||||
|
||||
func parseError(output []byte) error {
|
||||
// TODO: implement without allocations
|
||||
lines := string(output)
|
||||
|
||||
if strings.Contains(lines, "does not exist") {
|
||||
return ErrDoesNotExist
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseIfconfigOutput(output []byte) (*iface, error) {
|
||||
// TODO: implement without allocations
|
||||
lines := string(output)
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(lines))
|
||||
|
||||
var name, mtu, group string
|
||||
var ips []string
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
// If line contains ": flags", it's a line with interface information
|
||||
if strings.Contains(line, ": flags") {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 4 {
|
||||
return nil, fmt.Errorf("failed to parse line: %s", line)
|
||||
}
|
||||
name = strings.TrimSuffix(parts[0], ":")
|
||||
if strings.Contains(line, "mtu") {
|
||||
mtuIndex := 0
|
||||
for i, part := range parts {
|
||||
if part == "mtu" {
|
||||
mtuIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
mtu = parts[mtuIndex+1]
|
||||
}
|
||||
}
|
||||
|
||||
// If line contains "groups:", it's a line with interface group
|
||||
if strings.Contains(line, "groups:") {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 {
|
||||
return nil, fmt.Errorf("failed to parse line: %s", line)
|
||||
}
|
||||
group = parts[1]
|
||||
}
|
||||
|
||||
// If line contains "inet ", it's a line with IP address
|
||||
if strings.Contains(line, "inet ") {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 {
|
||||
return nil, fmt.Errorf("failed to parse line: %s", line)
|
||||
}
|
||||
ips = append(ips, parts[1])
|
||||
}
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
return nil, fmt.Errorf("interface name not found in ifconfig output")
|
||||
}
|
||||
|
||||
mtuInt, err := strconv.Atoi(mtu)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse MTU: %w", err)
|
||||
}
|
||||
|
||||
return &iface{
|
||||
Name: name,
|
||||
MTU: mtuInt,
|
||||
Group: group,
|
||||
IPAddrs: ips,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseIFName(output []byte) (string, error) {
|
||||
// TODO: implement without allocations
|
||||
lines := strings.Split(string(output), "\n")
|
||||
if len(lines) == 0 || lines[0] == "" {
|
||||
return "", fmt.Errorf("no output returned")
|
||||
}
|
||||
|
||||
fields := strings.Fields(lines[0])
|
||||
if len(fields) > 1 {
|
||||
return "", fmt.Errorf("invalid output")
|
||||
}
|
||||
|
||||
return fields[0], nil
|
||||
}
|
76
client/iface/freebsd/iface_internal_test.go
Normal file
76
client/iface/freebsd/iface_internal_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package freebsd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseIfconfigOutput(t *testing.T) {
|
||||
testOutput := `wg1: flags=8080<NOARP,MULTICAST> metric 0 mtu 1420
|
||||
options=80000<LINKSTATE>
|
||||
groups: wg
|
||||
nd6 options=109<PERFORMNUD,IFDISABLED,NO_DAD>`
|
||||
|
||||
expected := &iface{
|
||||
Name: "wg1",
|
||||
MTU: 1420,
|
||||
Group: "wg",
|
||||
}
|
||||
|
||||
result, err := parseIfconfigOutput(([]byte)(testOutput))
|
||||
if err != nil {
|
||||
t.Errorf("Error parsing ifconfig output: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, expected.Name, result.Name, "Name should match")
|
||||
assert.Equal(t, expected.MTU, result.MTU, "MTU should match")
|
||||
assert.Equal(t, expected.Group, result.Group, "Group should match")
|
||||
}
|
||||
|
||||
func TestParseIFName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
output string
|
||||
expected string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "ValidOutput",
|
||||
output: "eth0\n",
|
||||
expected: "eth0",
|
||||
},
|
||||
{
|
||||
name: "ValidOutputOneLine",
|
||||
output: "eth0",
|
||||
expected: "eth0",
|
||||
},
|
||||
{
|
||||
name: "EmptyOutput",
|
||||
output: "",
|
||||
expectedErr: fmt.Errorf("no output returned"),
|
||||
},
|
||||
{
|
||||
name: "InvalidOutput",
|
||||
output: "This is an invalid output\n",
|
||||
expectedErr: fmt.Errorf("invalid output"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result, err := parseIFName(([]byte)(test.output))
|
||||
|
||||
assert.Equal(t, test.expected, result, "Interface names should match")
|
||||
|
||||
if test.expectedErr != nil {
|
||||
assert.NotNil(t, err, "Error should not be nil")
|
||||
assert.EqualError(t, err, test.expectedErr.Error(), "Error messages should match")
|
||||
} else {
|
||||
assert.Nil(t, err, "Error should be nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
239
client/iface/freebsd/link.go
Normal file
239
client/iface/freebsd/link.go
Normal file
@@ -0,0 +1,239 @@
|
||||
package freebsd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const wgIFGroup = "wg"
|
||||
|
||||
// Link represents a network interface.
|
||||
type Link struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func NewLink(name string) *Link {
|
||||
return &Link{
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// LinkByName retrieves a network interface by its name.
|
||||
func LinkByName(name string) (*Link, error) {
|
||||
out, err := exec.Command("ifconfig", name).CombinedOutput()
|
||||
if err != nil {
|
||||
if pErr := parseError(out); pErr != nil {
|
||||
return nil, pErr
|
||||
}
|
||||
|
||||
log.Debugf("ifconfig out: %s", out)
|
||||
|
||||
return nil, fmt.Errorf("command run: %w", err)
|
||||
}
|
||||
|
||||
i, err := parseIfconfigOutput(out)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse ifconfig output: %w", err)
|
||||
}
|
||||
|
||||
if i.Name != name {
|
||||
return nil, ErrNameDoesNotMatch
|
||||
}
|
||||
|
||||
return &Link{name: i.Name}, nil
|
||||
}
|
||||
|
||||
// Recreate - create new interface, remove current before create if it exists
|
||||
func (l *Link) Recreate() error {
|
||||
ok, err := l.isExist()
|
||||
if err != nil {
|
||||
return fmt.Errorf("is exist: %w", err)
|
||||
}
|
||||
|
||||
if ok {
|
||||
if err := l.del(l.name); err != nil {
|
||||
return fmt.Errorf("del: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return l.Add()
|
||||
}
|
||||
|
||||
// Add creates a new network interface.
|
||||
func (l *Link) Add() error {
|
||||
parsedName, err := l.create(wgIFGroup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create link: %w", err)
|
||||
}
|
||||
|
||||
if parsedName == l.name {
|
||||
return nil
|
||||
}
|
||||
|
||||
parsedName, err = l.rename(parsedName, l.name)
|
||||
if err != nil {
|
||||
errDel := l.del(parsedName)
|
||||
if errDel != nil {
|
||||
return fmt.Errorf("del on rename link: %w: %w", err, errDel)
|
||||
}
|
||||
|
||||
return fmt.Errorf("rename link: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Del removes an existing network interface.
|
||||
func (l *Link) Del() error {
|
||||
return l.del(l.name)
|
||||
}
|
||||
|
||||
// SetMTU sets the MTU of the network interface.
|
||||
func (l *Link) SetMTU(mtu int) error {
|
||||
return l.setMTU(mtu)
|
||||
}
|
||||
|
||||
// AssignAddr assigns an IP address and netmask to the network interface.
|
||||
func (l *Link) AssignAddr(ip, netmask string) error {
|
||||
return l.setAddr(ip, netmask)
|
||||
}
|
||||
|
||||
func (l *Link) Up() error {
|
||||
return l.up(l.name)
|
||||
}
|
||||
|
||||
func (l *Link) Down() error {
|
||||
return l.down(l.name)
|
||||
}
|
||||
|
||||
func (l *Link) isExist() (bool, error) {
|
||||
_, err := LinkByName(l.name)
|
||||
if errors.Is(err, ErrDoesNotExist) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("link by name: %w", err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (l *Link) create(groupName string) (string, error) {
|
||||
cmd := exec.Command("ifconfig", groupName, "create")
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Debugf("ifconfig out: %s", output)
|
||||
|
||||
return "", fmt.Errorf("create %s interface: %w", groupName, err)
|
||||
}
|
||||
|
||||
interfaceName, err := parseIFName(output)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parse interface name: %w", err)
|
||||
}
|
||||
|
||||
return interfaceName, nil
|
||||
}
|
||||
|
||||
func (l *Link) rename(oldName, newName string) (string, error) {
|
||||
cmd := exec.Command("ifconfig", oldName, "name", newName)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Debugf("ifconfig out: %s", output)
|
||||
|
||||
return "", fmt.Errorf("change name %q -> %q: %w", oldName, newName, err)
|
||||
}
|
||||
|
||||
interfaceName, err := parseIFName(output)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parse new name: %w", err)
|
||||
}
|
||||
|
||||
return interfaceName, nil
|
||||
}
|
||||
|
||||
func (l *Link) del(name string) error {
|
||||
var stderr bytes.Buffer
|
||||
|
||||
cmd := exec.Command("ifconfig", name, "destroy")
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Debugf("ifconfig out: %s", stderr.String())
|
||||
|
||||
return fmt.Errorf("destroy %s interface: %w", name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Link) setMTU(mtu int) error {
|
||||
var stderr bytes.Buffer
|
||||
|
||||
cmd := exec.Command("ifconfig", l.name, "mtu", strconv.Itoa(mtu))
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Debugf("ifconfig out: %s", stderr.String())
|
||||
|
||||
return fmt.Errorf("set interface mtu: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Link) setAddr(ip, netmask string) error {
|
||||
var stderr bytes.Buffer
|
||||
|
||||
cmd := exec.Command("ifconfig", l.name, "inet", ip, "netmask", netmask)
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Debugf("ifconfig out: %s", stderr.String())
|
||||
|
||||
return fmt.Errorf("set interface addr: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Link) up(name string) error {
|
||||
var stderr bytes.Buffer
|
||||
|
||||
cmd := exec.Command("ifconfig", name, "up")
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Debugf("ifconfig out: %s", stderr.String())
|
||||
|
||||
return fmt.Errorf("up %s interface: %w", name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Link) down(name string) error {
|
||||
var stderr bytes.Buffer
|
||||
|
||||
cmd := exec.Command("ifconfig", name, "down")
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Debugf("ifconfig out: %s", stderr.String())
|
||||
|
||||
return fmt.Errorf("down %s interface: %w", name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user