mirror of
https://github.com/speatzle/nfsense.git
synced 2025-05-10 18:38:22 +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/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)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue