wip: implement dhcp v4 server

This commit is contained in:
Samuel Lorch 2023-04-23 19:52:30 +02:00
parent e71625ae8f
commit cc7f8b85b5
9 changed files with 342 additions and 1 deletions

View file

@ -0,0 +1,103 @@
package dhcp
import (
"context"
"fmt"
"os"
systemctl "github.com/coreos/go-systemd/v22/dbus"
"nfsense.net/nfsense/internal/definitions/config"
)
const defaultsFile = "/etc/default/isc-dhcp-server"
const dhcpv4File = "/etc/dhcp/dhcpd.conf"
const dhcpv6File = "/etc/dhcp/dhcpd6.conf"
func ApplyDHCPServerConfiguration(currentConfig config.Config, pendingConfig config.Config) error {
defaultsConfig, err := GenerateDHCPServerDefaultsConfiguration(pendingConfig)
if err != nil {
return fmt.Errorf("Generating DHCPServer Defaults Configuration: %w", err)
}
v4Conf, err := GenerateDHCPServerV4Configuration(pendingConfig)
if err != nil {
return fmt.Errorf("Generating DHCPServerV4 Configuration: %w", err)
}
v6Conf, err := GenerateDHCPServerV6Configuration(pendingConfig)
if err != nil {
return fmt.Errorf("Generating DHCPServerV6 Configuration: %w", err)
}
err = OverwriteFile(defaultsFile, defaultsConfig)
if err != nil {
return fmt.Errorf("Writing defaults Configuration: %w", err)
}
err = OverwriteFile(dhcpv4File, v4Conf)
if err != nil {
return fmt.Errorf("Writing v4 Configuration: %w", err)
}
err = OverwriteFile(dhcpv6File, v6Conf)
if err != nil {
return fmt.Errorf("Writing v6 Configuration: %w", err)
}
conn, err := systemctl.NewSystemConnectionContext(context.Background())
if err != nil {
return fmt.Errorf("Opening Dbus Connection: %w", err)
}
if len(pendingConfig.Service.DHCPv4Servers) == 0 && len(pendingConfig.Service.DHCPv6Servers) == 0 {
// if there are no servers stop the service instead
_, err := conn.StopUnitContext(context.Background(), "isc-dhcp-server.service", "replace", nil)
if err != nil {
return fmt.Errorf("stopping isc-dhcp-server.service: %w", err)
}
_, err = conn.DisableUnitFilesContext(context.Background(), []string{"isc-dhcp-server.service"}, false)
if err != nil {
return fmt.Errorf("disableing isc-dhcp-server.service: %w", err)
}
} else {
_, err := conn.ReloadOrRestartUnitContext(context.Background(), "isc-dhcp-server.service", "replace", nil)
if err != nil {
return fmt.Errorf("restarting isc-dhcp-server.service: %w", err)
}
_, _, err = conn.EnableUnitFilesContext(context.Background(), []string{"isc-dhcp-server.service"}, false, true)
if err != nil {
return fmt.Errorf("enableing isc-dhcp-server.service: %w", err)
}
}
return nil
}
func OverwriteFile(path, content string) error {
f, err := os.OpenFile(path, os.O_RDWR, 0644)
if err != nil {
return fmt.Errorf("opening File: %w", err)
}
err = f.Truncate(0)
if err != nil {
return fmt.Errorf("truncate File: %w", err)
}
_, err = f.Seek(0, 0)
if err != nil {
return fmt.Errorf("seek File: %w", err)
}
_, err = f.WriteString(content + "\n")
if err != nil {
return fmt.Errorf("writing File: %w", err)
}
err = f.Sync()
if err != nil {
return fmt.Errorf("syncing File: %w", err)
}
return nil
}

View file

@ -0,0 +1,43 @@
package dhcp
import (
"bytes"
"fmt"
"nfsense.net/nfsense/internal/definitions/config"
"nfsense.net/nfsense/internal/definitions/network"
)
type DHCPServerInterfaces struct {
V4 []string
V6 []string
}
func GenerateDHCPServerDefaultsConfiguration(conf config.Config) (string, error) {
v4 := []string{}
for _, s := range conf.Service.DHCPv4Servers {
if conf.Network.Interfaces[s.Interface].Type == network.Hardware {
v4 = append(v4, *conf.Network.Interfaces[s.Interface].HardwareDevice)
} else {
v4 = append(v4, s.Interface)
}
}
v6 := []string{}
for _, s := range conf.Service.DHCPv6Servers {
if conf.Network.Interfaces[s.Interface].Type == network.Hardware {
v6 = append(v6, *conf.Network.Interfaces[s.Interface].HardwareDevice)
} else {
v6 = append(v6, s.Interface)
}
}
interfaces := DHCPServerInterfaces{
V4: v4,
V6: v6,
}
buf := new(bytes.Buffer)
err := templates.ExecuteTemplate(buf, "default.tmpl", interfaces)
if err != nil {
return "", fmt.Errorf("executing default.tmpl template: %w", err)
}
return buf.String(), nil
}

View file

@ -0,0 +1,17 @@
package dhcp
import (
"bytes"
"fmt"
"nfsense.net/nfsense/internal/definitions/config"
)
func GenerateDHCPServerV4Configuration(conf config.Config) (string, error) {
buf := new(bytes.Buffer)
err := templates.ExecuteTemplate(buf, "v4_config.tmpl", conf)
if err != nil {
return "", fmt.Errorf("executing config.tmpl template: %w", err)
}
return buf.String(), nil
}

View file

@ -0,0 +1,17 @@
package dhcp
import (
"bytes"
"fmt"
"nfsense.net/nfsense/internal/definitions/config"
)
func GenerateDHCPServerV6Configuration(conf config.Config) (string, error) {
buf := new(bytes.Buffer)
err := templates.ExecuteTemplate(buf, "v6_config.tmpl", conf)
if err != nil {
return "", fmt.Errorf("executing config.tmpl template: %w", err)
}
return buf.String(), nil
}

View file

@ -0,0 +1,107 @@
package dhcp
import (
"embed"
"fmt"
"net"
"net/netip"
"strconv"
"strings"
"text/template"
"time"
"nfsense.net/nfsense/internal/definitions/config"
"nfsense.net/nfsense/internal/util"
)
//go:embed template
var templateFS embed.FS
var templates *template.Template
func init() {
var err error
templates, err = template.New("").Funcs(template.FuncMap{
"getInterfaceAddress": getInterfaceAddress,
"getInterfaceNetworkAddress": getInterfaceNetworkAddress,
"getInterfaceBroadcastAddress": getInterfaceBroadcastAddress,
"getInterfaceNetworkMask": getInterfaceNetworkMask,
"getAddressObjectsAsCommaList": getAddressObjectsAsCommaList,
"getAddressObjectAsPoolRange": getAddressObjectAsPoolRange,
"getTimeInSecond": getTimeInSecond,
}).ParseFS(templateFS, "template/*.tmpl")
if err != nil {
panic(err)
}
}
func getInterfaceAddress(conf config.Config, name string) string {
return conf.Network.Interfaces[name].Address.Addr().String()
}
func getInterfaceNetworkAddress(conf config.Config, name string) string {
return conf.Network.Interfaces[name].Address.Masked().Addr().String()
}
func getInterfaceBroadcastAddress(conf config.Config, name string) string {
return util.BroadcastAddr(prefix2IPNet(*conf.Network.Interfaces[name].Address)).String()
}
func getInterfaceNetworkMask(conf config.Config, name string) string {
return NetMaskToString(conf.Network.Interfaces[name].Address.Bits())
}
func getAddressObjectsAsCommaList(conf config.Config, names []string) string {
res := ""
for i, name := range names {
res = res + conf.Object.Addresses[name].Host.String()
if len(names)-1 != i {
res = res + ", "
}
}
return res
}
func getAddressObjectAsPoolRange(conf config.Config, name string) string {
// TODO
return strings.ReplaceAll(conf.Object.Addresses[name].Range.String(), "-", " ")
}
func getTimeInSecond(dur time.Duration) string {
return fmt.Sprintf("%d", int(dur.Seconds()))
}
func prefix2IPNet(prefix netip.Prefix) net.IPNet {
addr := prefix.Addr() // extract the address portion of the prefix
pLen := 128 // plen is the total size of the subnet mask
if addr.Is4() {
pLen = 32
}
ones := prefix.Bits() // ones is the portion of the mask that's set
ip := net.IP(addr.AsSlice()) // convert the address portion to net.IP
mask := net.CIDRMask(ones, pLen) // create a net.IPMask
return net.IPNet{ // and construct the final IPNet
IP: ip,
Mask: mask,
}
}
func NetMaskToString(mask int) string {
var binarystring string
for ii := 1; ii <= mask; ii++ {
binarystring = binarystring + "1"
}
for ii := 1; ii <= (32 - mask); ii++ {
binarystring = binarystring + "0"
}
oct1 := binarystring[0:8]
oct2 := binarystring[8:16]
oct3 := binarystring[16:24]
oct4 := binarystring[24:]
ii1, _ := strconv.ParseInt(oct1, 2, 64)
ii2, _ := strconv.ParseInt(oct2, 2, 64)
ii3, _ := strconv.ParseInt(oct3, 2, 64)
ii4, _ := strconv.ParseInt(oct4, 2, 64)
return strconv.Itoa(int(ii1)) + "." + strconv.Itoa(int(ii2)) + "." + strconv.Itoa(int(ii3)) + "." + strconv.Itoa(int(ii4))
}

View file

@ -0,0 +1,2 @@
INTERFACESv4="{{range .V4}}{{.}} {{end}}"
INTERFACESv6="{{range .V6}}{{.}} {{end}}"

View file

@ -0,0 +1,46 @@
# Global Options
authoritative;
deny bootp;
deny declines;
one-lease-per-client on;
# Servers
{{- range $i, $server := .Service.DHCPv4Servers }}
subnet {{ getInterfaceNetworkAddress $ $server.Interface }} netmask {{ getInterfaceNetworkMask $ $server.Interface }} {
# Pool
{{- range $j, $p := $server.Pool }}
range {{ getAddressObjectAsPoolRange $ $p }};
{{- end}}
# Settings
default-lease-time {{ getTimeInSecond $server.DefaultLeaseTime }};
max-lease-time {{ getTimeInSecond $server.MaxLeaseTime }};
# Options
option subnet-mask {{ getInterfaceNetworkMask $ $server.Interface }};
option broadcast-address {{ getInterfaceBroadcastAddress $ $server.Interface }};
{{- if eq $server.GatewayMode 1 }}
option routers {{ getInterfaceAddress $ $server.Interface }};
{{- else if eq $server.GatewayMode 2 }}
option routers {{ $server.Gateway }};
{{- end }}
{{- if eq $server.DNSServerMode 1 }}
option domain-name-servers {{ getInterfaceAddress $ $server.Interface }};
{{- else if eq $server.DNSServerMode 2 }}
option domain-name-servers {{ getAddressObjectsAsCommaList $ $server.DNSServers }};
{{- end }}
{{- if eq $server.NTPServerMode 1 }}
option time-servers {{ getInterfaceAddress $ $server.Interface }};
{{- else if eq $server.NTPServerMode 2 }}
option time-servers {{ getAddressObjectsAsCommaList $ $server.NTPServers }};
{{- end }}
# Hosts
{{- range $j, $reservation := $server.Reservations }}
host {{ $j }} {
hardware ethernet $reservation.HardwareAddress;
fixed-address $reservation.IPAddress;
}
{{end}}
}
{{end}}

View file

@ -17,6 +17,7 @@ import (
"nfsense.net/nfsense/internal/api/object"
"nfsense.net/nfsense/internal/api/service"
"nfsense.net/nfsense/internal/config"
dhcp "nfsense.net/nfsense/internal/dhcp_server"
"nfsense.net/nfsense/internal/jsonrpc"
"nfsense.net/nfsense/internal/networkd"
"nfsense.net/nfsense/internal/server"
@ -36,7 +37,7 @@ func main() {
defer dbusConn.Close()
configManager := config.CreateConfigManager()
configManager.RegisterApplyFunction(networkd.ApplyNetworkdConfiguration)
RegisterApplyFunctions(configManager)
err = configManager.LoadCurrentConfigFromDisk()
if err != nil {
@ -99,3 +100,8 @@ func RegisterAPIMethods(apiHandler *jsonrpc.Handler, configManager *config.Confi
apiHandler.Register("Object", &object.Object{ConfigManager: configManager})
apiHandler.Register("Service", &service.Service{ConfigManager: configManager, DbusConn: dbusConn})
}
func RegisterApplyFunctions(configManager *config.ConfigManager) {
configManager.RegisterApplyFunction(networkd.ApplyNetworkdConfiguration)
configManager.RegisterApplyFunction(dhcp.ApplyDHCPServerConfiguration)
}