mirror of
https://github.com/speatzle/nfsense.git
synced 2025-05-11 19:08:20 +00:00
wip: implement dhcp v4 server
This commit is contained in:
parent
e71625ae8f
commit
cc7f8b85b5
9 changed files with 342 additions and 1 deletions
103
internal/dhcp_server/apply.go
Normal file
103
internal/dhcp_server/apply.go
Normal 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
|
||||||
|
}
|
43
internal/dhcp_server/defaults.go
Normal file
43
internal/dhcp_server/defaults.go
Normal 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
|
||||||
|
}
|
17
internal/dhcp_server/dhcpv4.go
Normal file
17
internal/dhcp_server/dhcpv4.go
Normal 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
|
||||||
|
}
|
17
internal/dhcp_server/dhcpv6.go
Normal file
17
internal/dhcp_server/dhcpv6.go
Normal 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
|
||||||
|
}
|
107
internal/dhcp_server/template.go
Normal file
107
internal/dhcp_server/template.go
Normal 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))
|
||||||
|
}
|
2
internal/dhcp_server/template/default.tmpl
Normal file
2
internal/dhcp_server/template/default.tmpl
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
INTERFACESv4="{{range .V4}}{{.}} {{end}}"
|
||||||
|
INTERFACESv6="{{range .V6}}{{.}} {{end}}"
|
46
internal/dhcp_server/template/v4_config.tmpl
Normal file
46
internal/dhcp_server/template/v4_config.tmpl
Normal 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}}
|
0
internal/dhcp_server/template/v6_config.tmpl
Normal file
0
internal/dhcp_server/template/v6_config.tmpl
Normal file
8
main.go
8
main.go
|
@ -17,6 +17,7 @@ import (
|
||||||
"nfsense.net/nfsense/internal/api/object"
|
"nfsense.net/nfsense/internal/api/object"
|
||||||
"nfsense.net/nfsense/internal/api/service"
|
"nfsense.net/nfsense/internal/api/service"
|
||||||
"nfsense.net/nfsense/internal/config"
|
"nfsense.net/nfsense/internal/config"
|
||||||
|
dhcp "nfsense.net/nfsense/internal/dhcp_server"
|
||||||
"nfsense.net/nfsense/internal/jsonrpc"
|
"nfsense.net/nfsense/internal/jsonrpc"
|
||||||
"nfsense.net/nfsense/internal/networkd"
|
"nfsense.net/nfsense/internal/networkd"
|
||||||
"nfsense.net/nfsense/internal/server"
|
"nfsense.net/nfsense/internal/server"
|
||||||
|
@ -36,7 +37,7 @@ func main() {
|
||||||
defer dbusConn.Close()
|
defer dbusConn.Close()
|
||||||
|
|
||||||
configManager := config.CreateConfigManager()
|
configManager := config.CreateConfigManager()
|
||||||
configManager.RegisterApplyFunction(networkd.ApplyNetworkdConfiguration)
|
RegisterApplyFunctions(configManager)
|
||||||
|
|
||||||
err = configManager.LoadCurrentConfigFromDisk()
|
err = configManager.LoadCurrentConfigFromDisk()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -99,3 +100,8 @@ func RegisterAPIMethods(apiHandler *jsonrpc.Handler, configManager *config.Confi
|
||||||
apiHandler.Register("Object", &object.Object{ConfigManager: configManager})
|
apiHandler.Register("Object", &object.Object{ConfigManager: configManager})
|
||||||
apiHandler.Register("Service", &service.Service{ConfigManager: configManager, DbusConn: dbusConn})
|
apiHandler.Register("Service", &service.Service{ConfigManager: configManager, DbusConn: dbusConn})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RegisterApplyFunctions(configManager *config.ConfigManager) {
|
||||||
|
configManager.RegisterApplyFunction(networkd.ApplyNetworkdConfiguration)
|
||||||
|
configManager.RegisterApplyFunction(dhcp.ApplyDHCPServerConfiguration)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue