add all files from Hong
This commit is contained in:
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 coredhcp
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
1
_config.yml
Normal file
1
_config.yml
Normal file
@@ -0,0 +1 @@
|
||||
theme: jekyll-theme-tactile
|
||||
5
cmds/client/README.md
Normal file
5
cmds/client/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# DHCPv6 debug client
|
||||
|
||||
This is a simple dhcpv6 client for use as a debugging tool with coredhcp
|
||||
|
||||
***This is not a general-purpose DHCP client. This is only a testing/debugging tool for developing CoreDHCP***
|
||||
61
cmds/client/main.go
Normal file
61
cmds/client/main.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package main
|
||||
|
||||
/*
|
||||
* Sample DHCPv6 client to test on the local interface
|
||||
*/
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"net"
|
||||
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6/client6"
|
||||
"github.com/insomniacslk/dhcp/iana"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("main")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
var macString string
|
||||
if len(flag.Args()) > 0 {
|
||||
macString = flag.Arg(0)
|
||||
} else {
|
||||
macString = "00:11:22:33:44:55"
|
||||
}
|
||||
|
||||
c := client6.NewClient()
|
||||
c.LocalAddr = &net.UDPAddr{
|
||||
IP: net.ParseIP("::1"),
|
||||
Port: 546,
|
||||
}
|
||||
c.RemoteAddr = &net.UDPAddr{
|
||||
IP: net.ParseIP("::1"),
|
||||
Port: 547,
|
||||
}
|
||||
log.Printf("%+v", c)
|
||||
|
||||
mac, err := net.ParseMAC(macString)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
duid := dhcpv6.DUIDLLT{
|
||||
HWType: iana.HWTypeEthernet,
|
||||
Time: dhcpv6.GetTime(),
|
||||
LinkLayerAddr: mac,
|
||||
}
|
||||
|
||||
conv, err := c.Exchange("lo", dhcpv6.WithClientID(&duid))
|
||||
for _, p := range conv {
|
||||
log.Print(p.Summary())
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
40
cmds/coredhcp-generator/README.md
Normal file
40
cmds/coredhcp-generator/README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
## CoreDHCP Generator
|
||||
|
||||
`coredhcp-generator` is a tool used to build CoreDHCP with the plugins you want.
|
||||
|
||||
Why is it even needed? Go is a compiled language with no dynamic loading
|
||||
support. In order to load a plugin, it has to be compiled in. We are happy to
|
||||
provide a standard [main.go](/cmds/coredhcp/main.go), and at the same time we
|
||||
don't want to include plugins that not everyone would use, otherwise the binary
|
||||
size would grow without control.
|
||||
|
||||
You can use `coredhcp-generator` to generate a `main.go` that includes all the
|
||||
plugins you wish. Just use it as follows:
|
||||
|
||||
```
|
||||
$ ./coredhcp-generator --from core-plugins.txt
|
||||
2019/11/21 23:32:04 Generating output file '/tmp/coredhcp547019106/coredhcp.go' with 7 plugin(s):
|
||||
2019/11/21 23:32:04 1) github.com/coredhcp/coredhcp/plugins/file
|
||||
2019/11/21 23:32:04 2) github.com/coredhcp/coredhcp/plugins/lease_time
|
||||
2019/11/21 23:32:04 3) github.com/coredhcp/coredhcp/plugins/netmask
|
||||
2019/11/21 23:32:04 4) github.com/coredhcp/coredhcp/plugins/range
|
||||
2019/11/21 23:32:04 5) github.com/coredhcp/coredhcp/plugins/router
|
||||
2019/11/21 23:32:04 6) github.com/coredhcp/coredhcp/plugins/server_id
|
||||
2019/11/21 23:32:04 7) github.com/coredhcp/coredhcp/plugins/dns
|
||||
2019/11/21 23:32:04 Generated file '/tmp/coredhcp547019106/coredhcp.go'. You can build it by running 'go build' in the output directory.
|
||||
```
|
||||
|
||||
You can also specify the plugin list on the command line, or mix it with
|
||||
`--from`:
|
||||
```
|
||||
$ ./coredhcp-generator --from core-plugins.txt \
|
||||
github.com/coredhcp/plugins/redis
|
||||
```
|
||||
|
||||
Notice that it created a file called `coredhcp.go` in a temporary directory. You
|
||||
can now `go build` that file and have your own custom CoreDHCP.
|
||||
|
||||
## Bugs
|
||||
|
||||
CoreDHCP uses Go versioned modules. The generated file does not do that yet. We
|
||||
will add this feature soon.
|
||||
15
cmds/coredhcp-generator/core-plugins.txt
Normal file
15
cmds/coredhcp-generator/core-plugins.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
github.com/coredhcp/coredhcp/plugins/autoconfigure
|
||||
github.com/coredhcp/coredhcp/plugins/dns
|
||||
github.com/coredhcp/coredhcp/plugins/file
|
||||
github.com/coredhcp/coredhcp/plugins/ipv6only
|
||||
github.com/coredhcp/coredhcp/plugins/leasetime
|
||||
github.com/coredhcp/coredhcp/plugins/mtu
|
||||
github.com/coredhcp/coredhcp/plugins/netmask
|
||||
github.com/coredhcp/coredhcp/plugins/nbp
|
||||
github.com/coredhcp/coredhcp/plugins/prefix
|
||||
github.com/coredhcp/coredhcp/plugins/range
|
||||
github.com/coredhcp/coredhcp/plugins/router
|
||||
github.com/coredhcp/coredhcp/plugins/serverid
|
||||
github.com/coredhcp/coredhcp/plugins/searchdomains
|
||||
github.com/coredhcp/coredhcp/plugins/sleep
|
||||
github.com/coredhcp/coredhcp/plugins/staticroute
|
||||
104
cmds/coredhcp-generator/coredhcp.go.template
Normal file
104
cmds/coredhcp-generator/coredhcp.go.template
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
{{/* This file is the template source. The following comment obviously doesn't apply here */ -}}
|
||||
// This is a generated file, edits should be made in the corresponding source file
|
||||
// And this file regenerated using `coredhcp-generator --from core-plugins.txt`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/coredhcp/coredhcp/config"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/server"
|
||||
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
{{- range $plugin := .}}
|
||||
{{- /* We import all plugins as pl_<pluginname> to avoid conflicts with reserved keywords */}}
|
||||
{{importname $plugin}} "{{$plugin}}"
|
||||
{{- end}}
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
flag "github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var (
|
||||
flagLogFile = flag.StringP("logfile", "l", "", "Name of the log file to append to. Default: stdout/stderr only")
|
||||
flagLogNoStdout = flag.BoolP("nostdout", "N", false, "Disable logging to stdout/stderr")
|
||||
flagLogLevel = flag.StringP("loglevel", "L", "info", fmt.Sprintf("Log level. One of %v", getLogLevels()))
|
||||
flagConfig = flag.StringP("conf", "c", "", "Use this configuration file instead of the default location")
|
||||
flagPlugins = flag.BoolP("plugins", "P", false, "list plugins")
|
||||
)
|
||||
|
||||
var logLevels = map[string]func(*logrus.Logger){
|
||||
"none": func(l *logrus.Logger) { l.SetOutput(io.Discard) },
|
||||
"debug": func(l *logrus.Logger) { l.SetLevel(logrus.DebugLevel) },
|
||||
"info": func(l *logrus.Logger) { l.SetLevel(logrus.InfoLevel) },
|
||||
"warning": func(l *logrus.Logger) { l.SetLevel(logrus.WarnLevel) },
|
||||
"error": func(l *logrus.Logger) { l.SetLevel(logrus.ErrorLevel) },
|
||||
"fatal": func(l *logrus.Logger) { l.SetLevel(logrus.FatalLevel) },
|
||||
}
|
||||
|
||||
func getLogLevels() []string {
|
||||
var levels []string
|
||||
for k := range logLevels {
|
||||
levels = append(levels, k)
|
||||
}
|
||||
return levels
|
||||
}
|
||||
|
||||
var desiredPlugins = []*plugins.Plugin{
|
||||
{{- range $plugin := .}}
|
||||
&{{importname $plugin}}.Plugin,
|
||||
{{- end}}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if *flagPlugins {
|
||||
for _, p := range desiredPlugins {
|
||||
fmt.Println(p.Name)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
log := logger.GetLogger("main")
|
||||
fn, ok := logLevels[*flagLogLevel]
|
||||
if !ok {
|
||||
log.Fatalf("Invalid log level '%s'. Valid log levels are %v", *flagLogLevel, getLogLevels())
|
||||
}
|
||||
fn(log.Logger)
|
||||
log.Infof("Setting log level to '%s'", *flagLogLevel)
|
||||
if *flagLogFile != "" {
|
||||
log.Infof("Logging to file %s", *flagLogFile)
|
||||
logger.WithFile(log, *flagLogFile)
|
||||
}
|
||||
if *flagLogNoStdout {
|
||||
log.Infof("Disabling logging to stdout/stderr")
|
||||
logger.WithNoStdOutErr(log)
|
||||
}
|
||||
config, err := config.Load(*flagConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load configuration: %v", err)
|
||||
}
|
||||
// register plugins
|
||||
for _, plugin := range desiredPlugins {
|
||||
if err := plugins.RegisterPlugin(plugin); err != nil {
|
||||
log.Fatalf("Failed to register plugin '%s': %v", plugin.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// start server
|
||||
srv, err := server.Start(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := srv.Wait(); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
144
cmds/coredhcp-generator/main.go
Normal file
144
cmds/coredhcp-generator/main.go
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
flag "github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTemplateFile = "coredhcp.go.template"
|
||||
importBase = "github.com/coredhcp/coredhcp/"
|
||||
)
|
||||
|
||||
var (
|
||||
flagTemplate = flag.StringP("template", "t", defaultTemplateFile, "Template file name")
|
||||
flagOutfile = flag.StringP("outfile", "o", "", "Output file path")
|
||||
flagFromFile = flag.StringP("from", "f", "", "Optional file name to get the plugin list from, one import path per line")
|
||||
)
|
||||
|
||||
var funcMap = template.FuncMap{
|
||||
"importname": func(importPath string) (string, error) {
|
||||
parts := strings.Split(importPath, "/")
|
||||
if len(parts) < 1 {
|
||||
return "", fmt.Errorf("no components found in import path '%s'", importPath)
|
||||
}
|
||||
return "pl_" + parts[len(parts)-1], nil
|
||||
},
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(flag.CommandLine.Output(),
|
||||
"%s [-template tpl] [-outfile out] [-from pluginlist] [plugin [plugin...]]\n",
|
||||
os.Args[0],
|
||||
)
|
||||
flag.PrintDefaults()
|
||||
fmt.Fprintln(flag.CommandLine.Output(), ` plugin
|
||||
Plugin name to include, as go import path.
|
||||
Short names can be used for builtin coredhcp plugins (eg "serverid")`)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
data, err := os.ReadFile(*flagTemplate)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read template file '%s': %v", *flagTemplate, err)
|
||||
}
|
||||
t, err := template.New("coredhcp").Funcs(funcMap).Parse(string(data))
|
||||
if err != nil {
|
||||
log.Fatalf("Template parsing failed: %v", err)
|
||||
}
|
||||
plugins := make(map[string]bool)
|
||||
for _, pl := range flag.Args() {
|
||||
pl := strings.TrimSpace(pl)
|
||||
if pl == "" {
|
||||
continue
|
||||
}
|
||||
if !strings.ContainsRune(pl, '/') {
|
||||
// A bare name was specified, not a full import path.
|
||||
// Coredhcp plugins aren't in the standard library, and it's unlikely someone
|
||||
// would put them at the base of $GOPATH/src.
|
||||
// Assume this is one of the builtin plugins. If needed, use the -from option
|
||||
// which always requires (and uses) exact paths
|
||||
|
||||
// XXX: we could also look into github.com/coredhcp/plugins
|
||||
pl = importBase + pl
|
||||
}
|
||||
plugins[pl] = true
|
||||
}
|
||||
if *flagFromFile != "" {
|
||||
// additional plugin names from a text file, one line per plugin import
|
||||
// path
|
||||
fd, err := os.Open(*flagFromFile)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read file '%s': %v", *flagFromFile, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := fd.Close(); err != nil {
|
||||
log.Printf("Error closing file '%s': %v", *flagFromFile, err)
|
||||
}
|
||||
}()
|
||||
sc := bufio.NewScanner(fd)
|
||||
for sc.Scan() {
|
||||
pl := strings.TrimSpace(sc.Text())
|
||||
if pl == "" {
|
||||
continue
|
||||
}
|
||||
plugins[pl] = true
|
||||
}
|
||||
if err := sc.Err(); err != nil {
|
||||
log.Fatalf("Error reading file '%s': %v", *flagFromFile, err)
|
||||
}
|
||||
}
|
||||
if len(plugins) == 0 {
|
||||
log.Fatalf("No plugin specified!")
|
||||
}
|
||||
outfile := *flagOutfile
|
||||
if outfile == "" {
|
||||
tmpdir, err := os.MkdirTemp("", "coredhcp")
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot create temporary directory: %v", err)
|
||||
}
|
||||
outfile = path.Join(tmpdir, "coredhcp.go")
|
||||
}
|
||||
|
||||
log.Printf("Generating output file '%s' with %d plugin(s):", outfile, len(plugins))
|
||||
idx := 1
|
||||
for pl := range plugins {
|
||||
log.Printf("% 3d) %s", idx, pl)
|
||||
idx++
|
||||
}
|
||||
outFD, err := os.OpenFile(outfile, os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create output file '%s': %v", outfile, err)
|
||||
}
|
||||
defer func() {
|
||||
if err := outFD.Close(); err != nil {
|
||||
log.Printf("Error while closing file descriptor for '%s': %v", outfile, err)
|
||||
}
|
||||
}()
|
||||
// WARNING: no escaping of the provided strings is done
|
||||
pluginList := make([]string, 0, len(plugins))
|
||||
for pl := range plugins {
|
||||
pluginList = append(pluginList, pl)
|
||||
}
|
||||
sort.Strings(pluginList)
|
||||
if err := t.Execute(outFD, pluginList); err != nil {
|
||||
log.Fatalf("Template execution failed: %v", err)
|
||||
}
|
||||
log.Printf("Generated file '%s'. You can build it by running 'go build' in the output directory.", outfile)
|
||||
fmt.Println(path.Dir(outfile))
|
||||
}
|
||||
167
cmds/coredhcp/config.yml
Normal file
167
cmds/coredhcp/config.yml
Normal file
@@ -0,0 +1,167 @@
|
||||
# CoreDHCP configuration (yaml)
|
||||
|
||||
# In this file, lines starting with "## " represent default values,
|
||||
# while uncommented lines are examples which have no default value
|
||||
|
||||
# The base level configuration has two sections, one for each protocol version
|
||||
# (DHCPv4 and DHCPv6). There is no shared configuration at the moment.
|
||||
# At a high level, both accept the same structure of configuration
|
||||
|
||||
# DHCPv6 configuration
|
||||
#server6:
|
||||
# listen is an optional section to specify how the server binds to an
|
||||
# interface or address.
|
||||
# If unset, the server will join the link-layer multicast group for all
|
||||
# dhcp servers and relays on each interface, as well as the site-scoped
|
||||
# multicast group for all dhcp servers.
|
||||
# Note that in this default configuration the server will not handle
|
||||
# unicast datagrams, and is equivalent to:
|
||||
## listen:
|
||||
## - "[ff02::1:2]"
|
||||
## - "[ff05::1:3]"
|
||||
|
||||
# In general, listen takes a list of addresses, with the general syntax
|
||||
# "[address%interface]:port", where each part is optional.
|
||||
# Omitting the address results in the wildcard address being used
|
||||
# Omitting the interface skips binding the listener to a specific interface
|
||||
# and listens on all interfaces instead
|
||||
# Omitting the port uses the default port for DHCPv6 (547)
|
||||
#
|
||||
# For example:
|
||||
# - "[::]"
|
||||
# Listen on the wildcard address on all interfaces on the default port.
|
||||
# Note that no multicast group will be joined, so this will *not* work with
|
||||
# most clients
|
||||
#
|
||||
# - ":44480"
|
||||
# Listens on the wildcard address on a specific port. This can be used if
|
||||
# you have a relay setup that can contact this server using unicast
|
||||
#
|
||||
# - "[::%eno1]"
|
||||
# Listens on the wildcard address on one interface. This can be used if you
|
||||
# want to spawn multiple servers with different configurations for multiple
|
||||
# interfaces, behind a relay that can use unicast
|
||||
#
|
||||
# There are some special considerations for multicast:
|
||||
# - "[ff02::1:2%eno1]"
|
||||
# Listens on a link-layer multicast address bound to an interface. Also
|
||||
# used to spawn multiple servers, but for clients on the same subnet
|
||||
#
|
||||
# - "[ff05::1:3%eno1]"
|
||||
# Joining a multicast group with an interface allows to skip the default
|
||||
# routing table when responding to clients, which can be useful if
|
||||
# multicast is not otherwise configured system-wide
|
||||
#
|
||||
# - "[ff02::1:2]"
|
||||
# Using a multicast address without an interface will be auto-expanded, so
|
||||
# that it listens on all available interfaces
|
||||
|
||||
|
||||
# plugins is a mandatory section, which defines how requests are handled.
|
||||
# It is a list of maps, matching plugin names to their arguments.
|
||||
# The order is meaningful, as incoming requests are handled by each plugin
|
||||
# in turn. There is no default value for a plugin configuration, and a
|
||||
# plugin that is not mentioned will not be loaded at all
|
||||
#
|
||||
# The following contains examples of the most common, builtin plugins.
|
||||
# External plugins should document their arguments in their own
|
||||
# documentations or readmes
|
||||
#plugins:
|
||||
# server_id is mandatory for RFC-compliant operation.
|
||||
# - server_id: <DUID format> <LL address>
|
||||
# The supported DUID formats are LL and LLT
|
||||
#- server_id: LL 00:de:ad:be:ef:00
|
||||
|
||||
# file serves leases defined in a static file, matching link-layer addresses to IPs
|
||||
# - file: <file name> [autorefresh]
|
||||
# The file format is one lease per line, "<hw address> <IPv6>"
|
||||
# When the 'autorefresh' argument is given, the plugin will try to refresh
|
||||
# the lease mapping during runtime whenever the lease file is updated.
|
||||
#- file: "leases.txt"
|
||||
|
||||
# dns adds information about available DNS resolvers to the responses
|
||||
# - dns: <resolver IP> <... resolver IPs>
|
||||
#- dns: 2001:4860:4860::8888 2001:4860:4860::8844
|
||||
|
||||
# nbp can add information about the location of a network boot program
|
||||
# - nbp: <NBP URL>
|
||||
#- nbp: "http://[2001:db8:a::1]/nbp"
|
||||
|
||||
# prefix provides prefix delegation.
|
||||
# - prefix: <prefix> <allocation size>
|
||||
# prefix is the prefix pool from which the allocations will be carved
|
||||
# allocation size is the maximum size for prefixes that will be allocated to clients
|
||||
# EG for allocating /64 or smaller prefixes within 2001:db8::/48 :
|
||||
#- prefix: 2001:db8::/48 64
|
||||
|
||||
# DHCPv4 configuration
|
||||
server4:
|
||||
# listen is an optional section to specify how the server binds to an
|
||||
# interface or address.
|
||||
# If unset, the server will listen on the broadcast address on all
|
||||
# interfaces, equivalent to:
|
||||
## listen:
|
||||
## - "0.0.0.0"
|
||||
|
||||
# In general, listen takes a list of addresses, with the general syntax
|
||||
# "address%interface:port", where each part is optional.
|
||||
# * Omitting the address results in the wildcard address being used
|
||||
# * Omitting the interface skips binding the listener to a specific interface
|
||||
# and listens on all interfaces instead
|
||||
# * Omitting the port uses the default port for DHCPv4 (67)
|
||||
#
|
||||
# For example:
|
||||
# - ":44480" Listens on a specific port.
|
||||
# - "%eno1" Listens on the wildcard address on one interface.
|
||||
# - "192.0.2.1%eno1:44480" with all parts
|
||||
|
||||
# plugins is a mandatory section, which defines how requests are handled.
|
||||
# It is a list of maps, matching plugin names to their arguments.
|
||||
# The order is meaningful, as incoming requests are handled by each plugin
|
||||
# in turn. There is no default value for a plugin configuration, and a
|
||||
# plugin that is not mentioned will not be loaded at all
|
||||
#
|
||||
# The following contains examples of the most common, builtin plugins.
|
||||
# External plugins should document their arguments in their own
|
||||
# documentations or readmes
|
||||
plugins:
|
||||
# lease_time sets the default lease time for advertised leases
|
||||
# - lease_time: <duration>
|
||||
# The duration can be given in any format understood by go's
|
||||
# "ParseDuration": https://golang.org/pkg/time/#ParseDuration
|
||||
- lease_time: 3600s
|
||||
|
||||
# server_id advertises a DHCP Server Identifier, to help resolve
|
||||
# situations where there are multiple DHCP servers on the network
|
||||
# - server_id: <IP address>
|
||||
# The IP address should be one address where this server is reachable
|
||||
- server_id: 10.10.10.1
|
||||
|
||||
# dns advertises DNS resolvers usable by the clients on this network
|
||||
# - dns: <IP address> <...IP addresses>
|
||||
- dns: 8.8.8.8 8.8.4.4
|
||||
|
||||
# router is mandatory, and advertises the address of the default router
|
||||
# for this network
|
||||
# - router: <IP address>
|
||||
- router: 192.168.1.1
|
||||
|
||||
# netmask advertises the network mask for the IPs assigned through this
|
||||
# server
|
||||
# - netmask: <network mask>
|
||||
- netmask: 255.255.255.0
|
||||
|
||||
# range allocates leases within a range of IPs
|
||||
# - range: <lease file> <start IP> <end IP> <lease duration>
|
||||
# * the lease file is an initially empty file where the leases that are
|
||||
# allocated to clients will be stored across server restarts
|
||||
# * lease duration can be given in any format understood by go's
|
||||
# "ParseDuration": https://golang.org/pkg/time/#ParseDuration
|
||||
- range: leases.txt 10.10.10.100 10.10.10.200 60s
|
||||
|
||||
# staticroute advertises additional routes the client should install in
|
||||
# its routing table as described in RFC3442
|
||||
# - staticroute: <destination>,<gateway> [<destination>,<gateway> ...]
|
||||
# where destination should be in CIDR notation and gateway should be
|
||||
# the IP address of the router through which the destination is reachable
|
||||
# - staticroute: 10.20.20.0/24,10.10.10.1
|
||||
167
cmds/coredhcp/config.yml.example
Normal file
167
cmds/coredhcp/config.yml.example
Normal file
@@ -0,0 +1,167 @@
|
||||
# CoreDHCP configuration (yaml)
|
||||
|
||||
# In this file, lines starting with "## " represent default values,
|
||||
# while uncommented lines are examples which have no default value
|
||||
|
||||
# The base level configuration has two sections, one for each protocol version
|
||||
# (DHCPv4 and DHCPv6). There is no shared configuration at the moment.
|
||||
# At a high level, both accept the same structure of configuration
|
||||
|
||||
# DHCPv6 configuration
|
||||
server6:
|
||||
# listen is an optional section to specify how the server binds to an
|
||||
# interface or address.
|
||||
# If unset, the server will join the link-layer multicast group for all
|
||||
# dhcp servers and relays on each interface, as well as the site-scoped
|
||||
# multicast group for all dhcp servers.
|
||||
# Note that in this default configuration the server will not handle
|
||||
# unicast datagrams, and is equivalent to:
|
||||
## listen:
|
||||
## - "[ff02::1:2]"
|
||||
## - "[ff05::1:3]"
|
||||
|
||||
# In general, listen takes a list of addresses, with the general syntax
|
||||
# "[address%interface]:port", where each part is optional.
|
||||
# Omitting the address results in the wildcard address being used
|
||||
# Omitting the interface skips binding the listener to a specific interface
|
||||
# and listens on all interfaces instead
|
||||
# Omitting the port uses the default port for DHCPv6 (547)
|
||||
#
|
||||
# For example:
|
||||
# - "[::]"
|
||||
# Listen on the wildcard address on all interfaces on the default port.
|
||||
# Note that no multicast group will be joined, so this will *not* work with
|
||||
# most clients
|
||||
#
|
||||
# - ":44480"
|
||||
# Listens on the wildcard address on a specific port. This can be used if
|
||||
# you have a relay setup that can contact this server using unicast
|
||||
#
|
||||
# - "[::%eno1]"
|
||||
# Listens on the wildcard address on one interface. This can be used if you
|
||||
# want to spawn multiple servers with different configurations for multiple
|
||||
# interfaces, behind a relay that can use unicast
|
||||
#
|
||||
# There are some special considerations for multicast:
|
||||
# - "[ff02::1:2%eno1]"
|
||||
# Listens on a link-layer multicast address bound to an interface. Also
|
||||
# used to spawn multiple servers, but for clients on the same subnet
|
||||
#
|
||||
# - "[ff05::1:3%eno1]"
|
||||
# Joining a multicast group with an interface allows to skip the default
|
||||
# routing table when responding to clients, which can be useful if
|
||||
# multicast is not otherwise configured system-wide
|
||||
#
|
||||
# - "[ff02::1:2]"
|
||||
# Using a multicast address without an interface will be auto-expanded, so
|
||||
# that it listens on all available interfaces
|
||||
|
||||
|
||||
# plugins is a mandatory section, which defines how requests are handled.
|
||||
# It is a list of maps, matching plugin names to their arguments.
|
||||
# The order is meaningful, as incoming requests are handled by each plugin
|
||||
# in turn. There is no default value for a plugin configuration, and a
|
||||
# plugin that is not mentioned will not be loaded at all
|
||||
#
|
||||
# The following contains examples of the most common, builtin plugins.
|
||||
# External plugins should document their arguments in their own
|
||||
# documentations or readmes
|
||||
plugins:
|
||||
# server_id is mandatory for RFC-compliant operation.
|
||||
# - server_id: <DUID format> <LL address>
|
||||
# The supported DUID formats are LL and LLT
|
||||
- server_id: LL 00:de:ad:be:ef:00
|
||||
|
||||
# file serves leases defined in a static file, matching link-layer addresses to IPs
|
||||
# - file: <file name> [autorefresh]
|
||||
# The file format is one lease per line, "<hw address> <IPv6>"
|
||||
# When the 'autorefresh' argument is given, the plugin will try to refresh
|
||||
# the lease mapping during runtime whenever the lease file is updated.
|
||||
- file: "leases.txt"
|
||||
|
||||
# dns adds information about available DNS resolvers to the responses
|
||||
# - dns: <resolver IP> <... resolver IPs>
|
||||
- dns: 2001:4860:4860::8888 2001:4860:4860::8844
|
||||
|
||||
# nbp can add information about the location of a network boot program
|
||||
# - nbp: <NBP URL>
|
||||
- nbp: "http://[2001:db8:a::1]/nbp"
|
||||
|
||||
# prefix provides prefix delegation.
|
||||
# - prefix: <prefix> <allocation size>
|
||||
# prefix is the prefix pool from which the allocations will be carved
|
||||
# allocation size is the maximum size for prefixes that will be allocated to clients
|
||||
# EG for allocating /64 or smaller prefixes within 2001:db8::/48 :
|
||||
- prefix: 2001:db8::/48 64
|
||||
|
||||
# DHCPv4 configuration
|
||||
server4:
|
||||
# listen is an optional section to specify how the server binds to an
|
||||
# interface or address.
|
||||
# If unset, the server will listen on the broadcast address on all
|
||||
# interfaces, equivalent to:
|
||||
## listen:
|
||||
## - "0.0.0.0"
|
||||
|
||||
# In general, listen takes a list of addresses, with the general syntax
|
||||
# "address%interface:port", where each part is optional.
|
||||
# * Omitting the address results in the wildcard address being used
|
||||
# * Omitting the interface skips binding the listener to a specific interface
|
||||
# and listens on all interfaces instead
|
||||
# * Omitting the port uses the default port for DHCPv4 (67)
|
||||
#
|
||||
# For example:
|
||||
# - ":44480" Listens on a specific port.
|
||||
# - "%eno1" Listens on the wildcard address on one interface.
|
||||
# - "192.0.2.1%eno1:44480" with all parts
|
||||
|
||||
# plugins is a mandatory section, which defines how requests are handled.
|
||||
# It is a list of maps, matching plugin names to their arguments.
|
||||
# The order is meaningful, as incoming requests are handled by each plugin
|
||||
# in turn. There is no default value for a plugin configuration, and a
|
||||
# plugin that is not mentioned will not be loaded at all
|
||||
#
|
||||
# The following contains examples of the most common, builtin plugins.
|
||||
# External plugins should document their arguments in their own
|
||||
# documentations or readmes
|
||||
plugins:
|
||||
# lease_time sets the default lease time for advertised leases
|
||||
# - lease_time: <duration>
|
||||
# The duration can be given in any format understood by go's
|
||||
# "ParseDuration": https://golang.org/pkg/time/#ParseDuration
|
||||
- lease_time: 3600s
|
||||
|
||||
# server_id advertises a DHCP Server Identifier, to help resolve
|
||||
# situations where there are multiple DHCP servers on the network
|
||||
# - server_id: <IP address>
|
||||
# The IP address should be one address where this server is reachable
|
||||
- server_id: 10.10.10.1
|
||||
|
||||
# dns advertises DNS resolvers usable by the clients on this network
|
||||
# - dns: <IP address> <...IP addresses>
|
||||
- dns: 8.8.8.8 8.8.4.4
|
||||
|
||||
# router is mandatory, and advertises the address of the default router
|
||||
# for this network
|
||||
# - router: <IP address>
|
||||
- router: 192.168.1.1
|
||||
|
||||
# netmask advertises the network mask for the IPs assigned through this
|
||||
# server
|
||||
# - netmask: <network mask>
|
||||
- netmask: 255.255.255.0
|
||||
|
||||
# range allocates leases within a range of IPs
|
||||
# - range: <lease file> <start IP> <end IP> <lease duration>
|
||||
# * the lease file is an initially empty file where the leases that are
|
||||
# allocated to clients will be stored across server restarts
|
||||
# * lease duration can be given in any format understood by go's
|
||||
# "ParseDuration": https://golang.org/pkg/time/#ParseDuration
|
||||
- range: leases.txt 10.10.10.100 10.10.10.200 60s
|
||||
|
||||
# staticroute advertises additional routes the client should install in
|
||||
# its routing table as described in RFC3442
|
||||
# - staticroute: <destination>,<gateway> [<destination>,<gateway> ...]
|
||||
# where destination should be in CIDR notation and gateway should be
|
||||
# the IP address of the router through which the destination is reachable
|
||||
# - staticroute: 10.20.20.0/24,10.10.10.1
|
||||
BIN
cmds/coredhcp/coredhcp
Executable file
BIN
cmds/coredhcp/coredhcp
Executable file
Binary file not shown.
1
cmds/coredhcp/file_leases.txt.example
Normal file
1
cmds/coredhcp/file_leases.txt.example
Normal file
@@ -0,0 +1 @@
|
||||
00:11:22:33:44:55 2001:2::1
|
||||
BIN
cmds/coredhcp/leases.txt
Normal file
BIN
cmds/coredhcp/leases.txt
Normal file
Binary file not shown.
129
cmds/coredhcp/main.go
Normal file
129
cmds/coredhcp/main.go
Normal file
@@ -0,0 +1,129 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
// This is a generated file, edits should be made in the corresponding source file
|
||||
// And this file regenerated using `coredhcp-generator --from core-plugins.txt`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/coredhcp/coredhcp/config"
|
||||
grpcServer "github.com/coredhcp/coredhcp/grpc_server"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/server"
|
||||
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
pl_autoconfigure "github.com/coredhcp/coredhcp/plugins/autoconfigure"
|
||||
pl_dns "github.com/coredhcp/coredhcp/plugins/dns"
|
||||
pl_file "github.com/coredhcp/coredhcp/plugins/file"
|
||||
pl_ipv6only "github.com/coredhcp/coredhcp/plugins/ipv6only"
|
||||
pl_leasetime "github.com/coredhcp/coredhcp/plugins/leasetime"
|
||||
pl_mtu "github.com/coredhcp/coredhcp/plugins/mtu"
|
||||
pl_nbp "github.com/coredhcp/coredhcp/plugins/nbp"
|
||||
pl_netmask "github.com/coredhcp/coredhcp/plugins/netmask"
|
||||
pl_prefix "github.com/coredhcp/coredhcp/plugins/prefix"
|
||||
pl_range "github.com/coredhcp/coredhcp/plugins/range"
|
||||
pl_router "github.com/coredhcp/coredhcp/plugins/router"
|
||||
pl_searchdomains "github.com/coredhcp/coredhcp/plugins/searchdomains"
|
||||
pl_serverid "github.com/coredhcp/coredhcp/plugins/serverid"
|
||||
pl_sleep "github.com/coredhcp/coredhcp/plugins/sleep"
|
||||
pl_staticroute "github.com/coredhcp/coredhcp/plugins/staticroute"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
flag "github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var (
|
||||
flagLogFile = flag.StringP("logfile", "l", "", "Name of the log file to append to. Default: stdout/stderr only")
|
||||
flagLogNoStdout = flag.BoolP("nostdout", "N", false, "Disable logging to stdout/stderr")
|
||||
flagLogLevel = flag.StringP("loglevel", "L", "info", fmt.Sprintf("Log level. One of %v", getLogLevels()))
|
||||
flagConfig = flag.StringP("conf", "c", "", "Use this configuration file instead of the default location")
|
||||
flagPlugins = flag.BoolP("plugins", "P", false, "list plugins")
|
||||
)
|
||||
|
||||
var logLevels = map[string]func(*logrus.Logger){
|
||||
"none": func(l *logrus.Logger) { l.SetOutput(io.Discard) },
|
||||
"debug": func(l *logrus.Logger) { l.SetLevel(logrus.DebugLevel) },
|
||||
"info": func(l *logrus.Logger) { l.SetLevel(logrus.InfoLevel) },
|
||||
"warning": func(l *logrus.Logger) { l.SetLevel(logrus.WarnLevel) },
|
||||
"error": func(l *logrus.Logger) { l.SetLevel(logrus.ErrorLevel) },
|
||||
"fatal": func(l *logrus.Logger) { l.SetLevel(logrus.FatalLevel) },
|
||||
}
|
||||
|
||||
func getLogLevels() []string {
|
||||
var levels []string
|
||||
for k := range logLevels {
|
||||
levels = append(levels, k)
|
||||
}
|
||||
return levels
|
||||
}
|
||||
|
||||
var desiredPlugins = []*plugins.Plugin{
|
||||
&pl_autoconfigure.Plugin,
|
||||
&pl_dns.Plugin,
|
||||
&pl_file.Plugin,
|
||||
&pl_ipv6only.Plugin,
|
||||
&pl_leasetime.Plugin,
|
||||
&pl_mtu.Plugin,
|
||||
&pl_nbp.Plugin,
|
||||
&pl_netmask.Plugin,
|
||||
&pl_prefix.Plugin,
|
||||
&pl_range.Plugin,
|
||||
&pl_router.Plugin,
|
||||
&pl_searchdomains.Plugin,
|
||||
&pl_serverid.Plugin,
|
||||
&pl_sleep.Plugin,
|
||||
&pl_staticroute.Plugin,
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if *flagPlugins {
|
||||
for _, p := range desiredPlugins {
|
||||
fmt.Println(p.Name)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
log := logger.GetLogger("main")
|
||||
fn, ok := logLevels[*flagLogLevel]
|
||||
if !ok {
|
||||
log.Fatalf("Invalid log level '%s'. Valid log levels are %v", *flagLogLevel, getLogLevels())
|
||||
}
|
||||
fn(log.Logger)
|
||||
log.Infof("Setting log level to '%s'", *flagLogLevel)
|
||||
if *flagLogFile != "" {
|
||||
log.Infof("Logging to file %s", *flagLogFile)
|
||||
logger.WithFile(log, *flagLogFile)
|
||||
}
|
||||
if *flagLogNoStdout {
|
||||
log.Infof("Disabling logging to stdout/stderr")
|
||||
logger.WithNoStdOutErr(log)
|
||||
}
|
||||
config, err := config.Load(*flagConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load configuration: %v", err)
|
||||
}
|
||||
// register plugins
|
||||
for _, plugin := range desiredPlugins {
|
||||
if err := plugins.RegisterPlugin(plugin); err != nil {
|
||||
log.Fatalf("Failed to register plugin '%s': %v", plugin.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
go grpcServer.Run()
|
||||
|
||||
// start server
|
||||
srv, err := server.Start(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := srv.Wait(); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
341
config/config.go
Normal file
341
config/config.go
Normal file
@@ -0,0 +1,341 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
"github.com/spf13/cast"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("config")
|
||||
|
||||
type protocolVersion int
|
||||
|
||||
const (
|
||||
protocolV6 protocolVersion = 6
|
||||
protocolV4 protocolVersion = 4
|
||||
)
|
||||
|
||||
// Config holds the DHCPv6/v4 server configuration
|
||||
type Config struct {
|
||||
v *viper.Viper
|
||||
Server6 *ServerConfig
|
||||
Server4 *ServerConfig
|
||||
}
|
||||
|
||||
// New returns a new initialized instance of a Config object
|
||||
func New() *Config {
|
||||
return &Config{v: viper.New()}
|
||||
}
|
||||
|
||||
// ServerConfig holds a server configuration that is specific to either the
|
||||
// DHCPv6 server or the DHCPv4 server.
|
||||
type ServerConfig struct {
|
||||
Addresses []net.UDPAddr
|
||||
Plugins []PluginConfig
|
||||
}
|
||||
|
||||
// PluginConfig holds the configuration of a plugin
|
||||
type PluginConfig struct {
|
||||
Name string
|
||||
Args []string
|
||||
}
|
||||
|
||||
// Load reads a configuration file and returns a Config object, or an error if
|
||||
// any.
|
||||
func Load(pathOverride string) (*Config, error) {
|
||||
log.Print("Loading configuration")
|
||||
c := New()
|
||||
c.v.SetConfigType("yml")
|
||||
if pathOverride != "" {
|
||||
c.v.SetConfigFile(pathOverride)
|
||||
} else {
|
||||
c.v.SetConfigName("config")
|
||||
c.v.AddConfigPath(".")
|
||||
c.v.AddConfigPath("$XDG_CONFIG_HOME/coredhcp/")
|
||||
c.v.AddConfigPath("$HOME/.coredhcp/")
|
||||
c.v.AddConfigPath("/etc/coredhcp/")
|
||||
}
|
||||
|
||||
if err := c.v.ReadInConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.parseConfig(protocolV6); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.parseConfig(protocolV4); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.Server6 == nil && c.Server4 == nil {
|
||||
return nil, ConfigErrorFromString("need at least one valid config for DHCPv6 or DHCPv4")
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func protoVersionCheck(v protocolVersion) error {
|
||||
if v != protocolV6 && v != protocolV4 {
|
||||
return fmt.Errorf("invalid protocol version: %d", v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parsePlugins(pluginList []interface{}) ([]PluginConfig, error) {
|
||||
plugins := make([]PluginConfig, 0, len(pluginList))
|
||||
for idx, val := range pluginList {
|
||||
conf := cast.ToStringMap(val)
|
||||
if conf == nil {
|
||||
return nil, ConfigErrorFromString("dhcpv6: plugin #%d is not a string map", idx)
|
||||
}
|
||||
// make sure that only one item is specified, since it's a
|
||||
// map name -> args
|
||||
if len(conf) != 1 {
|
||||
return nil, ConfigErrorFromString("dhcpv6: exactly one plugin per item can be specified")
|
||||
}
|
||||
var (
|
||||
name string
|
||||
args []string
|
||||
)
|
||||
// only one item, as enforced above, so read just that
|
||||
for k, v := range conf {
|
||||
name = k
|
||||
args = strings.Fields(cast.ToString(v))
|
||||
break
|
||||
}
|
||||
plugins = append(plugins, PluginConfig{Name: name, Args: args})
|
||||
}
|
||||
return plugins, nil
|
||||
}
|
||||
|
||||
// BUG(Natolumin): listen specifications of the form `[ip6]%iface:port` or
|
||||
// `[ip6]%iface` are not supported, even though they are the default format of
|
||||
// the `ss` utility in linux. Use `[ip6%iface]:port` instead
|
||||
|
||||
// splitHostPort splits an address of the form ip%zone:port into ip,zone and port.
|
||||
// It still returns if any of these are unset (unlike net.SplitHostPort which
|
||||
// returns an error if there is no port)
|
||||
func splitHostPort(hostport string) (ip string, zone string, port string, err error) {
|
||||
ip, port, err = net.SplitHostPort(hostport)
|
||||
if err != nil {
|
||||
// Either there is no port, or a more serious error.
|
||||
// Supply a synthetic port to differentiate cases
|
||||
var altErr error
|
||||
if ip, _, altErr = net.SplitHostPort(hostport + ":0"); altErr != nil {
|
||||
// Invalid even with a fake port. Return the original error
|
||||
return
|
||||
}
|
||||
err = nil
|
||||
}
|
||||
if i := strings.LastIndexByte(ip, '%'); i >= 0 {
|
||||
ip, zone = ip[:i], ip[i+1:]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Config) getListenAddress(addr string, ver protocolVersion) (*net.UDPAddr, error) {
|
||||
if err := protoVersionCheck(ver); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ipStr, ifname, portStr, err := splitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, ConfigErrorFromString("dhcpv%d: %v", ver, err)
|
||||
}
|
||||
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ipStr == "" {
|
||||
switch ver {
|
||||
case protocolV4:
|
||||
ip = net.IPv4zero
|
||||
case protocolV6:
|
||||
ip = net.IPv6unspecified
|
||||
default:
|
||||
panic("BUG: Unknown protocol version")
|
||||
}
|
||||
}
|
||||
if ip == nil {
|
||||
return nil, ConfigErrorFromString("dhcpv%d: invalid IP address in `listen` directive: %s", ver, ipStr)
|
||||
}
|
||||
if ip4 := ip.To4(); (ver == protocolV6 && ip4 != nil) || (ver == protocolV4 && ip4 == nil) {
|
||||
return nil, ConfigErrorFromString("dhcpv%d: not a valid IPv%d address in `listen` directive: '%s'", ver, ver, ipStr)
|
||||
}
|
||||
|
||||
var port int
|
||||
if portStr == "" {
|
||||
switch ver {
|
||||
case protocolV4:
|
||||
port = dhcpv4.ServerPort
|
||||
case protocolV6:
|
||||
port = dhcpv6.DefaultServerPort
|
||||
default:
|
||||
panic("BUG: Unknown protocol version")
|
||||
}
|
||||
} else {
|
||||
port, err = strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return nil, ConfigErrorFromString("dhcpv%d: invalid `listen` port '%s'", ver, portStr)
|
||||
}
|
||||
}
|
||||
|
||||
listener := net.UDPAddr{
|
||||
IP: ip,
|
||||
Port: port,
|
||||
Zone: ifname,
|
||||
}
|
||||
return &listener, nil
|
||||
}
|
||||
|
||||
func (c *Config) getPlugins(ver protocolVersion) ([]PluginConfig, error) {
|
||||
if err := protoVersionCheck(ver); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pluginList := cast.ToSlice(c.v.Get(fmt.Sprintf("server%d.plugins", ver)))
|
||||
if pluginList == nil {
|
||||
return nil, ConfigErrorFromString("dhcpv%d: invalid plugins section, not a list or no plugin specified", ver)
|
||||
}
|
||||
return parsePlugins(pluginList)
|
||||
}
|
||||
|
||||
func (c *Config) parseConfig(ver protocolVersion) error {
|
||||
if err := protoVersionCheck(ver); err != nil {
|
||||
return err
|
||||
}
|
||||
if exists := c.v.Get(fmt.Sprintf("server%d", ver)); exists == nil {
|
||||
// it is valid to have no server configuration defined
|
||||
return nil
|
||||
}
|
||||
// read plugin configuration
|
||||
plugins, err := c.getPlugins(ver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, p := range plugins {
|
||||
log.Printf("DHCPv%d: found plugin `%s` with %d args: %v", ver, p.Name, len(p.Args), p.Args)
|
||||
}
|
||||
|
||||
listeners, err := c.parseListen(ver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sc := ServerConfig{
|
||||
Addresses: listeners,
|
||||
Plugins: plugins,
|
||||
}
|
||||
if ver == protocolV6 {
|
||||
c.Server6 = &sc
|
||||
} else if ver == protocolV4 {
|
||||
c.Server4 = &sc
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BUG(Natolumin): When listening on link-local multicast addresses without
|
||||
// binding to a specific interface, new interfaces coming up after the server
|
||||
// starts will not be taken into account.
|
||||
|
||||
func expandLLMulticast(addr *net.UDPAddr) ([]net.UDPAddr, error) {
|
||||
if !addr.IP.IsLinkLocalMulticast() && !addr.IP.IsInterfaceLocalMulticast() {
|
||||
return nil, errors.New("Address is not multicast")
|
||||
}
|
||||
if addr.Zone != "" {
|
||||
return nil, errors.New("Address is already zoned")
|
||||
}
|
||||
var needFlags = net.FlagMulticast
|
||||
if addr.IP.To4() != nil {
|
||||
// We need to be able to send broadcast responses in ipv4
|
||||
needFlags |= net.FlagBroadcast
|
||||
}
|
||||
|
||||
ifs, err := net.Interfaces()
|
||||
ret := make([]net.UDPAddr, 0, len(ifs))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not list network interfaces: %v", err)
|
||||
}
|
||||
for _, iface := range ifs {
|
||||
if (iface.Flags & needFlags) != needFlags {
|
||||
continue
|
||||
}
|
||||
caddr := *addr
|
||||
caddr.Zone = iface.Name
|
||||
ret = append(ret, caddr)
|
||||
}
|
||||
if len(ret) == 0 {
|
||||
return nil, errors.New("No suitable interface found for multicast listener")
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func defaultListen(ver protocolVersion) ([]net.UDPAddr, error) {
|
||||
switch ver {
|
||||
case protocolV4:
|
||||
return []net.UDPAddr{{Port: dhcpv4.ServerPort}}, nil
|
||||
case protocolV6:
|
||||
l, err := expandLLMulticast(&net.UDPAddr{IP: dhcpv6.AllDHCPRelayAgentsAndServers, Port: dhcpv6.DefaultServerPort})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l = append(l,
|
||||
net.UDPAddr{IP: dhcpv6.AllDHCPServers, Port: dhcpv6.DefaultServerPort},
|
||||
// XXX: Do we want to listen on [::] as default ?
|
||||
)
|
||||
return l, nil
|
||||
}
|
||||
return nil, errors.New("defaultListen: Incorrect protocol version")
|
||||
}
|
||||
|
||||
func (c *Config) parseListen(ver protocolVersion) ([]net.UDPAddr, error) {
|
||||
if err := protoVersionCheck(ver); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
listen := c.v.Get(fmt.Sprintf("server%d.listen", ver))
|
||||
|
||||
// Provide an emulation of the old keyword "interface" to avoid breaking config files
|
||||
if iface := c.v.Get(fmt.Sprintf("server%d.interface", ver)); iface != nil && listen != nil {
|
||||
return nil, ConfigErrorFromString("interface is a deprecated alias for listen, " +
|
||||
"both cannot be used at the same time. Choose one and remove the other.")
|
||||
} else if iface != nil {
|
||||
listen = "%" + cast.ToString(iface)
|
||||
}
|
||||
|
||||
if listen == nil {
|
||||
return defaultListen(ver)
|
||||
}
|
||||
|
||||
addrs, err := cast.ToStringSliceE(listen)
|
||||
if err != nil {
|
||||
addrs = []string{cast.ToString(listen)}
|
||||
}
|
||||
|
||||
listeners := []net.UDPAddr{}
|
||||
for _, a := range addrs {
|
||||
l, err := c.getListenAddress(a, ver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if l.Zone == "" && (l.IP.IsLinkLocalMulticast() || l.IP.IsInterfaceLocalMulticast()) {
|
||||
// link-local multicast specified without interface gets expanded to listen on all interfaces
|
||||
expanded, err := expandLLMulticast(l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
listeners = append(listeners, expanded...)
|
||||
continue
|
||||
}
|
||||
|
||||
listeners = append(listeners, *l)
|
||||
}
|
||||
return listeners, nil
|
||||
}
|
||||
52
config/config_test.go
Normal file
52
config/config_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package config
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSplitHostPort(t *testing.T) {
|
||||
testcases := []struct {
|
||||
hostport string
|
||||
ip string
|
||||
zone string
|
||||
port string
|
||||
err bool // Should return an error (ie true for err != nil)
|
||||
}{
|
||||
{"0.0.0.0:67", "0.0.0.0", "", "67", false},
|
||||
{"192.0.2.0", "192.0.2.0", "", "", false},
|
||||
{"192.0.2.9%eth0", "192.0.2.9", "eth0", "", false},
|
||||
{"0.0.0.0%eth0:67", "0.0.0.0", "eth0", "67", false},
|
||||
{"0.0.0.0:20%eth0:67", "0.0.0.0", "eth0", "67", true},
|
||||
{"2001:db8::1:547", "", "", "547", true}, // [] mandatory for v6
|
||||
{"[::]:547", "::", "", "547", false},
|
||||
{"[fe80::1%eth0]", "fe80::1", "eth0", "", false},
|
||||
{"[fe80::1]:eth1", "fe80::1", "", "eth1", false}, // no validation of ports in this function
|
||||
{"fe80::1%eth0:547", "fe80::1", "eth0", "547", true}, // [] mandatory for v6 even with %zone
|
||||
{"fe80::1%eth0", "fe80::1", "eth0", "547", true}, // [] mandatory for v6 even without port
|
||||
{"[2001:db8::2]47", "fe80::1", "eth0", "547", true}, // garbage after []
|
||||
{"[ff02::1:2]%srv_u:547", "ff02::1:2", "srv_u", "547", true}, // FIXME: Linux `ss` format, we should accept but net.SplitHostPort doesn't
|
||||
{":http", "", "", "http", false},
|
||||
{"%eth0:80", "", "eth0", "80", false}, // janky, but looks valid enough for "[::%eth0]:80" imo
|
||||
{"%eth0", "", "eth0", "", false}, // janky
|
||||
{"fe80::1]:80", "fe80::1", "", "80", true}, // unbalanced ]
|
||||
{"fe80::1%eth0]", "fe80::1", "eth0", "", true}, // unbalanced ], no port
|
||||
{"", "", "", "", false}, // trivial case, still valid
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
ip, zone, port, err := splitHostPort(tc.hostport)
|
||||
if tc.err != (err != nil) {
|
||||
errState := "not "
|
||||
if tc.err {
|
||||
errState = ""
|
||||
}
|
||||
t.Errorf("Mismatched error state: %s should %serror (got err: %v)\n", tc.hostport, errState, err)
|
||||
continue
|
||||
}
|
||||
if err == nil && (ip != tc.ip || zone != tc.zone || port != tc.port) {
|
||||
t.Errorf("%s => \"%s\", \"%s\", \"%s\" expected \"%s\",\"%s\",\"%s\"\n", tc.hostport, ip, zone, port, tc.ip, tc.zone, tc.port)
|
||||
}
|
||||
}
|
||||
}
|
||||
32
config/errors.go
Normal file
32
config/errors.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ConfigError is an error type returned upon configuration errors.
|
||||
type ConfigError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
// ConfigErrorFromString returns a ConfigError from the given error string.
|
||||
func ConfigErrorFromString(format string, args ...interface{}) *ConfigError {
|
||||
return &ConfigError{
|
||||
err: fmt.Errorf(format, args...),
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigErrorFromError returns a ConfigError from the given error object.
|
||||
func ConfigErrorFromError(err error) *ConfigError {
|
||||
return &ConfigError{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (ce ConfigError) Error() string {
|
||||
return fmt.Sprintf("error parsing config: %v", ce.err)
|
||||
}
|
||||
2
config/static_ip.csv
Executable file
2
config/static_ip.csv
Executable file
@@ -0,0 +1,2 @@
|
||||
42:9c:aa:1b:87:d5,172.16.11.20
|
||||
d2:ab:13:8a:e8:15,172.16.11.21
|
||||
|
55
go.mod
Normal file
55
go.mod
Normal file
@@ -0,0 +1,55 @@
|
||||
module github.com/coredhcp/coredhcp
|
||||
|
||||
go 1.23.3
|
||||
|
||||
require (
|
||||
github.com/bits-and-blooms/bitset v1.15.0
|
||||
github.com/chappjc/logrus-prefix v0.0.0-20180227015900-3a1d64819adb
|
||||
github.com/fsnotify/fsnotify v1.8.0
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475
|
||||
github.com/mattn/go-sqlite3 v1.14.24
|
||||
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cast v1.7.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/vishvananda/netns v0.0.5
|
||||
golang.org/x/net v0.31.0
|
||||
google.golang.org/grpc v1.68.0
|
||||
google.golang.org/protobuf v1.35.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/onsi/gomega v1.35.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.29.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/sys v0.27.0 // indirect
|
||||
golang.org/x/term v0.26.0 // indirect
|
||||
golang.org/x/text v0.20.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
276
go.sum
Normal file
276
go.sum
Normal file
@@ -0,0 +1,276 @@
|
||||
cel.dev/expr v0.16.1/go.mod h1:AsGA5zb3WruAEQeQng1RZdGEXmBj0jvMWh6l5SnNuC8=
|
||||
cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
|
||||
cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40=
|
||||
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
|
||||
cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk=
|
||||
cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
|
||||
cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s=
|
||||
cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
|
||||
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
|
||||
github.com/bits-and-blooms/bitset v1.15.0 h1:DiCRMscZsGyYePE9AR3sVhKqUXCt5IZvkX5AfAc5xLQ=
|
||||
github.com/bits-and-blooms/bitset v1.15.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chappjc/logrus-prefix v0.0.0-20180227015900-3a1d64819adb h1:aZTKxMminKeQWHtzJBbV8TttfTxzdJ+7iEJFE6FmUzg=
|
||||
github.com/chappjc/logrus-prefix v0.0.0-20180227015900-3a1d64819adb/go.mod h1:xzXc1S/L+64uglB3pw54o8kqyM6KFYpTeC9Q6+qZIu8=
|
||||
github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnvMg4d7nvT/wl9WgVXn3Q8=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
|
||||
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/hashicorp/consul/api v1.28.2/go.mod h1:KyzqzgMEya+IZPcD65YFoOVAgPpbfERu4I/tzG6/ueE=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475 h1:hxST5pwMBEOWmxpkX20w9oZG+hXdhKmAIPQ3NGGAxas=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
|
||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jsimonetti/rtnetlink v1.3.5/go.mod h1:0LFedyiTkebnd43tE4YAkWGIq9jQphow4CcwxaT2Y00=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY=
|
||||
github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/nats-io/nats.go v1.34.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
|
||||
github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
|
||||
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
|
||||
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo=
|
||||
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/sagikazarmark/crypt v0.19.0/go.mod h1:c6vimRziqqERhtSe0MhIvzE1w54FrCHtrXb5NH/ja78=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.12/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4=
|
||||
go.etcd.io/etcd/client/v2 v2.305.12/go.mod h1:aQ/yhsxMu+Oht1FOupSr60oBvcS9cKXHrzBpDsPTf9E=
|
||||
go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9mSbPiqw=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
|
||||
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
google.golang.org/api v0.171.0/go.mod h1:Hnq5AHm4OTMt2BUVjael2CWZFD6vksJdWCWiUAmjC9o=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
|
||||
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
369
grpc_server/dhcpServer/dhcp_server.pb.go
Normal file
369
grpc_server/dhcpServer/dhcp_server.pb.go
Normal file
@@ -0,0 +1,369 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.2-devel
|
||||
// protoc v3.12.4
|
||||
// source: dhcp_server.proto
|
||||
|
||||
package dhcpServer
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type MacAddr struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Mac string `protobuf:"bytes,1,opt,name=mac,proto3" json:"mac,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MacAddr) Reset() {
|
||||
*x = MacAddr{}
|
||||
mi := &file_dhcp_server_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *MacAddr) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MacAddr) ProtoMessage() {}
|
||||
|
||||
func (x *MacAddr) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_dhcp_server_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use MacAddr.ProtoReflect.Descriptor instead.
|
||||
func (*MacAddr) Descriptor() ([]byte, []int) {
|
||||
return file_dhcp_server_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *MacAddr) GetMac() string {
|
||||
if x != nil {
|
||||
return x.Mac
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// The response message containing the staInfo.
|
||||
type StaDhcpInfo struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"`
|
||||
Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"`
|
||||
}
|
||||
|
||||
func (x *StaDhcpInfo) Reset() {
|
||||
*x = StaDhcpInfo{}
|
||||
mi := &file_dhcp_server_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *StaDhcpInfo) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*StaDhcpInfo) ProtoMessage() {}
|
||||
|
||||
func (x *StaDhcpInfo) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_dhcp_server_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use StaDhcpInfo.ProtoReflect.Descriptor instead.
|
||||
func (*StaDhcpInfo) Descriptor() ([]byte, []int) {
|
||||
return file_dhcp_server_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *StaDhcpInfo) GetIp() string {
|
||||
if x != nil {
|
||||
return x.Ip
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *StaDhcpInfo) GetHostname() string {
|
||||
if x != nil {
|
||||
return x.Hostname
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type EmptyRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *EmptyRequest) Reset() {
|
||||
*x = EmptyRequest{}
|
||||
mi := &file_dhcp_server_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *EmptyRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*EmptyRequest) ProtoMessage() {}
|
||||
|
||||
func (x *EmptyRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_dhcp_server_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use EmptyRequest.ProtoReflect.Descriptor instead.
|
||||
func (*EmptyRequest) Descriptor() ([]byte, []int) {
|
||||
return file_dhcp_server_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
// The response message containing the dhcpInfo.
|
||||
type DhcpInfo struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
UeInfo []*UeInfo `protobuf:"bytes,1,rep,name=ueInfo,proto3" json:"ueInfo,omitempty"`
|
||||
}
|
||||
|
||||
func (x *DhcpInfo) Reset() {
|
||||
*x = DhcpInfo{}
|
||||
mi := &file_dhcp_server_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *DhcpInfo) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DhcpInfo) ProtoMessage() {}
|
||||
|
||||
func (x *DhcpInfo) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_dhcp_server_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DhcpInfo.ProtoReflect.Descriptor instead.
|
||||
func (*DhcpInfo) Descriptor() ([]byte, []int) {
|
||||
return file_dhcp_server_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *DhcpInfo) GetUeInfo() []*UeInfo {
|
||||
if x != nil {
|
||||
return x.UeInfo
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UeInfo struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"`
|
||||
Mac string `protobuf:"bytes,2,opt,name=mac,proto3" json:"mac,omitempty"`
|
||||
Hostname string `protobuf:"bytes,3,opt,name=hostname,proto3" json:"hostname,omitempty"`
|
||||
StartTime string `protobuf:"bytes,4,opt,name=startTime,proto3" json:"startTime,omitempty"`
|
||||
EndTime string `protobuf:"bytes,5,opt,name=endTime,proto3" json:"endTime,omitempty"`
|
||||
}
|
||||
|
||||
func (x *UeInfo) Reset() {
|
||||
*x = UeInfo{}
|
||||
mi := &file_dhcp_server_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *UeInfo) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*UeInfo) ProtoMessage() {}
|
||||
|
||||
func (x *UeInfo) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_dhcp_server_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use UeInfo.ProtoReflect.Descriptor instead.
|
||||
func (*UeInfo) Descriptor() ([]byte, []int) {
|
||||
return file_dhcp_server_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *UeInfo) GetIp() string {
|
||||
if x != nil {
|
||||
return x.Ip
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *UeInfo) GetMac() string {
|
||||
if x != nil {
|
||||
return x.Mac
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *UeInfo) GetHostname() string {
|
||||
if x != nil {
|
||||
return x.Hostname
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *UeInfo) GetStartTime() string {
|
||||
if x != nil {
|
||||
return x.StartTime
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *UeInfo) GetEndTime() string {
|
||||
if x != nil {
|
||||
return x.EndTime
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_dhcp_server_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_dhcp_server_proto_rawDesc = []byte{
|
||||
0x0a, 0x11, 0x64, 0x68, 0x63, 0x70, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x22, 0x1b, 0x0a, 0x07, 0x4d, 0x61, 0x63, 0x41, 0x64, 0x64, 0x72, 0x12, 0x10,
|
||||
0x0a, 0x03, 0x6d, 0x61, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x61, 0x63,
|
||||
0x22, 0x39, 0x0a, 0x0b, 0x53, 0x74, 0x61, 0x44, 0x68, 0x63, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x12,
|
||||
0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12,
|
||||
0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x0e, 0x0a, 0x0c, 0x45,
|
||||
0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2b, 0x0a, 0x08, 0x44,
|
||||
0x68, 0x63, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1f, 0x0a, 0x06, 0x75, 0x65, 0x49, 0x6e, 0x66,
|
||||
0x6f, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x55, 0x65, 0x49, 0x6e, 0x66, 0x6f,
|
||||
0x52, 0x06, 0x75, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x7e, 0x0a, 0x06, 0x55, 0x65, 0x49, 0x6e,
|
||||
0x66, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
|
||||
0x69, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x03, 0x6d, 0x61, 0x63, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x12, 0x1c, 0x0a, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x18,
|
||||
0x0a, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x32, 0x30, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x53,
|
||||
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x22, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61,
|
||||
0x12, 0x08, 0x2e, 0x4d, 0x61, 0x63, 0x41, 0x64, 0x64, 0x72, 0x1a, 0x0c, 0x2e, 0x53, 0x74, 0x61,
|
||||
0x44, 0x68, 0x63, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x00, 0x32, 0x34, 0x0a, 0x0b, 0x44, 0x68,
|
||||
0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x07, 0x47, 0x65, 0x74,
|
||||
0x44, 0x68, 0x63, 0x70, 0x12, 0x0d, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x1a, 0x09, 0x2e, 0x44, 0x68, 0x63, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x00,
|
||||
0x42, 0x0e, 0x5a, 0x0c, 0x2e, 0x2f, 0x64, 0x68, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_dhcp_server_proto_rawDescOnce sync.Once
|
||||
file_dhcp_server_proto_rawDescData = file_dhcp_server_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_dhcp_server_proto_rawDescGZIP() []byte {
|
||||
file_dhcp_server_proto_rawDescOnce.Do(func() {
|
||||
file_dhcp_server_proto_rawDescData = protoimpl.X.CompressGZIP(file_dhcp_server_proto_rawDescData)
|
||||
})
|
||||
return file_dhcp_server_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_dhcp_server_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||
var file_dhcp_server_proto_goTypes = []any{
|
||||
(*MacAddr)(nil), // 0: MacAddr
|
||||
(*StaDhcpInfo)(nil), // 1: StaDhcpInfo
|
||||
(*EmptyRequest)(nil), // 2: EmptyRequest
|
||||
(*DhcpInfo)(nil), // 3: DhcpInfo
|
||||
(*UeInfo)(nil), // 4: UeInfo
|
||||
}
|
||||
var file_dhcp_server_proto_depIdxs = []int32{
|
||||
4, // 0: DhcpInfo.ueInfo:type_name -> UeInfo
|
||||
0, // 1: StaService.GetSta:input_type -> MacAddr
|
||||
2, // 2: DhcpService.GetDhcp:input_type -> EmptyRequest
|
||||
1, // 3: StaService.GetSta:output_type -> StaDhcpInfo
|
||||
3, // 4: DhcpService.GetDhcp:output_type -> DhcpInfo
|
||||
3, // [3:5] is the sub-list for method output_type
|
||||
1, // [1:3] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_dhcp_server_proto_init() }
|
||||
func file_dhcp_server_proto_init() {
|
||||
if File_dhcp_server_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_dhcp_server_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 5,
|
||||
NumExtensions: 0,
|
||||
NumServices: 2,
|
||||
},
|
||||
GoTypes: file_dhcp_server_proto_goTypes,
|
||||
DependencyIndexes: file_dhcp_server_proto_depIdxs,
|
||||
MessageInfos: file_dhcp_server_proto_msgTypes,
|
||||
}.Build()
|
||||
File_dhcp_server_proto = out.File
|
||||
file_dhcp_server_proto_rawDesc = nil
|
||||
file_dhcp_server_proto_goTypes = nil
|
||||
file_dhcp_server_proto_depIdxs = nil
|
||||
}
|
||||
231
grpc_server/dhcpServer/dhcp_server_grpc.pb.go
Normal file
231
grpc_server/dhcpServer/dhcp_server_grpc.pb.go
Normal file
@@ -0,0 +1,231 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v3.12.4
|
||||
// source: dhcp_server.proto
|
||||
|
||||
package dhcpServer
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
StaService_GetSta_FullMethodName = "/StaService/GetSta"
|
||||
)
|
||||
|
||||
// StaServiceClient is the client API for StaService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
//
|
||||
// The get sta service definition.
|
||||
type StaServiceClient interface {
|
||||
GetSta(ctx context.Context, in *MacAddr, opts ...grpc.CallOption) (*StaDhcpInfo, error)
|
||||
}
|
||||
|
||||
type staServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewStaServiceClient(cc grpc.ClientConnInterface) StaServiceClient {
|
||||
return &staServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *staServiceClient) GetSta(ctx context.Context, in *MacAddr, opts ...grpc.CallOption) (*StaDhcpInfo, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(StaDhcpInfo)
|
||||
err := c.cc.Invoke(ctx, StaService_GetSta_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// StaServiceServer is the server API for StaService service.
|
||||
// All implementations must embed UnimplementedStaServiceServer
|
||||
// for forward compatibility.
|
||||
//
|
||||
// The get sta service definition.
|
||||
type StaServiceServer interface {
|
||||
GetSta(context.Context, *MacAddr) (*StaDhcpInfo, error)
|
||||
mustEmbedUnimplementedStaServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedStaServiceServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedStaServiceServer struct{}
|
||||
|
||||
func (UnimplementedStaServiceServer) GetSta(context.Context, *MacAddr) (*StaDhcpInfo, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetSta not implemented")
|
||||
}
|
||||
func (UnimplementedStaServiceServer) mustEmbedUnimplementedStaServiceServer() {}
|
||||
func (UnimplementedStaServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeStaServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to StaServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeStaServiceServer interface {
|
||||
mustEmbedUnimplementedStaServiceServer()
|
||||
}
|
||||
|
||||
func RegisterStaServiceServer(s grpc.ServiceRegistrar, srv StaServiceServer) {
|
||||
// If the following call panics, it indicates UnimplementedStaServiceServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&StaService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _StaService_GetSta_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(MacAddr)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StaServiceServer).GetSta(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StaService_GetSta_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StaServiceServer).GetSta(ctx, req.(*MacAddr))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// StaService_ServiceDesc is the grpc.ServiceDesc for StaService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var StaService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "StaService",
|
||||
HandlerType: (*StaServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "GetSta",
|
||||
Handler: _StaService_GetSta_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "dhcp_server.proto",
|
||||
}
|
||||
|
||||
const (
|
||||
DhcpService_GetDhcp_FullMethodName = "/DhcpService/GetDhcp"
|
||||
)
|
||||
|
||||
// DhcpServiceClient is the client API for DhcpService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
//
|
||||
// The get dhcp service definition.
|
||||
type DhcpServiceClient interface {
|
||||
GetDhcp(ctx context.Context, in *EmptyRequest, opts ...grpc.CallOption) (*DhcpInfo, error)
|
||||
}
|
||||
|
||||
type dhcpServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewDhcpServiceClient(cc grpc.ClientConnInterface) DhcpServiceClient {
|
||||
return &dhcpServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *dhcpServiceClient) GetDhcp(ctx context.Context, in *EmptyRequest, opts ...grpc.CallOption) (*DhcpInfo, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(DhcpInfo)
|
||||
err := c.cc.Invoke(ctx, DhcpService_GetDhcp_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// DhcpServiceServer is the server API for DhcpService service.
|
||||
// All implementations must embed UnimplementedDhcpServiceServer
|
||||
// for forward compatibility.
|
||||
//
|
||||
// The get dhcp service definition.
|
||||
type DhcpServiceServer interface {
|
||||
GetDhcp(context.Context, *EmptyRequest) (*DhcpInfo, error)
|
||||
mustEmbedUnimplementedDhcpServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedDhcpServiceServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedDhcpServiceServer struct{}
|
||||
|
||||
func (UnimplementedDhcpServiceServer) GetDhcp(context.Context, *EmptyRequest) (*DhcpInfo, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetDhcp not implemented")
|
||||
}
|
||||
func (UnimplementedDhcpServiceServer) mustEmbedUnimplementedDhcpServiceServer() {}
|
||||
func (UnimplementedDhcpServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeDhcpServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to DhcpServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeDhcpServiceServer interface {
|
||||
mustEmbedUnimplementedDhcpServiceServer()
|
||||
}
|
||||
|
||||
func RegisterDhcpServiceServer(s grpc.ServiceRegistrar, srv DhcpServiceServer) {
|
||||
// If the following call panics, it indicates UnimplementedDhcpServiceServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&DhcpService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _DhcpService_GetDhcp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(EmptyRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(DhcpServiceServer).GetDhcp(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: DhcpService_GetDhcp_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(DhcpServiceServer).GetDhcp(ctx, req.(*EmptyRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// DhcpService_ServiceDesc is the grpc.ServiceDesc for DhcpService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var DhcpService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "DhcpService",
|
||||
HandlerType: (*DhcpServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "GetDhcp",
|
||||
Handler: _DhcpService_GetDhcp_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "dhcp_server.proto",
|
||||
}
|
||||
54
grpc_server/grpc_server.go
Normal file
54
grpc_server/grpc_server.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package grpcServer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/coredhcp/coredhcp/grpc_server/dhcpServer"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
rangeplugin "github.com/coredhcp/coredhcp/plugins/range"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("grpc")
|
||||
|
||||
type staServer struct {
|
||||
dhcpServer.UnimplementedStaServiceServer
|
||||
}
|
||||
|
||||
func (s *staServer) GetSta(ctx context.Context, req *dhcpServer.MacAddr) (*dhcpServer.StaDhcpInfo, error) {
|
||||
record := rangeplugin.GetRecord(req.Mac)
|
||||
if record != nil {
|
||||
return &dhcpServer.StaDhcpInfo{
|
||||
Ip: record.IP.String(),
|
||||
Hostname: record.Hostname,
|
||||
}, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
type dhcpService struct {
|
||||
dhcpServer.UnimplementedDhcpServiceServer
|
||||
}
|
||||
|
||||
func (d *dhcpService) GetDhcp(ctx context.Context, req *dhcpServer.EmptyRequest) (dhcpInfo *dhcpServer.DhcpInfo, err error) {
|
||||
return rangeplugin.GetDhcpInfo()
|
||||
}
|
||||
|
||||
func Run() {
|
||||
lis, err := net.Listen("tcp", "127.0.0.1:50051")
|
||||
if err != nil {
|
||||
log.Errorf("failed to listen: %v", err)
|
||||
}
|
||||
|
||||
s := grpc.NewServer()
|
||||
|
||||
dhcpServer.RegisterStaServiceServer(s, &staServer{})
|
||||
dhcpServer.RegisterDhcpServiceServer(s, &dhcpService{})
|
||||
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Errorf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
23
handler/handler.go
Normal file
23
handler/handler.go
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
)
|
||||
|
||||
// Handler6 is a function that is called on a given DHCPv6 packet.
|
||||
// It returns a DHCPv6 packet and a boolean.
|
||||
// If the boolean is true, this will be the last handler to be called.
|
||||
// The two input packets are the original request, and a response packet.
|
||||
// The response packet may or may not be modified by the function, and
|
||||
// the result will be returned by the handler.
|
||||
// If the returned boolean is true, the returned packet may be nil or
|
||||
// invalid, in which case no response will be sent.
|
||||
type Handler6 func(req, resp dhcpv6.DHCPv6) (dhcpv6.DHCPv6, bool)
|
||||
|
||||
// Handler4 behaves like Handler6, but for DHCPv4 packets.
|
||||
type Handler4 func(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool)
|
||||
1
integ/server6/leases-dhcpv6-test.txt
Normal file
1
integ/server6/leases-dhcpv6-test.txt
Normal file
@@ -0,0 +1 @@
|
||||
de:ad:be:ef:00:00 2001:db8::10:1
|
||||
129
integ/server6/server6.go
Normal file
129
integ/server6/server6.go
Normal file
@@ -0,0 +1,129 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
//go:build integration
|
||||
// +build integration
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"runtime"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6/client6"
|
||||
"github.com/insomniacslk/dhcp/iana"
|
||||
"github.com/vishvananda/netns"
|
||||
|
||||
"github.com/coredhcp/coredhcp/config"
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
"github.com/coredhcp/coredhcp/server"
|
||||
|
||||
// Plugins
|
||||
"github.com/coredhcp/coredhcp/plugins/file"
|
||||
"github.com/coredhcp/coredhcp/plugins/serverid"
|
||||
)
|
||||
|
||||
var serverConfig = config.Config{
|
||||
Server6: &config.ServerConfig{
|
||||
Addresses: []net.UDPAddr{
|
||||
{
|
||||
IP: net.ParseIP("ff02::1:2"),
|
||||
Port: dhcpv6.DefaultServerPort,
|
||||
Zone: "cdhcp_srv",
|
||||
},
|
||||
},
|
||||
Plugins: []config.PluginConfig{
|
||||
{Name: "server_id", Args: []string{"LL", "11:22:33:44:55:66"}},
|
||||
{Name: "file", Args: []string{"./leases-dhcpv6-test.txt"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// This function *must* be run in its own routine
|
||||
// For now this assumes ns are created outside.
|
||||
// TODO: dynamically create NS and interfaces directly in the test program
|
||||
func runServer(readyCh chan<- struct{}, nsName string, desiredPlugins []*plugins.Plugin) {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
ns, err := netns.GetFromName(nsName)
|
||||
if err != nil {
|
||||
log.Panicf("Netns `%s` not set up: %v", nsName, err)
|
||||
}
|
||||
if err := netns.Set(ns); err != nil {
|
||||
log.Panicf("Failed to switch to netns `%s`: %v", nsName, err)
|
||||
}
|
||||
// register plugins
|
||||
for _, pl := range desiredPlugins {
|
||||
if err := plugins.RegisterPlugin(pl); err != nil {
|
||||
log.Panicf("Failed to register plugin `%s`: %v", pl.Name, err)
|
||||
}
|
||||
}
|
||||
// start DHCP server
|
||||
srv, err := server.Start(&serverConfig)
|
||||
if err != nil {
|
||||
log.Panicf("Server could not start: %v", err)
|
||||
}
|
||||
readyCh <- struct{}{}
|
||||
if err := srv.Wait(); err != nil {
|
||||
log.Panicf("Server errored during run: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// runInNs will execute the provided cmd in the namespace nsName.
|
||||
// It returns the error status of the cmd. Errors in NS management will panic
|
||||
func runClient6(nsName, iface string, modifiers ...dhcpv6.Modifier) error {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
backupNS, err := netns.Get()
|
||||
if err != nil {
|
||||
panic("Could not save handle to original NS")
|
||||
}
|
||||
|
||||
ns, err := netns.GetFromName(nsName)
|
||||
if err != nil {
|
||||
panic("netns not set up")
|
||||
}
|
||||
if err := netns.Set(ns); err != nil {
|
||||
panic(fmt.Sprintf("Couldn't switch to test NS: %v", err))
|
||||
}
|
||||
|
||||
client := client6.NewClient()
|
||||
_, cErr := client.Exchange(iface, modifiers...)
|
||||
|
||||
if netns.Set(backupNS) != nil {
|
||||
panic("couldn't switch back to original NS")
|
||||
}
|
||||
|
||||
return cErr
|
||||
}
|
||||
|
||||
// Create a server and run a DORA exchange with it
|
||||
func main() {
|
||||
readyCh := make(chan struct{}, 1)
|
||||
go runServer(readyCh,
|
||||
"coredhcp-direct-upper",
|
||||
[]*plugins.Plugin{
|
||||
&serverid.Plugin, &file.Plugin,
|
||||
},
|
||||
)
|
||||
// wait for server to be ready before sending DHCP request
|
||||
<-readyCh
|
||||
mac, err := net.ParseMAC("de:ad:be:ef:00:00")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = runClient6(
|
||||
"coredhcp-direct-lower", "cdhcp_cli",
|
||||
dhcpv6.WithClientID(&dhcpv6.DUIDLL{
|
||||
HWType: iana.HWTypeEthernet,
|
||||
LinkLayerAddr: mac,
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
46
logger/logger.go
Normal file
46
logger/logger.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package logger
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
log_prefixed "github.com/chappjc/logrus-prefix"
|
||||
"github.com/rifflock/lfshook"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
globalLogger *logrus.Logger
|
||||
getLoggerMutex sync.Mutex
|
||||
)
|
||||
|
||||
// GetLogger returns a configured logger instance
|
||||
func GetLogger(prefix string) *logrus.Entry {
|
||||
if prefix == "" {
|
||||
prefix = "<no prefix>"
|
||||
}
|
||||
if globalLogger == nil {
|
||||
getLoggerMutex.Lock()
|
||||
defer getLoggerMutex.Unlock()
|
||||
logger := logrus.New()
|
||||
logger.SetFormatter(&log_prefixed.TextFormatter{
|
||||
FullTimestamp: true,
|
||||
})
|
||||
globalLogger = logger
|
||||
}
|
||||
return globalLogger.WithField("prefix", prefix)
|
||||
}
|
||||
|
||||
// WithFile logs to the specified file in addition to the existing output.
|
||||
func WithFile(log *logrus.Entry, logfile string) {
|
||||
log.Logger.AddHook(lfshook.NewHook(logfile, &logrus.TextFormatter{}))
|
||||
}
|
||||
|
||||
// WithNoStdOutErr disables logging to stdout/stderr.
|
||||
func WithNoStdOutErr(log *logrus.Entry) {
|
||||
log.Logger.SetOutput(io.Discard)
|
||||
}
|
||||
48
plugins/allocators/allocator.go
Normal file
48
plugins/allocators/allocator.go
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
// Package allocators provides the interface and the algorithm(s) for allocation of ipv6
|
||||
// prefixes of various sizes within a larger prefix.
|
||||
// There are many many parallels with memory allocation.
|
||||
package allocators
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
// Allocator is the interface to the address allocator. It only finds and
|
||||
// allocates blocks and is not concerned with lease-specific questions like
|
||||
// expiration (ie garbage collection needs to be handled separately)
|
||||
type Allocator interface {
|
||||
// Allocate finds a suitable prefix of the given size and returns it.
|
||||
//
|
||||
// hint is a prefix, which the client desires especially, and that the
|
||||
// allocator MAY try to return; the allocator SHOULD try to return a prefix of
|
||||
// the same size as the given hint prefix. The allocator MUST NOT return an
|
||||
// error if a prefix was successfully assigned, even if the prefix has nothing
|
||||
// in common with the hinted prefix
|
||||
Allocate(hint net.IPNet) (net.IPNet, error)
|
||||
|
||||
// Free returns the prefix containing the given network to the pool
|
||||
//
|
||||
// Free may return a DoubleFreeError if the prefix being returned was not
|
||||
// previously allocated
|
||||
Free(net.IPNet) error
|
||||
}
|
||||
|
||||
// ErrDoubleFree is an error type returned by Allocator.Free() when a
|
||||
// non-allocated block is passed
|
||||
type ErrDoubleFree struct {
|
||||
Loc net.IPNet
|
||||
}
|
||||
|
||||
// String returns a human-readable error message for a DoubleFree error
|
||||
func (err *ErrDoubleFree) Error() string {
|
||||
return fmt.Sprint("Attempted to free unallocated block at ", err.Loc.String())
|
||||
}
|
||||
|
||||
// ErrNoAddrAvail is returned when we can't allocate an IP because there's no unallocated space left
|
||||
var ErrNoAddrAvail = errors.New("no address available to allocate")
|
||||
135
plugins/allocators/bitmap/bitmap.go
Normal file
135
plugins/allocators/bitmap/bitmap.go
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
// This allocator only returns prefixes of a single size
|
||||
// This is much simpler to implement (reduces the problem to an equivalent of
|
||||
// single ip allocations), probably makes sense in cases where the available
|
||||
// range is much larger than the expected number of clients. Also is what KEA
|
||||
// does so at least it's not worse than that
|
||||
|
||||
package bitmap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/bits-and-blooms/bitset"
|
||||
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/plugins/allocators"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("plugins/allocators/bitmap")
|
||||
|
||||
// Allocator is a prefix allocator allocating in chunks of a fixed size
|
||||
// regardless of the size requested by the client.
|
||||
// It consumes an amount of memory proportional to the total amount of available prefixes
|
||||
type Allocator struct {
|
||||
containing net.IPNet
|
||||
page int
|
||||
bitmap *bitset.BitSet
|
||||
l sync.Mutex
|
||||
}
|
||||
|
||||
// prefix must verify: containing.Mask.Size < prefix.Mask.Size < page
|
||||
func (a *Allocator) toIndex(base net.IP) (uint, error) {
|
||||
value, err := allocators.Offset(base, a.containing.IP, a.page)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Cannot compute prefix index: %w", err)
|
||||
}
|
||||
|
||||
return uint(value), nil
|
||||
}
|
||||
|
||||
func (a *Allocator) toPrefix(idx uint) (net.IP, error) {
|
||||
return allocators.AddPrefixes(a.containing.IP, uint64(idx), uint64(a.page))
|
||||
}
|
||||
|
||||
// Allocate reserves a maxsize-sized block and returns a block of size
|
||||
// min(maxsize, hint.size)
|
||||
func (a *Allocator) Allocate(hint net.IPNet) (ret net.IPNet, err error) {
|
||||
|
||||
// Ensure size is max(maxsize, hint.size)
|
||||
reqSize, hintErr := hint.Mask.Size()
|
||||
if reqSize < a.page || hintErr != 128 {
|
||||
reqSize = a.page
|
||||
}
|
||||
ret.Mask = net.CIDRMask(reqSize, 128)
|
||||
|
||||
// Try to allocate the requested prefix
|
||||
a.l.Lock()
|
||||
defer a.l.Unlock()
|
||||
if hint.IP.To16() != nil && a.containing.Contains(hint.IP) {
|
||||
idx, hintErr := a.toIndex(hint.IP)
|
||||
if hintErr == nil && !a.bitmap.Test(idx) {
|
||||
a.bitmap.Set(idx)
|
||||
ret.IP, err = a.toPrefix(idx)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Find a free prefix
|
||||
next, ok := a.bitmap.NextClear(0)
|
||||
if !ok {
|
||||
err = allocators.ErrNoAddrAvail
|
||||
return
|
||||
}
|
||||
a.bitmap.Set(next)
|
||||
ret.IP, err = a.toPrefix(next)
|
||||
if err != nil {
|
||||
// This violates the assumption that every index in the bitmap maps back to a valid prefix
|
||||
err = fmt.Errorf("BUG: could not get prefix from allocation: %w", err)
|
||||
a.bitmap.Clear(next)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Free returns the given prefix to the available pool if it was taken.
|
||||
func (a *Allocator) Free(prefix net.IPNet) error {
|
||||
idx, err := a.toIndex(prefix.IP.Mask(prefix.Mask))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not find prefix in pool: %w", err)
|
||||
}
|
||||
|
||||
a.l.Lock()
|
||||
defer a.l.Unlock()
|
||||
|
||||
if !a.bitmap.Test(idx) {
|
||||
return &allocators.ErrDoubleFree{Loc: prefix}
|
||||
}
|
||||
a.bitmap.Clear(idx)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewBitmapAllocator creates a new allocator, allocating /`size` prefixes
|
||||
// carved out of the given `pool` prefix
|
||||
func NewBitmapAllocator(pool net.IPNet, size int) (*Allocator, error) {
|
||||
|
||||
poolSize, _ := pool.Mask.Size()
|
||||
allocOrder := size - poolSize
|
||||
|
||||
if allocOrder < 0 {
|
||||
return nil, errors.New("The size of allocated prefixes cannot be larger than the pool they're allocated from")
|
||||
} else if allocOrder >= strconv.IntSize {
|
||||
return nil, fmt.Errorf("A pool with more than 2^%d items is not representable", size-poolSize)
|
||||
} else if allocOrder >= 32 {
|
||||
log.Warningln("Using a pool of more than 2^32 elements may result in large memory consumption")
|
||||
}
|
||||
|
||||
if !(1<<uint(allocOrder) <= bitset.Cap()) {
|
||||
return nil, errors.New("Can't fit this pool using the bitmap allocator")
|
||||
}
|
||||
|
||||
alloc := Allocator{
|
||||
containing: pool,
|
||||
page: size,
|
||||
|
||||
bitmap: bitset.New(1 << uint(allocOrder)),
|
||||
}
|
||||
|
||||
return &alloc, nil
|
||||
}
|
||||
122
plugins/allocators/bitmap/bitmap_ipv4.go
Normal file
122
plugins/allocators/bitmap/bitmap_ipv4.go
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package bitmap
|
||||
|
||||
// This allocator handles IPv4 assignments with a similar logic to the base bitmap, but a simpler
|
||||
// implementation due to the ability to just use uint32 for IPv4 addresses
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/bits-and-blooms/bitset"
|
||||
"github.com/coredhcp/coredhcp/plugins/allocators"
|
||||
)
|
||||
|
||||
var (
|
||||
errNotInRange = errors.New("IPv4 address outside of allowed range")
|
||||
errInvalidIP = errors.New("invalid IPv4 address passed as input")
|
||||
)
|
||||
|
||||
// IPv4Allocator allocates IPv4 addresses, tracking utilization with a bitmap
|
||||
type IPv4Allocator struct {
|
||||
start uint32
|
||||
end uint32
|
||||
|
||||
// This bitset implementation isn't goroutine-safe, we protect it with a mutex for now
|
||||
// until we can swap for another concurrent implementation
|
||||
bitmap *bitset.BitSet
|
||||
l sync.Mutex
|
||||
}
|
||||
|
||||
func (a *IPv4Allocator) toIP(offset uint32) net.IP {
|
||||
if offset > a.end-a.start {
|
||||
panic("BUG: offset out of bounds")
|
||||
}
|
||||
|
||||
r := make(net.IP, net.IPv4len)
|
||||
binary.BigEndian.PutUint32(r, a.start+offset)
|
||||
return r
|
||||
}
|
||||
|
||||
func (a *IPv4Allocator) toOffset(ip net.IP) (uint, error) {
|
||||
if ip.To4() == nil {
|
||||
return 0, errInvalidIP
|
||||
}
|
||||
|
||||
intIP := binary.BigEndian.Uint32(ip.To4())
|
||||
if intIP < a.start || intIP > a.end {
|
||||
return 0, errNotInRange
|
||||
}
|
||||
|
||||
return uint(intIP - a.start), nil
|
||||
}
|
||||
|
||||
// Allocate reserves an IP for a client
|
||||
func (a *IPv4Allocator) Allocate(hint net.IPNet) (n net.IPNet, err error) {
|
||||
n.Mask = net.CIDRMask(32, 32)
|
||||
|
||||
// This is just a hint, ignore any error with it
|
||||
hintOffset, _ := a.toOffset(hint.IP)
|
||||
|
||||
a.l.Lock()
|
||||
defer a.l.Unlock()
|
||||
|
||||
var next uint
|
||||
// First try the exact match
|
||||
if !a.bitmap.Test(hintOffset) {
|
||||
next = hintOffset
|
||||
} else {
|
||||
// Then any available address
|
||||
avail, ok := a.bitmap.NextClear(0)
|
||||
if !ok {
|
||||
return n, allocators.ErrNoAddrAvail
|
||||
}
|
||||
next = avail
|
||||
}
|
||||
|
||||
a.bitmap.Set(next)
|
||||
n.IP = a.toIP(uint32(next))
|
||||
return
|
||||
}
|
||||
|
||||
// Free releases the given IP
|
||||
func (a *IPv4Allocator) Free(n net.IPNet) error {
|
||||
offset, err := a.toOffset(n.IP)
|
||||
if err != nil {
|
||||
return errNotInRange
|
||||
}
|
||||
|
||||
a.l.Lock()
|
||||
defer a.l.Unlock()
|
||||
|
||||
if !a.bitmap.Test(uint(offset)) {
|
||||
return &allocators.ErrDoubleFree{Loc: n}
|
||||
}
|
||||
a.bitmap.Clear(offset)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewIPv4Allocator creates a new allocator suitable for giving out IPv4 addresses
|
||||
func NewIPv4Allocator(start, end net.IP) (*IPv4Allocator, error) {
|
||||
if start.To4() == nil || end.To4() == nil {
|
||||
return nil, fmt.Errorf("invalid IPv4 addresses given to create the allocator: [%s,%s]", start, end)
|
||||
}
|
||||
|
||||
alloc := IPv4Allocator{
|
||||
start: binary.BigEndian.Uint32(start.To4()),
|
||||
end: binary.BigEndian.Uint32(end.To4()),
|
||||
}
|
||||
|
||||
if alloc.start > alloc.end {
|
||||
return nil, errors.New("no IPs in the given range to allocate")
|
||||
}
|
||||
alloc.bitmap = bitset.New(uint(alloc.end - alloc.start + 1))
|
||||
|
||||
return &alloc, nil
|
||||
}
|
||||
63
plugins/allocators/bitmap/bitmap_ipv4_test.go
Normal file
63
plugins/allocators/bitmap/bitmap_ipv4_test.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package bitmap
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func getv4Allocator() *IPv4Allocator {
|
||||
alloc, err := NewIPv4Allocator(net.IPv4(192, 0, 2, 0), net.IPv4(192, 0, 2, 255))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return alloc
|
||||
}
|
||||
func Test4Alloc(t *testing.T) {
|
||||
alloc := getv4Allocator()
|
||||
|
||||
net1, err := alloc.Allocate(net.IPNet{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
net2, err := alloc.Allocate(net.IPNet{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if net1.IP.Equal(net2.IP) {
|
||||
t.Fatal("That address was already allocated")
|
||||
}
|
||||
|
||||
err = alloc.Free(net1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = alloc.Free(net1)
|
||||
if err == nil {
|
||||
t.Fatal("Expected DoubleFree error")
|
||||
}
|
||||
}
|
||||
|
||||
func Test4OutOfPool(t *testing.T) {
|
||||
alloc := getv4Allocator()
|
||||
|
||||
hint := net.IPv4(198, 51, 100, 5)
|
||||
res, err := alloc.Allocate(net.IPNet{IP: hint, Mask: net.CIDRMask(32, 32)})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to allocate with invalid hint: %v", err)
|
||||
}
|
||||
_, prefix, _ := net.ParseCIDR("192.0.2.0/24")
|
||||
if !prefix.Contains(res.IP) {
|
||||
t.Fatal("Obtained prefix outside of range: ", res)
|
||||
}
|
||||
if prefLen, totalLen := res.Mask.Size(); prefLen != 32 || totalLen != 32 {
|
||||
t.Fatalf("Prefixes have wrong size %d/%d", prefLen, totalLen)
|
||||
}
|
||||
}
|
||||
137
plugins/allocators/bitmap/bitmap_test.go
Normal file
137
plugins/allocators/bitmap/bitmap_test.go
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package bitmap
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/bits-and-blooms/bitset"
|
||||
)
|
||||
|
||||
func getAllocator(bits int) *Allocator {
|
||||
_, prefix, err := net.ParseCIDR("2001:db8::/56")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
alloc, err := NewBitmapAllocator(*prefix, 56+bits)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return alloc
|
||||
}
|
||||
func TestAlloc(t *testing.T) {
|
||||
alloc := getAllocator(8)
|
||||
|
||||
net, err := alloc.Allocate(net.IPNet{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = alloc.Free(net)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = alloc.Free(net)
|
||||
if err == nil {
|
||||
t.Fatal("Expected DoubleFree error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExhaust(t *testing.T) {
|
||||
_, prefix, _ := net.ParseCIDR("2001:db8::/62")
|
||||
alloc, _ := NewBitmapAllocator(*prefix, 64)
|
||||
|
||||
allocd := []net.IPNet{}
|
||||
for i := 0; i < 4; i++ {
|
||||
net, err := alloc.Allocate(net.IPNet{Mask: net.CIDRMask(64, 128)})
|
||||
if err != nil {
|
||||
t.Fatalf("Error before exhaustion: %v", err)
|
||||
}
|
||||
allocd = append(allocd, net)
|
||||
}
|
||||
|
||||
_, err := alloc.Allocate(net.IPNet{})
|
||||
if err == nil {
|
||||
t.Fatalf("Successfully allocated more prefixes than there are in the pool")
|
||||
}
|
||||
|
||||
err = alloc.Free(allocd[1])
|
||||
if err != nil {
|
||||
t.Fatalf("Could not free: %v", err)
|
||||
}
|
||||
net, err := alloc.Allocate(allocd[1])
|
||||
if err != nil {
|
||||
t.Fatalf("Could not reallocate after free: %v", err)
|
||||
}
|
||||
if !net.IP.Equal(allocd[1].IP) || net.Mask.String() != allocd[1].Mask.String() {
|
||||
t.Fatalf("Did not obtain the right network after free: got %v, expected %v", net, allocd[1])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestOutOfPool(t *testing.T) {
|
||||
alloc := getAllocator(8)
|
||||
_, prefix, _ := net.ParseCIDR("fe80:abcd::/48")
|
||||
|
||||
res, err := alloc.Allocate(*prefix)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to allocate with invalid hint: %v", err)
|
||||
}
|
||||
if !alloc.containing.Contains(res.IP) {
|
||||
t.Fatal("Obtained prefix outside of range: ", res)
|
||||
}
|
||||
if prefLen, totalLen := res.Mask.Size(); prefLen != 64 || totalLen != 128 {
|
||||
t.Fatalf("Prefixes have wrong size %d/%d", prefLen, totalLen)
|
||||
}
|
||||
}
|
||||
|
||||
func prefixSizeForAllocs(allocs int) int {
|
||||
return int(math.Ceil(math.Log2(float64(allocs))))
|
||||
}
|
||||
|
||||
// Benchmark parallel Allocate, when the bitmap is mostly empty and we're allocating few values
|
||||
// compared to the available allocations
|
||||
func BenchmarkParallelAllocInitiallyEmpty(b *testing.B) {
|
||||
// Run with -race to debug concurrency issues
|
||||
|
||||
alloc := getAllocator(prefixSizeForAllocs(b.N) + 2) // Use max 25% of the bitmap (initially empty)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if net, err := alloc.Allocate(net.IPNet{}); err != nil {
|
||||
b.Logf("Could not allocate (got %v and an error): %v", net, err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkParallelAllocPartiallyFilled(b *testing.B) {
|
||||
// We'll make a bitmap with 2x the number of allocs we want to make.
|
||||
// Then randomly fill it to about 50% utilization
|
||||
alloc := getAllocator(prefixSizeForAllocs(b.N) + 1)
|
||||
|
||||
// Build a replacement bitmap that we'll put in the allocator, with approx. 50% of values filled
|
||||
newbmap := make([]uint64, alloc.bitmap.Len())
|
||||
for i := uint(0); i < alloc.bitmap.Len(); i++ {
|
||||
newbmap[i] = rand.Uint64()
|
||||
}
|
||||
alloc.bitmap = bitset.From(newbmap)
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if net, err := alloc.Allocate(net.IPNet{}); err != nil {
|
||||
b.Logf("Could not allocate (got %v and an error): %v", net, err)
|
||||
b.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
126
plugins/allocators/ipcalc.go
Normal file
126
plugins/allocators/ipcalc.go
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
// Provides functions to add/subtract ipv6 addresses, for use in offset
|
||||
// calculations in allocators
|
||||
|
||||
package allocators
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/bits"
|
||||
"net"
|
||||
)
|
||||
|
||||
// ErrOverflow is returned when arithmetic operations on IPs carry bits
|
||||
// over/under the 0th or 128th bit respectively
|
||||
var ErrOverflow = errors.New("Operation overflows")
|
||||
|
||||
// Offset returns the absolute distance between addresses `a` and `b` in units
|
||||
// of /`prefixLength` subnets.
|
||||
// Both addresses will have a /`prefixLength` mask applied to them, any
|
||||
// differences of less than that will be discarded
|
||||
// If the distance is larger than 2^64 units of /`prefixLength` an error is returned
|
||||
//
|
||||
// This function is used in allocators to index bitmaps by an offset from the
|
||||
// first ip of the range
|
||||
func Offset(a, b net.IP, prefixLength int) (uint64, error) {
|
||||
if prefixLength > 128 || prefixLength < 0 {
|
||||
return 0, errors.New("prefix out of range")
|
||||
}
|
||||
|
||||
reverse := bytes.Compare(a, b)
|
||||
if reverse == 0 {
|
||||
return 0, nil
|
||||
} else if reverse < 0 {
|
||||
a, b = b, a
|
||||
}
|
||||
|
||||
// take an example of [a:b:c:d:e:f:g:h] [1:2:3:4:5:6:7:8]
|
||||
// Cut the addresses as such: [a:b:c:d|e:f:g:h] [1:2:3:4|5:6:7:8] so we can use
|
||||
// native integers for computation
|
||||
ah, bh := binary.BigEndian.Uint64(a[:8]), binary.BigEndian.Uint64(b[:8])
|
||||
|
||||
if prefixLength <= 64 {
|
||||
// [(a:b:c):d|e:f:g:h] - [(1:2:3):4|5:6:7:8]
|
||||
// Only the high bits matter, so the distance always fits within 64 bits.
|
||||
// We shift to remove anything to the right of the cut
|
||||
// [(a:b:c):d] => [0:a:b:c]
|
||||
return (ah - bh) >> (64 - uint(prefixLength)), nil
|
||||
}
|
||||
|
||||
// General case where both high and low bits matter
|
||||
al, bl := binary.BigEndian.Uint64(a[8:]), binary.BigEndian.Uint64(b[8:])
|
||||
distanceLow, borrow := bits.Sub64(al, bl, 0)
|
||||
|
||||
// This is the distance between the high bits. depending on the prefix unit, we
|
||||
// will shift this distance left or right
|
||||
distanceHigh, _ := bits.Sub64(ah, bh, borrow) // [a:b:c:d] - [1:2:3:4]
|
||||
|
||||
// [a:b:c:(d|e:f:g):h] - [1:2:3:(4|5:6:7):8]
|
||||
// we cut in the low bits (eg. between the parentheses)
|
||||
// To ensure we stay within 64 bits, we need to ensure [a:b:c:d] - [1:2:3:4] = [0:0:0:d-4]
|
||||
// so that we don't overflow when adding to the low bits
|
||||
if distanceHigh >= (1 << (128 - uint(prefixLength))) {
|
||||
return 0, ErrOverflow
|
||||
}
|
||||
|
||||
// Schema of the carry and shifts:
|
||||
// [a:b:c:(d]
|
||||
// [e:f:g):h]
|
||||
// <---------------> prefixLen
|
||||
// <-> 128 - prefixLen (cut right)
|
||||
// <-----> prefixLen - 64 (cut left)
|
||||
//
|
||||
// [a:b:c:(d] => [d:0:0:0]
|
||||
distanceHigh <<= uint(prefixLength) - 64
|
||||
// [e:f:g):h] => [0:e:f:g]
|
||||
distanceLow >>= 128 - uint(prefixLength)
|
||||
// [d:0:0:0] + [0:e:f:g] = (d:e:f:g)
|
||||
return distanceHigh + distanceLow, nil
|
||||
}
|
||||
|
||||
// AddPrefixes returns the `n`th /`unit` subnet after the `ip` base subnet. It
|
||||
// is the converse operation of Offset(), used to retrieve a prefix from the
|
||||
// index within the allocator table
|
||||
func AddPrefixes(ip net.IP, n, unit uint64) (net.IP, error) {
|
||||
if unit == 0 && n != 0 {
|
||||
return net.IP{}, ErrOverflow
|
||||
} else if n == 0 {
|
||||
return ip, nil
|
||||
}
|
||||
if len(ip) != 16 {
|
||||
// We don't actually care if they're true v6 or v4-mapped,
|
||||
// but they need to be 128-bit to handle as 64-bit ints
|
||||
return net.IP{}, errors.New("AddPrefixes needs 128-bit IPs")
|
||||
}
|
||||
|
||||
// Compute as pairs of uint64 for easier operations
|
||||
// This could all be 1 function call if go had 128-bit integers
|
||||
iph, ipl := binary.BigEndian.Uint64(ip[:8]), binary.BigEndian.Uint64(ip[8:])
|
||||
|
||||
// Compute `n` /`unit` subnets as uint64 pair
|
||||
var offh, offl uint64
|
||||
if unit <= 64 {
|
||||
offh = n << (64 - unit)
|
||||
} else {
|
||||
offh, offl = bits.Mul64(n, 1<<(128-unit))
|
||||
}
|
||||
|
||||
// Now add the 2, check for overflow
|
||||
ipl, carry := bits.Add64(offl, ipl, 0)
|
||||
iph, carry = bits.Add64(offh, iph, carry)
|
||||
if carry != 0 {
|
||||
return net.IP{}, ErrOverflow
|
||||
}
|
||||
|
||||
// Finally convert back to net.IP
|
||||
ret := make(net.IP, net.IPv6len)
|
||||
binary.BigEndian.PutUint64(ret[:8], iph)
|
||||
binary.BigEndian.PutUint64(ret[8:], ipl)
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
77
plugins/allocators/ipcalc_test.go
Normal file
77
plugins/allocators/ipcalc_test.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package allocators
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func ExampleOffset() {
|
||||
fmt.Println(Offset(net.ParseIP("2001:db8:0:aabb::"), net.ParseIP("2001:db8:ff::34"), 0))
|
||||
fmt.Println(Offset(net.ParseIP("2001:db8:0:aabb::"), net.ParseIP("2001:db8:ff::34"), 16))
|
||||
fmt.Println(Offset(net.ParseIP("2001:db8:0:aabb::"), net.ParseIP("2001:db8:ff::34"), 32))
|
||||
fmt.Println(Offset(net.ParseIP("2001:db8:0:aabb::"), net.ParseIP("2001:db8:ff::34"), 48))
|
||||
fmt.Println(Offset(net.ParseIP("2001:db8:0:aabb::"), net.ParseIP("2001:db8:ff::34"), 64))
|
||||
fmt.Println(Offset(net.ParseIP("2001:db8:0:aabb::"), net.ParseIP("2001:db8:ff::34"), 73))
|
||||
fmt.Println(Offset(net.ParseIP("2001:db8:0:aabb::"), net.ParseIP("2001:db8:ff::34"), 80))
|
||||
fmt.Println(Offset(net.ParseIP("2001:db8:0:aabb::"), net.ParseIP("2001:db8:ff::34"), 96))
|
||||
fmt.Println(Offset(net.ParseIP("2001:db8:0:aabb::"), net.ParseIP("2001:db8:ff::34"), 112))
|
||||
fmt.Println(Offset(net.ParseIP("2001:db8:0:aabb::"), net.ParseIP("2001:db8:ff::34"), 128))
|
||||
// Output:
|
||||
// 0 <nil>
|
||||
// 0 <nil>
|
||||
// 0 <nil>
|
||||
// 254 <nil>
|
||||
// 16667973 <nil>
|
||||
// 8534002176 <nil>
|
||||
// 1092352278528 <nil>
|
||||
// 71588398925611008 <nil>
|
||||
// 0 Operation overflows
|
||||
// 0 Operation overflows
|
||||
}
|
||||
|
||||
func ExampleAddPrefixes() {
|
||||
fmt.Println(AddPrefixes(net.ParseIP("2001:db8::"), 0xff, 64))
|
||||
fmt.Println(AddPrefixes(net.ParseIP("2001:db8::"), 0x1, 128))
|
||||
fmt.Println(AddPrefixes(net.ParseIP("2001:db8::"), 0xff, 32))
|
||||
fmt.Println(AddPrefixes(net.ParseIP("2001:db8::"), 0x1, 16))
|
||||
fmt.Println(AddPrefixes(net.ParseIP("2001:db8::"), 0xff, 65))
|
||||
// Error cases
|
||||
fmt.Println(AddPrefixes(net.ParseIP("2001:db8::"), 0xff, 8))
|
||||
fmt.Println(AddPrefixes(net.IP{10, 0, 0, 1}, 64, 32))
|
||||
// Output:
|
||||
// 2001:db8:0:ff:: <nil>
|
||||
// 2001:db8::1 <nil>
|
||||
// 2001:eb7:: <nil>
|
||||
// 2002:db8:: <nil>
|
||||
// 2001:db8:0:7f:8000:: <nil>
|
||||
// <nil> Operation overflows
|
||||
// <nil> AddPrefixes needs 128-bit IPs
|
||||
}
|
||||
|
||||
// Offset is used as a hash function, so it needs to be reasonably fast
|
||||
func BenchmarkOffset(b *testing.B) {
|
||||
// Need predictable randomness for benchmark reproducibility
|
||||
rng := rand.New(rand.NewSource(0))
|
||||
addresses := make([]byte, b.N*net.IPv6len*2)
|
||||
_, err := rng.Read(addresses)
|
||||
if err != nil {
|
||||
b.Fatalf("Could not generate random addresses: %v", err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// The arrays will be in cache, so this should amortize to measure mostly just the offset
|
||||
// computation itself
|
||||
_, _ = Offset(
|
||||
addresses[i*2*net.IPv6len:(i*2+1)*net.IPv6len],
|
||||
addresses[(i*2+1)*net.IPv6len:(i+1)*2*net.IPv6len],
|
||||
(i*4)%128,
|
||||
)
|
||||
}
|
||||
}
|
||||
86
plugins/autoconfigure/plugin.go
Normal file
86
plugins/autoconfigure/plugin.go
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package autoconfigure
|
||||
|
||||
// This plugin implements RFC2563:
|
||||
// 1. If the client has been allocated an IP address, do nothing
|
||||
// 2. If the client has not been allocated an IP address
|
||||
// (yiaddr=0.0.0.0), then:
|
||||
// 2a. If the client has requested the "AutoConfigure" option,
|
||||
// then add the defined value to the response
|
||||
// 2b. Otherwise, terminate processing and send no reply
|
||||
//
|
||||
// This plugin should be used at the end of the plugin chain,
|
||||
// after any IP address allocation has taken place.
|
||||
//
|
||||
// The optional argument is the string "DoNotAutoConfigure" or
|
||||
// "AutoConfigure" (or "0" or "1" respectively). The default
|
||||
// is DoNotAutoConfigure.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/coredhcp/coredhcp/handler"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("plugins/autoconfigure")
|
||||
|
||||
var autoconfigure dhcpv4.AutoConfiguration
|
||||
|
||||
var Plugin = plugins.Plugin{
|
||||
Name: "autoconfigure",
|
||||
Setup4: setup4,
|
||||
}
|
||||
|
||||
var argMap = map[string]dhcpv4.AutoConfiguration{
|
||||
"0": dhcpv4.AutoConfiguration(0),
|
||||
"1": dhcpv4.AutoConfiguration(1),
|
||||
"DoNotAutoConfigure": dhcpv4.DoNotAutoConfigure,
|
||||
"AutoConfigure": dhcpv4.AutoConfigure,
|
||||
}
|
||||
|
||||
func setup4(args ...string) (handler.Handler4, error) {
|
||||
if len(args) > 0 {
|
||||
var ok bool
|
||||
autoconfigure, ok = argMap[args[0]]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected value '%v' for autoconfigure argument", args[0])
|
||||
}
|
||||
}
|
||||
if len(args) > 1 {
|
||||
return nil, errors.New("too many arguments")
|
||||
}
|
||||
return Handler4, nil
|
||||
}
|
||||
|
||||
func Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
|
||||
if resp.MessageType() != dhcpv4.MessageTypeOffer || !resp.YourIPAddr.IsUnspecified() {
|
||||
return resp, false
|
||||
}
|
||||
|
||||
ac, ok := req.AutoConfigure()
|
||||
if ok {
|
||||
resp.UpdateOption(dhcpv4.OptAutoConfigure(autoconfigure))
|
||||
log.WithFields(logrus.Fields{
|
||||
"mac": req.ClientHWAddr.String(),
|
||||
"autoconfigure": fmt.Sprintf("%v", ac),
|
||||
}).Debugf("Responded with autoconfigure %v", autoconfigure)
|
||||
return resp, false
|
||||
}
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"mac": req.ClientHWAddr.String(),
|
||||
"autoconfigure": "nil",
|
||||
}).Debugf("Client does not support autoconfigure")
|
||||
// RFC2563 2.3: if no address is chosen for the host [...]
|
||||
// If the DHCPDISCOVER does not contain the Auto-Configure option,
|
||||
// it is not answered.
|
||||
return nil, true
|
||||
}
|
||||
118
plugins/autoconfigure/plugin_test.go
Normal file
118
plugins/autoconfigure/plugin_test.go
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package autoconfigure
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
)
|
||||
|
||||
func TestOptionRequested0(t *testing.T) {
|
||||
req, err := dhcpv4.NewDiscovery(net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.UpdateOption(dhcpv4.OptGeneric(dhcpv4.OptionAutoConfigure, []byte{1}))
|
||||
stub, err := dhcpv4.NewReplyFromRequest(req,
|
||||
dhcpv4.WithMessageType(dhcpv4.MessageTypeOffer),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, stop := Handler4(req, stub)
|
||||
if resp == nil {
|
||||
t.Fatal("plugin did not return a message")
|
||||
}
|
||||
if stop {
|
||||
t.Error("plugin interrupted processing")
|
||||
}
|
||||
opt := resp.Options.Get(dhcpv4.OptionAutoConfigure)
|
||||
if opt == nil {
|
||||
t.Fatal("plugin did not return the Auto-Configure option")
|
||||
}
|
||||
if !bytes.Equal(opt, []byte{0}) {
|
||||
t.Errorf("plugin gave wrong option response: %v", opt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptionRequested1(t *testing.T) {
|
||||
req, err := dhcpv4.NewDiscovery(net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.UpdateOption(dhcpv4.OptGeneric(dhcpv4.OptionAutoConfigure, []byte{1}))
|
||||
stub, err := dhcpv4.NewReplyFromRequest(req,
|
||||
dhcpv4.WithMessageType(dhcpv4.MessageTypeOffer),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
autoconfigure = 1
|
||||
resp, stop := Handler4(req, stub)
|
||||
if resp == nil {
|
||||
t.Fatal("plugin did not return a message")
|
||||
}
|
||||
if stop {
|
||||
t.Error("plugin interrupted processing")
|
||||
}
|
||||
opt := resp.Options.Get(dhcpv4.OptionAutoConfigure)
|
||||
if opt == nil {
|
||||
t.Fatal("plugin did not return the Auto-Configure option")
|
||||
}
|
||||
if !bytes.Equal(opt, []byte{1}) {
|
||||
t.Errorf("plugin gave wrong option response: %v", opt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotRequestedAssignedIP(t *testing.T) {
|
||||
req, err := dhcpv4.NewDiscovery(net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stub, err := dhcpv4.NewReplyFromRequest(req,
|
||||
dhcpv4.WithMessageType(dhcpv4.MessageTypeOffer),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stub.YourIPAddr = net.ParseIP("192.0.2.100")
|
||||
|
||||
resp, stop := Handler4(req, stub)
|
||||
if resp == nil {
|
||||
t.Fatal("plugin did not return a message")
|
||||
}
|
||||
if stop {
|
||||
t.Error("plugin interrupted processing")
|
||||
}
|
||||
if resp.Options.Get(dhcpv4.OptionAutoConfigure) != nil {
|
||||
t.Error("plugin responsed with AutoConfigure option")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotRequestedNoIP(t *testing.T) {
|
||||
req, err := dhcpv4.NewDiscovery(net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stub, err := dhcpv4.NewReplyFromRequest(req,
|
||||
dhcpv4.WithMessageType(dhcpv4.MessageTypeOffer),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, stop := Handler4(req, stub)
|
||||
if resp != nil {
|
||||
t.Error("plugin returned a message")
|
||||
}
|
||||
if !stop {
|
||||
t.Error("plugin did not interrupt processing")
|
||||
}
|
||||
}
|
||||
83
plugins/dns/plugin.go
Normal file
83
plugins/dns/plugin.go
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/coredhcp/coredhcp/handler"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("plugins/dns")
|
||||
|
||||
// Plugin wraps the DNS plugin information.
|
||||
var Plugin = plugins.Plugin{
|
||||
Name: "dns",
|
||||
Setup6: setup6,
|
||||
Setup4: setup4,
|
||||
}
|
||||
|
||||
var (
|
||||
dnsServers6 []net.IP
|
||||
dnsServers4 []net.IP
|
||||
)
|
||||
|
||||
func setup6(args ...string) (handler.Handler6, error) {
|
||||
if len(args) < 1 {
|
||||
return nil, errors.New("need at least one DNS server")
|
||||
}
|
||||
for _, arg := range args {
|
||||
server := net.ParseIP(arg)
|
||||
if server.To16() == nil {
|
||||
return Handler6, errors.New("expected an DNS server address, got: " + arg)
|
||||
}
|
||||
dnsServers6 = append(dnsServers6, server)
|
||||
}
|
||||
log.Infof("loaded %d DNS servers.", len(dnsServers6))
|
||||
return Handler6, nil
|
||||
}
|
||||
|
||||
func setup4(args ...string) (handler.Handler4, error) {
|
||||
log.Printf("loaded plugin for DHCPv4.")
|
||||
if len(args) < 1 {
|
||||
return nil, errors.New("need at least one DNS server")
|
||||
}
|
||||
for _, arg := range args {
|
||||
DNSServer := net.ParseIP(arg)
|
||||
if DNSServer.To4() == nil {
|
||||
return Handler4, errors.New("expected an DNS server address, got: " + arg)
|
||||
}
|
||||
dnsServers4 = append(dnsServers4, DNSServer)
|
||||
}
|
||||
log.Infof("loaded %d DNS servers.", len(dnsServers4))
|
||||
return Handler4, nil
|
||||
}
|
||||
|
||||
// Handler6 handles DHCPv6 packets for the dns plugin
|
||||
func Handler6(req, resp dhcpv6.DHCPv6) (dhcpv6.DHCPv6, bool) {
|
||||
decap, err := req.GetInnerMessage()
|
||||
if err != nil {
|
||||
log.Errorf("Could not decapsulate relayed message, aborting: %v", err)
|
||||
return nil, true
|
||||
}
|
||||
|
||||
if decap.IsOptionRequested(dhcpv6.OptionDNSRecursiveNameServer) {
|
||||
resp.UpdateOption(dhcpv6.OptDNS(dnsServers6...))
|
||||
}
|
||||
return resp, false
|
||||
}
|
||||
|
||||
//Handler4 handles DHCPv4 packets for the dns plugin
|
||||
func Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
|
||||
if req.IsOptionRequested(dhcpv4.OptionDomainNameServer) {
|
||||
resp.Options.Update(dhcpv4.OptDNS(dnsServers4...))
|
||||
}
|
||||
return resp, false
|
||||
}
|
||||
149
plugins/dns/plugin_test.go
Normal file
149
plugins/dns/plugin_test.go
Normal file
@@ -0,0 +1,149 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
)
|
||||
|
||||
func TestAddServer6(t *testing.T) {
|
||||
req, err := dhcpv6.NewMessage()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.MessageType = dhcpv6.MessageTypeRequest
|
||||
req.AddOption(dhcpv6.OptRequestedOption(dhcpv6.OptionDNSRecursiveNameServer))
|
||||
|
||||
stub, err := dhcpv6.NewMessage()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stub.MessageType = dhcpv6.MessageTypeReply
|
||||
|
||||
dnsServers6 = []net.IP{
|
||||
net.ParseIP("2001:db8::1"),
|
||||
net.ParseIP("2001:db8::3"),
|
||||
}
|
||||
|
||||
resp, stop := Handler6(req, stub)
|
||||
if resp == nil {
|
||||
t.Fatal("plugin did not return a message")
|
||||
}
|
||||
|
||||
if stop {
|
||||
t.Error("plugin interrupted processing")
|
||||
}
|
||||
opts := resp.GetOption(dhcpv6.OptionDNSRecursiveNameServer)
|
||||
if len(opts) != 1 {
|
||||
t.Fatalf("Expected 1 RDNSS option, got %d: %v", len(opts), opts)
|
||||
}
|
||||
foundServers := resp.(*dhcpv6.Message).Options.DNS()
|
||||
// XXX: is enforcing the order relevant here ?
|
||||
for i, srv := range foundServers {
|
||||
if !srv.Equal(dnsServers6[i]) {
|
||||
t.Errorf("Found server %s, expected %s", srv, dnsServers6[i])
|
||||
}
|
||||
}
|
||||
if len(foundServers) != len(dnsServers6) {
|
||||
t.Errorf("Found %d servers, expected %d", len(foundServers), len(dnsServers6))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotRequested6(t *testing.T) {
|
||||
req, err := dhcpv6.NewMessage()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.MessageType = dhcpv6.MessageTypeRequest
|
||||
req.AddOption(dhcpv6.OptRequestedOption())
|
||||
|
||||
stub, err := dhcpv6.NewMessage()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stub.MessageType = dhcpv6.MessageTypeReply
|
||||
|
||||
dnsServers6 = []net.IP{
|
||||
net.ParseIP("2001:db8::1"),
|
||||
}
|
||||
|
||||
resp, stop := Handler6(req, stub)
|
||||
if resp == nil {
|
||||
t.Fatal("plugin did not return a message")
|
||||
}
|
||||
if stop {
|
||||
t.Error("plugin interrupted processing")
|
||||
}
|
||||
|
||||
opts := resp.GetOption(dhcpv6.OptionDNSRecursiveNameServer)
|
||||
if len(opts) != 0 {
|
||||
t.Errorf("RDNSS options were added when not requested: %v", opts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddServer4(t *testing.T) {
|
||||
req, err := dhcpv4.NewDiscovery(net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stub, err := dhcpv4.NewReplyFromRequest(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dnsServers4 = []net.IP{
|
||||
net.ParseIP("192.0.2.1"),
|
||||
net.ParseIP("192.0.2.3"),
|
||||
}
|
||||
|
||||
resp, stop := Handler4(req, stub)
|
||||
if resp == nil {
|
||||
t.Fatal("plugin did not return a message")
|
||||
}
|
||||
if stop {
|
||||
t.Error("plugin interrupted processing")
|
||||
}
|
||||
servers := resp.DNS()
|
||||
for i, srv := range servers {
|
||||
if !srv.Equal(dnsServers4[i]) {
|
||||
t.Errorf("Found server %s, expected %s", srv, dnsServers4[i])
|
||||
}
|
||||
}
|
||||
if len(servers) != len(dnsServers4) {
|
||||
t.Errorf("Found %d servers, expected %d", len(servers), len(dnsServers4))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotRequested4(t *testing.T) {
|
||||
req, err := dhcpv4.NewDiscovery(net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stub, err := dhcpv4.NewReplyFromRequest(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dnsServers4 = []net.IP{
|
||||
net.ParseIP("192.0.2.1"),
|
||||
}
|
||||
req.UpdateOption(dhcpv4.OptParameterRequestList(dhcpv4.OptionBroadcastAddress))
|
||||
|
||||
resp, stop := Handler4(req, stub)
|
||||
if resp == nil {
|
||||
t.Fatal("plugin did not return a message")
|
||||
}
|
||||
if stop {
|
||||
t.Error("plugin interrupted processing")
|
||||
}
|
||||
servers := dhcpv4.GetIPs(dhcpv4.OptionDomainNameServer, resp.Options)
|
||||
if len(servers) != 0 {
|
||||
t.Errorf("Found %d DNS servers when explicitly not requested", len(servers))
|
||||
}
|
||||
}
|
||||
118
plugins/example/plugin.go
Normal file
118
plugins/example/plugin.go
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package example
|
||||
|
||||
// This is an example plugin that inspects a packet and prints it out. The code
|
||||
// is commented in a way that should walk you through the implementation of your
|
||||
// own plugins.
|
||||
// Feedback is welcome!
|
||||
|
||||
import (
|
||||
"github.com/coredhcp/coredhcp/handler"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
)
|
||||
|
||||
// We use a customizable logger, as part of the `logger` package. You can use
|
||||
// `logger.GetLogger()` to get a singleton instance of the logger. Then just use
|
||||
// it with the `logrus` interface (https://github.com/sirupsen/logrus). More
|
||||
// information in the docstring of the logger package.
|
||||
var log = logger.GetLogger("plugins/example")
|
||||
|
||||
// Plugin wraps the information necessary to register a plugin.
|
||||
// In the main package, you need to export a `plugins.Plugin` object called
|
||||
// `Plugin`, so it can be registered into the plugin registry.
|
||||
// Just import your plugin, and fill the structure with plugin name and setup
|
||||
// functions:
|
||||
//
|
||||
// import (
|
||||
// "github.com/coredhcp/coredhcp/plugins"
|
||||
// "github.com/coredhcp/coredhcp/plugins/example"
|
||||
// )
|
||||
//
|
||||
// var Plugin = plugins.Plugin{
|
||||
// Name: "example",
|
||||
// Setup6: setup6,
|
||||
// Setup4: setup4,
|
||||
// }
|
||||
//
|
||||
// Name is simply the name used to register the plugin. It must be unique to
|
||||
// other registered plugins, or the operation will fail. In other words, don't
|
||||
// declare plugins with colliding names.
|
||||
//
|
||||
// Setup6 and Setup4 are the setup functions for DHCPv6 and DHCPv4 traffic
|
||||
// handlers. They conform to the `plugins.SetupFunc6` and `plugins.SetupFunc4`
|
||||
// interfaces, so they must return a `plugins.Handler6` and a `plugins.Handler4`
|
||||
// respectively.
|
||||
// A `nil` setup function means that that protocol won't be handled by this
|
||||
// plugin.
|
||||
//
|
||||
// Note that importing the plugin is not enough to use it: you have to
|
||||
// explicitly specify the intention to use it in the `config.yml` file, in the
|
||||
// plugins section. For example:
|
||||
//
|
||||
// server6:
|
||||
// listen: '[::]547'
|
||||
// - example:
|
||||
// - server_id: LL aa:bb:cc:dd:ee:ff
|
||||
// - file: "leases.txt"
|
||||
//
|
||||
var Plugin = plugins.Plugin{
|
||||
Name: "example",
|
||||
Setup6: setup6,
|
||||
Setup4: setup4,
|
||||
}
|
||||
|
||||
// setup6 is the setup function to initialize the handler for DHCPv6
|
||||
// traffic. This function implements the `plugin.SetupFunc6` interface.
|
||||
// This function returns a `handler.Handler6` function, and an error if any.
|
||||
// In this example we do very little in the setup function, and just return the
|
||||
// `exampleHandler6` function. Such function will be called for every DHCPv6
|
||||
// packet that the server receives. Remember that a handler may not be called
|
||||
// for each packet, if the handler chain is interrupted before reaching it.
|
||||
func setup6(args ...string) (handler.Handler6, error) {
|
||||
log.Printf("loaded plugin for DHCPv6.")
|
||||
return exampleHandler6, nil
|
||||
}
|
||||
|
||||
// setup4 behaves like setupExample6, but for DHCPv4 packets. It
|
||||
// implements the `plugin.SetupFunc4` interface.
|
||||
func setup4(args ...string) (handler.Handler4, error) {
|
||||
log.Printf("loaded plugin for DHCPv4.")
|
||||
return exampleHandler4, nil
|
||||
}
|
||||
|
||||
// exampleHandler6 handles DHCPv6 packets for the example plugin. It implements
|
||||
// the `handler.Handler6` interface. The input arguments are the request packet
|
||||
// that the server received from a client, and the response packet that has been
|
||||
// computed so far. This function returns the response packet to be sent back to
|
||||
// the client, and a boolean.
|
||||
// The response can be either the same response packet received as input, a
|
||||
// modified response packet, or nil. If nil, the server will not reply to the
|
||||
// client, basically dropping the request.
|
||||
// The returned boolean indicates to the server whether the chain of plugins
|
||||
// should continue or not. If `true`, the server will stop at this plugin, and
|
||||
// respond to the client (or drop the response, if nil). If `false`, the server
|
||||
// will call the next plugin in the chan, using the returned response packet as
|
||||
// input for the next plugin.
|
||||
func exampleHandler6(req, resp dhcpv6.DHCPv6) (dhcpv6.DHCPv6, bool) {
|
||||
log.Printf("received DHCPv6 packet: %s", req.Summary())
|
||||
// return the unmodified response, and false. This means that the next
|
||||
// plugin in the chain will be called, and the unmodified response packet
|
||||
// will be used as its input.
|
||||
return resp, false
|
||||
}
|
||||
|
||||
// exampleHandler4 behaves like exampleHandler6, but for DHCPv4 packets. It
|
||||
// implements the `handler.Handler4` interface.
|
||||
func exampleHandler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
|
||||
log.Printf("received DHCPv4 packet: %s", req.Summary())
|
||||
// return the unmodified response, and false. This means that the next
|
||||
// plugin in the chain will be called, and the unmodified response packet
|
||||
// will be used as its input.
|
||||
return resp, false
|
||||
}
|
||||
282
plugins/file/plugin.go
Normal file
282
plugins/file/plugin.go
Normal file
@@ -0,0 +1,282 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
// Package file enables static mapping of MAC <--> IP addresses.
|
||||
// The mapping is stored in a text file, where each mapping is described by one line containing
|
||||
// two fields separated by spaces: MAC address, and IP address. For example:
|
||||
//
|
||||
// $ cat file_leases.txt
|
||||
// 00:11:22:33:44:55 10.0.0.1
|
||||
// 01:23:45:67:89:01 10.0.10.10
|
||||
//
|
||||
// To specify the plugin configuration in the server6/server4 sections of the config file, just
|
||||
// pass the leases file name as plugin argument, e.g.:
|
||||
//
|
||||
// $ cat config.yml
|
||||
//
|
||||
// server6:
|
||||
// ...
|
||||
// plugins:
|
||||
// - file: "file_leases.txt" [autorefresh]
|
||||
// ...
|
||||
//
|
||||
// If the file path is not absolute, it is relative to the cwd where coredhcp is run.
|
||||
//
|
||||
// Optionally, when the 'autorefresh' argument is given, the plugin will try to refresh
|
||||
// the lease mapping during runtime whenever the lease file is updated.
|
||||
package file
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coredhcp/coredhcp/handler"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
)
|
||||
|
||||
const (
|
||||
autoRefreshArg = "autorefresh"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("plugins/file")
|
||||
|
||||
// Plugin wraps plugin registration information
|
||||
var Plugin = plugins.Plugin{
|
||||
Name: "file",
|
||||
Setup6: setup6,
|
||||
Setup4: setup4,
|
||||
}
|
||||
|
||||
var recLock sync.RWMutex
|
||||
|
||||
// StaticRecords holds a MAC -> IP address mapping
|
||||
var StaticRecords map[string]net.IP
|
||||
|
||||
// DHCPv6Records and DHCPv4Records are mappings between MAC addresses in
|
||||
// form of a string, to network configurations.
|
||||
var (
|
||||
DHCPv6Records map[string]net.IP
|
||||
DHCPv4Records map[string]net.IP
|
||||
)
|
||||
|
||||
// LoadDHCPv4Records loads the DHCPv4Records global map with records stored on
|
||||
// the specified file. The records have to be one per line, a mac address and an
|
||||
// IPv4 address.
|
||||
func LoadDHCPv4Records(filename string) (map[string]net.IP, error) {
|
||||
log.Infof("reading leases from %s", filename)
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
records := make(map[string]net.IP)
|
||||
for _, lineBytes := range bytes.Split(data, []byte{'\n'}) {
|
||||
line := string(lineBytes)
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
tokens := strings.Fields(line)
|
||||
if len(tokens) != 2 {
|
||||
return nil, fmt.Errorf("malformed line, want 2 fields, got %d: %s", len(tokens), line)
|
||||
}
|
||||
hwaddr, err := net.ParseMAC(tokens[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed hardware address: %s", tokens[0])
|
||||
}
|
||||
ipaddr := net.ParseIP(tokens[1])
|
||||
if ipaddr.To4() == nil {
|
||||
return nil, fmt.Errorf("expected an IPv4 address, got: %v", ipaddr)
|
||||
}
|
||||
records[hwaddr.String()] = ipaddr
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// LoadDHCPv6Records loads the DHCPv6Records global map with records stored on
|
||||
// the specified file. The records have to be one per line, a mac address and an
|
||||
// IPv6 address.
|
||||
func LoadDHCPv6Records(filename string) (map[string]net.IP, error) {
|
||||
log.Infof("reading leases from %s", filename)
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
records := make(map[string]net.IP)
|
||||
for _, lineBytes := range bytes.Split(data, []byte{'\n'}) {
|
||||
line := string(lineBytes)
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
tokens := strings.Fields(line)
|
||||
if len(tokens) != 2 {
|
||||
return nil, fmt.Errorf("malformed line, want 2 fields, got %d: %s", len(tokens), line)
|
||||
}
|
||||
hwaddr, err := net.ParseMAC(tokens[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed hardware address: %s", tokens[0])
|
||||
}
|
||||
ipaddr := net.ParseIP(tokens[1])
|
||||
if ipaddr.To16() == nil || ipaddr.To4() != nil {
|
||||
return nil, fmt.Errorf("expected an IPv6 address, got: %v", ipaddr)
|
||||
}
|
||||
records[hwaddr.String()] = ipaddr
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// Handler6 handles DHCPv6 packets for the file plugin
|
||||
func Handler6(req, resp dhcpv6.DHCPv6) (dhcpv6.DHCPv6, bool) {
|
||||
m, err := req.GetInnerMessage()
|
||||
if err != nil {
|
||||
log.Errorf("BUG: could not decapsulate: %v", err)
|
||||
return nil, true
|
||||
}
|
||||
|
||||
if m.Options.OneIANA() == nil {
|
||||
log.Debug("No address requested")
|
||||
return resp, false
|
||||
}
|
||||
|
||||
mac, err := dhcpv6.ExtractMAC(req)
|
||||
if err != nil {
|
||||
log.Warningf("Could not find client MAC, passing")
|
||||
return resp, false
|
||||
}
|
||||
log.Debugf("looking up an IP address for MAC %s", mac.String())
|
||||
|
||||
recLock.RLock()
|
||||
defer recLock.RUnlock()
|
||||
|
||||
ipaddr, ok := StaticRecords[mac.String()]
|
||||
if !ok {
|
||||
log.Warningf("MAC address %s is unknown", mac.String())
|
||||
return resp, false
|
||||
}
|
||||
log.Debugf("found IP address %s for MAC %s", ipaddr, mac.String())
|
||||
|
||||
resp.AddOption(&dhcpv6.OptIANA{
|
||||
IaId: m.Options.OneIANA().IaId,
|
||||
Options: dhcpv6.IdentityOptions{Options: []dhcpv6.Option{
|
||||
&dhcpv6.OptIAAddress{
|
||||
IPv6Addr: ipaddr,
|
||||
PreferredLifetime: 3600 * time.Second,
|
||||
ValidLifetime: 3600 * time.Second,
|
||||
},
|
||||
}},
|
||||
})
|
||||
return resp, false
|
||||
}
|
||||
|
||||
// Handler4 handles DHCPv4 packets for the file plugin
|
||||
func Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
|
||||
recLock.RLock()
|
||||
defer recLock.RUnlock()
|
||||
|
||||
ipaddr, ok := StaticRecords[req.ClientHWAddr.String()]
|
||||
if !ok {
|
||||
log.Warningf("MAC address %s is unknown", req.ClientHWAddr.String())
|
||||
return resp, false
|
||||
}
|
||||
resp.YourIPAddr = ipaddr
|
||||
log.Debugf("found IP address %s for MAC %s", ipaddr, req.ClientHWAddr.String())
|
||||
return resp, true
|
||||
}
|
||||
|
||||
func setup6(args ...string) (handler.Handler6, error) {
|
||||
h6, _, err := setupFile(true, args...)
|
||||
return h6, err
|
||||
}
|
||||
|
||||
func setup4(args ...string) (handler.Handler4, error) {
|
||||
_, h4, err := setupFile(false, args...)
|
||||
return h4, err
|
||||
}
|
||||
|
||||
func setupFile(v6 bool, args ...string) (handler.Handler6, handler.Handler4, error) {
|
||||
var err error
|
||||
if len(args) < 1 {
|
||||
return nil, nil, errors.New("need a file name")
|
||||
}
|
||||
filename := args[0]
|
||||
if filename == "" {
|
||||
return nil, nil, errors.New("got empty file name")
|
||||
}
|
||||
|
||||
// load initial database from lease file
|
||||
if err = loadFromFile(v6, filename); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// when the 'autorefresh' argument was passed, watch the lease file for
|
||||
// changes and reload the lease mapping on any event
|
||||
if len(args) > 1 && args[1] == autoRefreshArg {
|
||||
// creates a new file watcher
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create watcher: %w", err)
|
||||
}
|
||||
|
||||
// have file watcher watch over lease file
|
||||
if err = watcher.Add(filename); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to watch %s: %w", filename, err)
|
||||
}
|
||||
|
||||
// very simple watcher on the lease file to trigger a refresh on any event
|
||||
// on the file
|
||||
go func() {
|
||||
for range watcher.Events {
|
||||
err := loadFromFile(v6, filename)
|
||||
if err != nil {
|
||||
log.Warningf("failed to refresh from %s: %s", filename, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infof("updated to %d leases from %s", len(StaticRecords), filename)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
log.Infof("loaded %d leases from %s", len(StaticRecords), filename)
|
||||
return Handler6, Handler4, nil
|
||||
}
|
||||
|
||||
func loadFromFile(v6 bool, filename string) error {
|
||||
var err error
|
||||
var records map[string]net.IP
|
||||
var protver int
|
||||
if v6 {
|
||||
protver = 6
|
||||
records, err = LoadDHCPv6Records(filename)
|
||||
} else {
|
||||
protver = 4
|
||||
records, err = LoadDHCPv4Records(filename)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load DHCPv%d records: %w", protver, err)
|
||||
}
|
||||
|
||||
recLock.Lock()
|
||||
defer recLock.Unlock()
|
||||
|
||||
StaticRecords = records
|
||||
|
||||
return nil
|
||||
}
|
||||
368
plugins/file/plugin_test.go
Normal file
368
plugins/file/plugin_test.go
Normal file
@@ -0,0 +1,368 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package file
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLoadDHCPv4Records(t *testing.T) {
|
||||
t.Run("valid leases", func(t *testing.T) {
|
||||
// setup temp leases file
|
||||
tmp, err := os.CreateTemp("", "test_plugin_file")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
tmp.Close()
|
||||
os.Remove(tmp.Name())
|
||||
}()
|
||||
|
||||
// fill temp file with valid lease lines and some comments
|
||||
_, err = tmp.WriteString("00:11:22:33:44:55 192.0.2.100\n")
|
||||
require.NoError(t, err)
|
||||
_, err = tmp.WriteString("11:22:33:44:55:66 192.0.2.101\n")
|
||||
require.NoError(t, err)
|
||||
_, err = tmp.WriteString("# this is a comment\n")
|
||||
require.NoError(t, err)
|
||||
|
||||
records, err := LoadDHCPv4Records(tmp.Name())
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
if assert.Equal(t, 2, len(records)) {
|
||||
if assert.Contains(t, records, "00:11:22:33:44:55") {
|
||||
assert.Equal(t, net.ParseIP("192.0.2.100"), records["00:11:22:33:44:55"])
|
||||
}
|
||||
if assert.Contains(t, records, "11:22:33:44:55:66") {
|
||||
assert.Equal(t, net.ParseIP("192.0.2.101"), records["11:22:33:44:55:66"])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("missing field", func(t *testing.T) {
|
||||
// setup temp leases file
|
||||
tmp, err := os.CreateTemp("", "test_plugin_file")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
tmp.Close()
|
||||
os.Remove(tmp.Name())
|
||||
}()
|
||||
|
||||
// add line with too few fields
|
||||
_, err = tmp.WriteString("foo\n")
|
||||
require.NoError(t, err)
|
||||
_, err = LoadDHCPv4Records(tmp.Name())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("invalid MAC", func(t *testing.T) {
|
||||
// setup temp leases file
|
||||
tmp, err := os.CreateTemp("", "test_plugin_file")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
tmp.Close()
|
||||
os.Remove(tmp.Name())
|
||||
}()
|
||||
|
||||
// add line with invalid MAC address to trigger an error
|
||||
_, err = tmp.WriteString("abcd 192.0.2.102\n")
|
||||
require.NoError(t, err)
|
||||
_, err = LoadDHCPv4Records(tmp.Name())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("invalid IP address", func(t *testing.T) {
|
||||
// setup temp leases file
|
||||
tmp, err := os.CreateTemp("", "test_plugin_file")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
tmp.Close()
|
||||
os.Remove(tmp.Name())
|
||||
}()
|
||||
|
||||
// add line with invalid MAC address to trigger an error
|
||||
_, err = tmp.WriteString("22:33:44:55:66:77 bcde\n")
|
||||
require.NoError(t, err)
|
||||
_, err = LoadDHCPv4Records(tmp.Name())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("lease with IPv6 address", func(t *testing.T) {
|
||||
// setup temp leases file
|
||||
tmp, err := os.CreateTemp("", "test_plugin_file")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
tmp.Close()
|
||||
os.Remove(tmp.Name())
|
||||
}()
|
||||
|
||||
// add line with IPv6 address instead to trigger an error
|
||||
_, err = tmp.WriteString("00:11:22:33:44:55 2001:db8::10:1\n")
|
||||
require.NoError(t, err)
|
||||
_, err = LoadDHCPv4Records(tmp.Name())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadDHCPv6Records(t *testing.T) {
|
||||
t.Run("valid leases", func(t *testing.T) {
|
||||
// setup temp leases file
|
||||
tmp, err := os.CreateTemp("", "test_plugin_file")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
tmp.Close()
|
||||
os.Remove(tmp.Name())
|
||||
}()
|
||||
|
||||
// fill temp file with valid lease lines and some comments
|
||||
_, err = tmp.WriteString("00:11:22:33:44:55 2001:db8::10:1\n")
|
||||
require.NoError(t, err)
|
||||
_, err = tmp.WriteString("11:22:33:44:55:66 2001:db8::10:2\n")
|
||||
require.NoError(t, err)
|
||||
_, err = tmp.WriteString("# this is a comment\n")
|
||||
require.NoError(t, err)
|
||||
|
||||
records, err := LoadDHCPv6Records(tmp.Name())
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
if assert.Equal(t, 2, len(records)) {
|
||||
if assert.Contains(t, records, "00:11:22:33:44:55") {
|
||||
assert.Equal(t, net.ParseIP("2001:db8::10:1"), records["00:11:22:33:44:55"])
|
||||
}
|
||||
if assert.Contains(t, records, "11:22:33:44:55:66") {
|
||||
assert.Equal(t, net.ParseIP("2001:db8::10:2"), records["11:22:33:44:55:66"])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("missing field", func(t *testing.T) {
|
||||
// setup temp leases file
|
||||
tmp, err := os.CreateTemp("", "test_plugin_file")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
tmp.Close()
|
||||
os.Remove(tmp.Name())
|
||||
}()
|
||||
|
||||
// add line with too few fields
|
||||
_, err = tmp.WriteString("foo\n")
|
||||
require.NoError(t, err)
|
||||
_, err = LoadDHCPv6Records(tmp.Name())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("invalid MAC", func(t *testing.T) {
|
||||
// setup temp leases file
|
||||
tmp, err := os.CreateTemp("", "test_plugin_file")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
tmp.Close()
|
||||
os.Remove(tmp.Name())
|
||||
}()
|
||||
|
||||
// add line with invalid MAC address to trigger an error
|
||||
_, err = tmp.WriteString("abcd 2001:db8::10:3\n")
|
||||
require.NoError(t, err)
|
||||
_, err = LoadDHCPv6Records(tmp.Name())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("invalid IP address", func(t *testing.T) {
|
||||
// setup temp leases file
|
||||
tmp, err := os.CreateTemp("", "test_plugin_file")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
tmp.Close()
|
||||
os.Remove(tmp.Name())
|
||||
}()
|
||||
|
||||
// add line with invalid MAC address to trigger an error
|
||||
_, err = tmp.WriteString("22:33:44:55:66:77 bcde\n")
|
||||
require.NoError(t, err)
|
||||
_, err = LoadDHCPv6Records(tmp.Name())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("lease with IPv4 address", func(t *testing.T) {
|
||||
// setup temp leases file
|
||||
tmp, err := os.CreateTemp("", "test_plugin_file")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
tmp.Close()
|
||||
os.Remove(tmp.Name())
|
||||
}()
|
||||
|
||||
// add line with IPv4 address instead to trigger an error
|
||||
_, err = tmp.WriteString("00:11:22:33:44:55 192.0.2.100\n")
|
||||
require.NoError(t, err)
|
||||
_, err = LoadDHCPv6Records(tmp.Name())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHandler4(t *testing.T) {
|
||||
t.Run("unknown MAC", func(t *testing.T) {
|
||||
// prepare DHCPv4 request
|
||||
mac := "00:11:22:33:44:55"
|
||||
claddr, _ := net.ParseMAC(mac)
|
||||
req := &dhcpv4.DHCPv4{
|
||||
ClientHWAddr: claddr,
|
||||
}
|
||||
resp := &dhcpv4.DHCPv4{}
|
||||
assert.Nil(t, resp.ClientIPAddr)
|
||||
|
||||
// if we handle this DHCP request, nothing should change since the lease is
|
||||
// unknown
|
||||
result, stop := Handler4(req, resp)
|
||||
assert.Same(t, result, resp)
|
||||
assert.False(t, stop)
|
||||
assert.Nil(t, result.YourIPAddr)
|
||||
})
|
||||
|
||||
t.Run("known MAC", func(t *testing.T) {
|
||||
// prepare DHCPv4 request
|
||||
mac := "00:11:22:33:44:55"
|
||||
claddr, _ := net.ParseMAC(mac)
|
||||
req := &dhcpv4.DHCPv4{
|
||||
ClientHWAddr: claddr,
|
||||
}
|
||||
resp := &dhcpv4.DHCPv4{}
|
||||
assert.Nil(t, resp.ClientIPAddr)
|
||||
|
||||
// add lease for the MAC in the lease map
|
||||
clIPAddr := net.ParseIP("192.0.2.100")
|
||||
StaticRecords = map[string]net.IP{
|
||||
mac: clIPAddr,
|
||||
}
|
||||
|
||||
// if we handle this DHCP request, the YourIPAddr field should be set
|
||||
// in the result
|
||||
result, stop := Handler4(req, resp)
|
||||
assert.Same(t, result, resp)
|
||||
assert.True(t, stop)
|
||||
assert.Equal(t, clIPAddr, result.YourIPAddr)
|
||||
|
||||
// cleanup
|
||||
StaticRecords = make(map[string]net.IP)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHandler6(t *testing.T) {
|
||||
t.Run("unknown MAC", func(t *testing.T) {
|
||||
// prepare DHCPv6 request
|
||||
mac := "11:22:33:44:55:66"
|
||||
claddr, _ := net.ParseMAC(mac)
|
||||
req, err := dhcpv6.NewSolicit(claddr)
|
||||
require.NoError(t, err)
|
||||
resp, err := dhcpv6.NewAdvertiseFromSolicit(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, len(resp.GetOption(dhcpv6.OptionIANA)))
|
||||
|
||||
// if we handle this DHCP request, nothing should change since the lease is
|
||||
// unknown
|
||||
result, stop := Handler6(req, resp)
|
||||
assert.False(t, stop)
|
||||
assert.Equal(t, 0, len(result.GetOption(dhcpv6.OptionIANA)))
|
||||
})
|
||||
|
||||
t.Run("known MAC", func(t *testing.T) {
|
||||
// prepare DHCPv6 request
|
||||
mac := "11:22:33:44:55:66"
|
||||
claddr, _ := net.ParseMAC(mac)
|
||||
req, err := dhcpv6.NewSolicit(claddr)
|
||||
require.NoError(t, err)
|
||||
resp, err := dhcpv6.NewAdvertiseFromSolicit(req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, len(resp.GetOption(dhcpv6.OptionIANA)))
|
||||
|
||||
// add lease for the MAC in the lease map
|
||||
clIPAddr := net.ParseIP("2001:db8::10:1")
|
||||
StaticRecords = map[string]net.IP{
|
||||
mac: clIPAddr,
|
||||
}
|
||||
|
||||
// if we handle this DHCP request, there should be a specific IANA option
|
||||
// set in the resulting response
|
||||
result, stop := Handler6(req, resp)
|
||||
assert.False(t, stop)
|
||||
if assert.Equal(t, 1, len(result.GetOption(dhcpv6.OptionIANA))) {
|
||||
opt := result.GetOneOption(dhcpv6.OptionIANA)
|
||||
assert.Contains(t, opt.String(), "IP=2001:db8::10:1")
|
||||
}
|
||||
|
||||
// cleanup
|
||||
StaticRecords = make(map[string]net.IP)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetupFile(t *testing.T) {
|
||||
// too few arguments
|
||||
_, _, err := setupFile(false)
|
||||
assert.Error(t, err)
|
||||
|
||||
// empty file name
|
||||
_, _, err = setupFile(false, "")
|
||||
assert.Error(t, err)
|
||||
|
||||
// trigger error in LoadDHCPv*Records
|
||||
_, _, err = setupFile(false, "/foo/bar")
|
||||
assert.Error(t, err)
|
||||
|
||||
_, _, err = setupFile(true, "/foo/bar")
|
||||
assert.Error(t, err)
|
||||
|
||||
// setup temp leases file
|
||||
tmp, err := os.CreateTemp("", "test_plugin_file")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
tmp.Close()
|
||||
os.Remove(tmp.Name())
|
||||
}()
|
||||
|
||||
t.Run("typical case", func(t *testing.T) {
|
||||
_, err = tmp.WriteString("00:11:22:33:44:55 2001:db8::10:1\n")
|
||||
require.NoError(t, err)
|
||||
_, err = tmp.WriteString("11:22:33:44:55:66 2001:db8::10:2\n")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 0, len(StaticRecords))
|
||||
|
||||
// leases should show up in StaticRecords
|
||||
_, _, err = setupFile(true, tmp.Name())
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, 2, len(StaticRecords))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("autorefresh enabled", func(t *testing.T) {
|
||||
_, _, err = setupFile(true, tmp.Name(), autoRefreshArg)
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, 2, len(StaticRecords))
|
||||
}
|
||||
// we add more leases to the file
|
||||
// this should trigger an event to refresh the leases database
|
||||
// without calling setupFile again
|
||||
_, err = tmp.WriteString("22:33:44:55:66:77 2001:db8::10:3\n")
|
||||
require.NoError(t, err)
|
||||
// since the event is processed asynchronously, give it a little time
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
// an additional record should show up in the database
|
||||
// but we should respect the locking first
|
||||
recLock.RLock()
|
||||
defer recLock.RUnlock()
|
||||
|
||||
assert.Equal(t, 3, len(StaticRecords))
|
||||
})
|
||||
}
|
||||
64
plugins/ipv6only/plugin.go
Normal file
64
plugins/ipv6only/plugin.go
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package ipv6only
|
||||
|
||||
// This plugin implements RFC8925: if the client has requested the
|
||||
// IPv6-Only Preferred option, then add the option response and then
|
||||
// terminate processing immediately.
|
||||
//
|
||||
// This module should be invoked *before* any IP address
|
||||
// allocation has been done, so that the yiaddr is 0.0.0.0 and
|
||||
// no pool addresses are consumed for compatible clients.
|
||||
//
|
||||
// The optional argument is the V6ONLY_WAIT configuration variable,
|
||||
// described in RFC8925 section 3.2.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/coredhcp/coredhcp/handler"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("plugins/ipv6only")
|
||||
|
||||
var v6only_wait time.Duration
|
||||
|
||||
var Plugin = plugins.Plugin{
|
||||
Name: "ipv6only",
|
||||
Setup4: setup4,
|
||||
}
|
||||
|
||||
func setup4(args ...string) (handler.Handler4, error) {
|
||||
if len(args) > 0 {
|
||||
dur, err := time.ParseDuration(args[0])
|
||||
if err != nil {
|
||||
log.Errorf("invalid duration: %v", args[0])
|
||||
return nil, errors.New("ipv6only failed to initialize")
|
||||
}
|
||||
v6only_wait = dur
|
||||
}
|
||||
if len(args) > 1 {
|
||||
return nil, errors.New("too many arguments")
|
||||
}
|
||||
return Handler4, nil
|
||||
}
|
||||
|
||||
func Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
|
||||
v6pref := req.IsOptionRequested(dhcpv4.OptionIPv6OnlyPreferred)
|
||||
log.WithFields(logrus.Fields{
|
||||
"mac": req.ClientHWAddr.String(),
|
||||
"ipv6only": v6pref,
|
||||
}).Debug("ipv6only status")
|
||||
if v6pref {
|
||||
resp.UpdateOption(dhcpv4.OptIPv6OnlyPreferred(v6only_wait))
|
||||
return resp, true
|
||||
}
|
||||
return resp, false
|
||||
}
|
||||
65
plugins/ipv6only/plugin_test.go
Normal file
65
plugins/ipv6only/plugin_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package ipv6only
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
)
|
||||
|
||||
func TestOptionRequested(t *testing.T) {
|
||||
req, err := dhcpv4.NewDiscovery(net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.UpdateOption(dhcpv4.OptParameterRequestList(dhcpv4.OptionBroadcastAddress, dhcpv4.OptionIPv6OnlyPreferred))
|
||||
stub, err := dhcpv4.NewReplyFromRequest(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
v6only_wait = 0x1234 * time.Second
|
||||
|
||||
resp, stop := Handler4(req, stub)
|
||||
if resp == nil {
|
||||
t.Fatal("plugin did not return a message")
|
||||
}
|
||||
if !stop {
|
||||
t.Error("plugin did not interrupt processing")
|
||||
}
|
||||
opt := resp.Options.Get(dhcpv4.OptionIPv6OnlyPreferred)
|
||||
if opt == nil {
|
||||
t.Fatal("plugin did not return the IPv6-Only Preferred option")
|
||||
}
|
||||
if !bytes.Equal(opt, []byte{0x00, 0x00, 0x12, 0x34}) {
|
||||
t.Errorf("plugin gave wrong option response: %v", opt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotRequested(t *testing.T) {
|
||||
req, err := dhcpv4.NewDiscovery(net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stub, err := dhcpv4.NewReplyFromRequest(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, stop := Handler4(req, stub)
|
||||
if resp == nil {
|
||||
t.Fatal("plugin did not return a message")
|
||||
}
|
||||
if stop {
|
||||
t.Error("plugin interrupted processing")
|
||||
}
|
||||
if resp.Options.Get(dhcpv4.OptionIPv6OnlyPreferred) != nil {
|
||||
t.Error("Found IPv6-Only Preferred option when not requested")
|
||||
}
|
||||
}
|
||||
57
plugins/leasetime/plugin.go
Normal file
57
plugins/leasetime/plugin.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package leasetime
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/coredhcp/coredhcp/handler"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
)
|
||||
|
||||
// Plugin wraps plugin registration information
|
||||
var Plugin = plugins.Plugin{
|
||||
Name: "lease_time",
|
||||
// currently not supported for DHCPv6
|
||||
Setup6: nil,
|
||||
Setup4: setup4,
|
||||
}
|
||||
|
||||
var (
|
||||
log = logger.GetLogger("plugins/lease_time")
|
||||
v4LeaseTime time.Duration
|
||||
)
|
||||
|
||||
// Handler4 handles DHCPv4 packets for the lease_time plugin.
|
||||
func Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
|
||||
if req.OpCode != dhcpv4.OpcodeBootRequest {
|
||||
return resp, false
|
||||
}
|
||||
// Set lease time unless it has already been set
|
||||
if !resp.Options.Has(dhcpv4.OptionIPAddressLeaseTime) {
|
||||
resp.Options.Update(dhcpv4.OptIPAddressLeaseTime(v4LeaseTime))
|
||||
}
|
||||
return resp, false
|
||||
}
|
||||
|
||||
func setup4(args ...string) (handler.Handler4, error) {
|
||||
log.Print("loading `lease_time` plugin for DHCPv4")
|
||||
if len(args) < 1 {
|
||||
log.Error("No default lease time provided")
|
||||
return nil, errors.New("lease_time failed to initialize")
|
||||
}
|
||||
|
||||
leaseTime, err := time.ParseDuration(args[0])
|
||||
if err != nil {
|
||||
log.Errorf("invalid duration: %v", args[0])
|
||||
return nil, errors.New("lease_time failed to initialize")
|
||||
}
|
||||
v4LeaseTime = leaseTime
|
||||
|
||||
return Handler4, nil
|
||||
}
|
||||
50
plugins/mtu/plugin.go
Normal file
50
plugins/mtu/plugin.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package mtu
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
|
||||
"github.com/coredhcp/coredhcp/handler"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("plugins/mtu")
|
||||
|
||||
// Plugin wraps the MTU plugin information.
|
||||
var Plugin = plugins.Plugin{
|
||||
Name: "mtu",
|
||||
Setup4: setup4,
|
||||
// No Setup6 since DHCPv6 does not have MTU-related options
|
||||
}
|
||||
|
||||
var (
|
||||
mtu int
|
||||
)
|
||||
|
||||
func setup4(args ...string) (handler.Handler4, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, errors.New("need one mtu value")
|
||||
}
|
||||
var err error
|
||||
if mtu, err = strconv.Atoi(args[0]); err != nil {
|
||||
return nil, fmt.Errorf("invalid mtu: %v", args[0])
|
||||
}
|
||||
log.Infof("loaded mtu %d.", mtu)
|
||||
return Handler4, nil
|
||||
}
|
||||
|
||||
// Handler4 handles DHCPv4 packets for the mtu plugin
|
||||
func Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
|
||||
if req.IsOptionRequested(dhcpv4.OptionInterfaceMTU) {
|
||||
resp.Options.Update(dhcpv4.Option{Code: dhcpv4.OptionInterfaceMTU, Value: dhcpv4.Uint16(mtu)})
|
||||
}
|
||||
return resp, false
|
||||
}
|
||||
66
plugins/mtu/plugin_test.go
Normal file
66
plugins/mtu/plugin_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package mtu
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
)
|
||||
|
||||
func TestAddServer4(t *testing.T) {
|
||||
req, err := dhcpv4.NewDiscovery(net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, dhcpv4.WithRequestedOptions(dhcpv4.OptionInterfaceMTU))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stub, err := dhcpv4.NewReplyFromRequest(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mtu = 1500
|
||||
|
||||
resp, stop := Handler4(req, stub)
|
||||
if resp == nil {
|
||||
t.Fatal("plugin did not return a message")
|
||||
}
|
||||
if stop {
|
||||
t.Error("plugin interrupted processing")
|
||||
}
|
||||
rMTU, err := dhcpv4.GetUint16(dhcpv4.OptionInterfaceMTU, resp.Options)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to retrieve mtu from response")
|
||||
}
|
||||
|
||||
if mtu != int(rMTU) {
|
||||
t.Errorf("Found %d mtu, expected %d", rMTU, mtu)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotRequested4(t *testing.T) {
|
||||
req, err := dhcpv4.NewDiscovery(net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stub, err := dhcpv4.NewReplyFromRequest(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mtu = 1500
|
||||
req.UpdateOption(dhcpv4.OptParameterRequestList(dhcpv4.OptionBroadcastAddress))
|
||||
|
||||
resp, stop := Handler4(req, stub)
|
||||
if resp == nil {
|
||||
t.Fatal("plugin did not return a message")
|
||||
}
|
||||
if stop {
|
||||
t.Error("plugin interrupted processing")
|
||||
}
|
||||
if mtu, err := dhcpv4.GetUint16(dhcpv4.OptionInterfaceMTU, resp.Options); err == nil {
|
||||
t.Errorf("Retrieve mtu %d in response, expected none", mtu)
|
||||
}
|
||||
}
|
||||
142
plugins/nbp/nbp.go
Normal file
142
plugins/nbp/nbp.go
Normal file
@@ -0,0 +1,142 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
// Package nbp implements handling of an NBP (Network Boot Program) using an
|
||||
// URL, e.g. http://[fe80::abcd:efff:fe12:3456]/my-nbp or tftp://10.0.0.1/my-nbp .
|
||||
// The NBP information is only added if it is requested by the client.
|
||||
//
|
||||
// Note that for DHCPv4, unless the URL is prefixed with a "http", "https" or
|
||||
// "ftp" scheme, the URL will be split into TFTP server name (option 66)
|
||||
// and Bootfile name (option 67), so the scheme will be stripped out, and it
|
||||
// will be treated as a TFTP URL. Anything other than host name and file path
|
||||
// will be ignored (no port, no query string, etc).
|
||||
//
|
||||
// For DHCPv6 OPT_BOOTFILE_URL (option 59) is used, and the value is passed
|
||||
// unmodified. If the query string is specified and contains a "param" key,
|
||||
// its value is also passed as OPT_BOOTFILE_PARAM (option 60), so it will be
|
||||
// duplicated between option 59 and 60.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// server6:
|
||||
// - plugins:
|
||||
// - nbp: http://[2001:db8:a::1]/nbp
|
||||
//
|
||||
// server4:
|
||||
// - plugins:
|
||||
// - nbp: tftp://10.0.0.254/nbp
|
||||
//
|
||||
package nbp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/coredhcp/coredhcp/handler"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("plugins/nbp")
|
||||
|
||||
// Plugin wraps plugin registration information
|
||||
var Plugin = plugins.Plugin{
|
||||
Name: "nbp",
|
||||
Setup6: setup6,
|
||||
Setup4: setup4,
|
||||
}
|
||||
|
||||
var (
|
||||
opt59, opt60 dhcpv6.Option
|
||||
opt66, opt67 *dhcpv4.Option
|
||||
)
|
||||
|
||||
func parseArgs(args ...string) (*url.URL, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, fmt.Errorf("Exactly one argument must be passed to NBP plugin, got %d", len(args))
|
||||
}
|
||||
return url.Parse(args[0])
|
||||
}
|
||||
|
||||
func setup6(args ...string) (handler.Handler6, error) {
|
||||
u, err := parseArgs(args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opt59 = dhcpv6.OptBootFileURL(u.String())
|
||||
params := u.Query().Get("params")
|
||||
if params != "" {
|
||||
opt60 = &dhcpv6.OptionGeneric{
|
||||
OptionCode: dhcpv6.OptionBootfileParam,
|
||||
OptionData: []byte(params),
|
||||
}
|
||||
}
|
||||
log.Printf("loaded NBP plugin for DHCPv6.")
|
||||
return nbpHandler6, nil
|
||||
}
|
||||
|
||||
func setup4(args ...string) (handler.Handler4, error) {
|
||||
u, err := parseArgs(args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var otsn, obfn dhcpv4.Option
|
||||
switch u.Scheme {
|
||||
case "http", "https", "ftp":
|
||||
obfn = dhcpv4.OptBootFileName(u.String())
|
||||
default:
|
||||
otsn = dhcpv4.OptTFTPServerName(u.Host)
|
||||
obfn = dhcpv4.OptBootFileName(u.Path)
|
||||
opt66 = &otsn
|
||||
}
|
||||
|
||||
opt67 = &obfn
|
||||
log.Printf("loaded NBP plugin for DHCPv4.")
|
||||
return nbpHandler4, nil
|
||||
}
|
||||
|
||||
func nbpHandler6(req, resp dhcpv6.DHCPv6) (dhcpv6.DHCPv6, bool) {
|
||||
if opt59 == nil {
|
||||
// nothing to do
|
||||
return resp, true
|
||||
}
|
||||
decap, err := req.GetInnerMessage()
|
||||
if err != nil {
|
||||
log.Errorf("Could not decapsulate request: %v", err)
|
||||
// drop the request, this is probably a critical error in the packet.
|
||||
return nil, true
|
||||
}
|
||||
for _, code := range decap.Options.RequestedOptions() {
|
||||
if code == dhcpv6.OptionBootfileURL {
|
||||
// bootfile URL is requested
|
||||
resp.AddOption(opt59)
|
||||
} else if code == dhcpv6.OptionBootfileParam {
|
||||
// optionally add opt60, bootfile params, if requested
|
||||
if opt60 != nil {
|
||||
resp.AddOption(opt60)
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Debugf("Added NBP %s to request", opt59)
|
||||
return resp, true
|
||||
}
|
||||
|
||||
func nbpHandler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
|
||||
if opt67 == nil {
|
||||
// nothing to do
|
||||
return resp, true
|
||||
}
|
||||
if req.IsOptionRequested(dhcpv4.OptionTFTPServerName) && opt66 != nil {
|
||||
resp.Options.Update(*opt66)
|
||||
log.Debugf("Added NBP %s / %s to request", opt66, opt67)
|
||||
}
|
||||
if req.IsOptionRequested(dhcpv4.OptionBootfileName) {
|
||||
resp.Options.Update(*opt67)
|
||||
log.Debugf("Added NBP %s to request", opt67)
|
||||
}
|
||||
return resp, true
|
||||
}
|
||||
62
plugins/netmask/plugin.go
Normal file
62
plugins/netmask/plugin.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package netmask
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/coredhcp/coredhcp/handler"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("plugins/netmask")
|
||||
|
||||
// Plugin wraps plugin registration information
|
||||
var Plugin = plugins.Plugin{
|
||||
Name: "netmask",
|
||||
Setup4: setup4,
|
||||
}
|
||||
|
||||
var (
|
||||
netmask net.IPMask
|
||||
)
|
||||
|
||||
func setup4(args ...string) (handler.Handler4, error) {
|
||||
log.Printf("loaded plugin for DHCPv4.")
|
||||
if len(args) != 1 {
|
||||
return nil, errors.New("need at least one netmask IP address")
|
||||
}
|
||||
netmaskIP := net.ParseIP(args[0])
|
||||
if netmaskIP.IsUnspecified() {
|
||||
return nil, errors.New("netmask is not valid, got: " + args[0])
|
||||
}
|
||||
netmaskIP = netmaskIP.To4()
|
||||
if netmaskIP == nil {
|
||||
return nil, errors.New("expected an netmask address, got: " + args[0])
|
||||
}
|
||||
netmask = net.IPv4Mask(netmaskIP[0], netmaskIP[1], netmaskIP[2], netmaskIP[3])
|
||||
if !checkValidNetmask(netmask) {
|
||||
return nil, errors.New("netmask is not valid, got: " + args[0])
|
||||
}
|
||||
log.Printf("loaded client netmask")
|
||||
return Handler4, nil
|
||||
}
|
||||
|
||||
//Handler4 handles DHCPv4 packets for the netmask plugin
|
||||
func Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
|
||||
resp.Options.Update(dhcpv4.OptSubnetMask(netmask))
|
||||
return resp, false
|
||||
}
|
||||
|
||||
func checkValidNetmask(netmask net.IPMask) bool {
|
||||
netmaskInt := binary.BigEndian.Uint32(netmask)
|
||||
x := ^netmaskInt
|
||||
y := x + 1
|
||||
return (y & x) == 0
|
||||
}
|
||||
65
plugins/netmask/plugin_test.go
Normal file
65
plugins/netmask/plugin_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package netmask
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCheckValidNetmask(t *testing.T) {
|
||||
assert.True(t, checkValidNetmask(net.IPv4Mask(255, 255, 255, 0)))
|
||||
assert.True(t, checkValidNetmask(net.IPv4Mask(255, 255, 0, 0)))
|
||||
assert.True(t, checkValidNetmask(net.IPv4Mask(255, 0, 0, 0)))
|
||||
assert.True(t, checkValidNetmask(net.IPv4Mask(0, 0, 0, 0)))
|
||||
|
||||
assert.False(t, checkValidNetmask(net.IPv4Mask(0, 255, 255, 255)))
|
||||
assert.False(t, checkValidNetmask(net.IPv4Mask(0, 0, 255, 255)))
|
||||
assert.False(t, checkValidNetmask(net.IPv4Mask(0, 0, 0, 255)))
|
||||
}
|
||||
|
||||
func TestHandler4(t *testing.T) {
|
||||
// set plugin netmask
|
||||
netmask = net.IPv4Mask(255, 255, 255, 0)
|
||||
|
||||
// prepare DHCPv4 request
|
||||
req := &dhcpv4.DHCPv4{}
|
||||
resp := &dhcpv4.DHCPv4{
|
||||
Options: dhcpv4.Options{},
|
||||
}
|
||||
|
||||
// if we handle this DHCP request, the netmask should be one of the options
|
||||
// of the result
|
||||
result, stop := Handler4(req, resp)
|
||||
assert.Same(t, result, resp)
|
||||
assert.False(t, stop)
|
||||
assert.EqualValues(t, netmask, resp.Options.Get(dhcpv4.OptionSubnetMask))
|
||||
}
|
||||
|
||||
func TestSetup4(t *testing.T) {
|
||||
// valid configuration
|
||||
_, err := setup4("255.255.255.0")
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, netmask, net.IPv4Mask(255, 255, 255, 0))
|
||||
|
||||
// no configuration
|
||||
_, err = setup4()
|
||||
assert.Error(t, err)
|
||||
|
||||
// unspecified netmask
|
||||
_, err = setup4("0.0.0.0")
|
||||
assert.Error(t, err)
|
||||
|
||||
// ipv6 prefix
|
||||
_, err = setup4("ff02::/64")
|
||||
assert.Error(t, err)
|
||||
|
||||
// invalid netmask
|
||||
_, err = setup4("0.0.0.255")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
114
plugins/plugin.go
Normal file
114
plugins/plugin.go
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/coredhcp/coredhcp/config"
|
||||
"github.com/coredhcp/coredhcp/handler"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("plugins")
|
||||
|
||||
// Plugin represents a plugin object.
|
||||
// Setup6 and Setup4 are the setup functions for DHCPv6 and DHCPv4 handlers
|
||||
// respectively. Both setup functions can be nil.
|
||||
type Plugin struct {
|
||||
Name string
|
||||
Setup6 SetupFunc6
|
||||
Setup4 SetupFunc4
|
||||
}
|
||||
|
||||
// RegisteredPlugins maps a plugin name to a Plugin instance.
|
||||
var RegisteredPlugins = make(map[string]*Plugin)
|
||||
|
||||
// SetupFunc6 defines a plugin setup function for DHCPv6
|
||||
type SetupFunc6 func(args ...string) (handler.Handler6, error)
|
||||
|
||||
// SetupFunc4 defines a plugin setup function for DHCPv6
|
||||
type SetupFunc4 func(args ...string) (handler.Handler4, error)
|
||||
|
||||
// RegisterPlugin registers a plugin.
|
||||
func RegisterPlugin(plugin *Plugin) error {
|
||||
if plugin == nil {
|
||||
return errors.New("cannot register nil plugin")
|
||||
}
|
||||
log.Printf("Registering plugin '%s'", plugin.Name)
|
||||
if _, ok := RegisteredPlugins[plugin.Name]; ok {
|
||||
// TODO this highlights that asking the plugins to register themselves
|
||||
// is not the right approach. Need to register them in the main program.
|
||||
log.Panicf("Plugin '%s' is already registered", plugin.Name)
|
||||
}
|
||||
RegisteredPlugins[plugin.Name] = plugin
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadPlugins reads a Config object and loads the plugins as specified in the
|
||||
// `plugins` section, in order. For a plugin to be available, it must have been
|
||||
// previously registered with plugins.RegisterPlugin. This is normally done at
|
||||
// plugin import time.
|
||||
// This function returns the list of loaded v6 plugins, the list of loaded v4
|
||||
// plugins, and an error if any.
|
||||
func LoadPlugins(conf *config.Config) ([]handler.Handler4, []handler.Handler6, error) {
|
||||
log.Print("Loading plugins...")
|
||||
handlers4 := make([]handler.Handler4, 0)
|
||||
handlers6 := make([]handler.Handler6, 0)
|
||||
|
||||
if conf.Server6 == nil && conf.Server4 == nil {
|
||||
return nil, nil, errors.New("no configuration found for either DHCPv6 or DHCPv4")
|
||||
}
|
||||
|
||||
// now load the plugins. We need to call its setup function with
|
||||
// the arguments extracted above. The setup function is mapped in
|
||||
// plugins.RegisteredPlugins .
|
||||
|
||||
// Load DHCPv6 plugins.
|
||||
if conf.Server6 != nil {
|
||||
for _, pluginConf := range conf.Server6.Plugins {
|
||||
if plugin, ok := RegisteredPlugins[pluginConf.Name]; ok {
|
||||
log.Printf("DHCPv6: loading plugin `%s`", pluginConf.Name)
|
||||
if plugin.Setup6 == nil {
|
||||
log.Warningf("DHCPv6: plugin `%s` has no setup function for DHCPv6", pluginConf.Name)
|
||||
continue
|
||||
}
|
||||
h6, err := plugin.Setup6(pluginConf.Args...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
} else if h6 == nil {
|
||||
return nil, nil, config.ConfigErrorFromString("no DHCPv6 handler for plugin %s", pluginConf.Name)
|
||||
}
|
||||
handlers6 = append(handlers6, h6)
|
||||
} else {
|
||||
return nil, nil, config.ConfigErrorFromString("DHCPv6: unknown plugin `%s`", pluginConf.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Load DHCPv4 plugins. Yes, duplicated code, there's not really much that
|
||||
// can be deduplicated here.
|
||||
if conf.Server4 != nil {
|
||||
for _, pluginConf := range conf.Server4.Plugins {
|
||||
if plugin, ok := RegisteredPlugins[pluginConf.Name]; ok {
|
||||
log.Printf("DHCPv4: loading plugin `%s`", pluginConf.Name)
|
||||
if plugin.Setup4 == nil {
|
||||
log.Warningf("DHCPv4: plugin `%s` has no setup function for DHCPv4", pluginConf.Name)
|
||||
continue
|
||||
}
|
||||
h4, err := plugin.Setup4(pluginConf.Args...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
} else if h4 == nil {
|
||||
return nil, nil, config.ConfigErrorFromString("no DHCPv4 handler for plugin %s", pluginConf.Name)
|
||||
}
|
||||
handlers4 = append(handlers4, h4)
|
||||
} else {
|
||||
return nil, nil, config.ConfigErrorFromString("DHCPv4: unknown plugin `%s`", pluginConf.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return handlers4, handlers6, nil
|
||||
}
|
||||
271
plugins/prefix/plugin.go
Normal file
271
plugins/prefix/plugin.go
Normal file
@@ -0,0 +1,271 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
// Package prefix implements a plugin offering prefixes to clients requesting them
|
||||
// This plugin attributes prefixes to clients requesting them with IA_PREFIX requests.
|
||||
//
|
||||
// Arguments for the plugin configuration are as follows, in this order:
|
||||
// - prefix: The base prefix from which assigned prefixes are carved
|
||||
// - max: maximum size of the prefix delegated to clients. When a client requests a larger prefix
|
||||
// than this, this is the size of the offered prefix
|
||||
package prefix
|
||||
|
||||
// FIXME: various settings will be hardcoded (default size, minimum size, lease times) pending a
|
||||
// better configuration system
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bits-and-blooms/bitset"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
dhcpIana "github.com/insomniacslk/dhcp/iana"
|
||||
|
||||
"github.com/coredhcp/coredhcp/handler"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
"github.com/coredhcp/coredhcp/plugins/allocators"
|
||||
"github.com/coredhcp/coredhcp/plugins/allocators/bitmap"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("plugins/prefix")
|
||||
|
||||
// Plugin registers the prefix. Prefix delegation only exists for DHCPv6
|
||||
var Plugin = plugins.Plugin{
|
||||
Name: "prefix",
|
||||
Setup6: setupPrefix,
|
||||
}
|
||||
|
||||
const leaseDuration = 3600 * time.Second
|
||||
|
||||
func setupPrefix(args ...string) (handler.Handler6, error) {
|
||||
// - prefix: 2001:db8::/48 64
|
||||
if len(args) < 2 {
|
||||
return nil, errors.New("Need both a subnet and an allocation max size")
|
||||
}
|
||||
|
||||
_, prefix, err := net.ParseCIDR(args[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invalid pool subnet: %v", err)
|
||||
}
|
||||
|
||||
allocSize, err := strconv.Atoi(args[1])
|
||||
if err != nil || allocSize > 128 || allocSize < 0 {
|
||||
return nil, fmt.Errorf("Invalid prefix length: %v", err)
|
||||
}
|
||||
|
||||
// TODO: select allocators based on heuristics or user configuration
|
||||
alloc, err := bitmap.NewBitmapAllocator(*prefix, allocSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not initialize prefix allocator: %v", err)
|
||||
}
|
||||
|
||||
return (&Handler{
|
||||
Records: make(map[string][]lease),
|
||||
allocator: alloc,
|
||||
}).Handle, nil
|
||||
}
|
||||
|
||||
type lease struct {
|
||||
Prefix net.IPNet
|
||||
Expire time.Time
|
||||
}
|
||||
|
||||
// Handler holds state of allocations for the plugin
|
||||
type Handler struct {
|
||||
// Mutex here is the simplest implementation fit for purpose.
|
||||
// We can revisit for perf when we move lease management to separate plugins
|
||||
sync.Mutex
|
||||
// Records has a string'd []byte as key, because []byte can't be a key itself
|
||||
// Since it's not valid utf-8 we can't use any other string function though
|
||||
Records map[string][]lease
|
||||
allocator allocators.Allocator
|
||||
}
|
||||
|
||||
// samePrefix returns true if both prefixes are defined and equal
|
||||
// The empty prefix is equal to nothing, not even itself
|
||||
func samePrefix(a, b *net.IPNet) bool {
|
||||
if a == nil || b == nil {
|
||||
return false
|
||||
}
|
||||
return a.IP.Equal(b.IP) && bytes.Equal(a.Mask, b.Mask)
|
||||
}
|
||||
|
||||
// recordKey computes the key for the Records array from the client ID
|
||||
func recordKey(d dhcpv6.DUID) string {
|
||||
return string(d.ToBytes())
|
||||
}
|
||||
|
||||
// Handle processes DHCPv6 packets for the prefix plugin for a given allocator/leaseset
|
||||
func (h *Handler) Handle(req, resp dhcpv6.DHCPv6) (dhcpv6.DHCPv6, bool) {
|
||||
msg, err := req.GetInnerMessage()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil, true
|
||||
}
|
||||
|
||||
client := msg.Options.ClientID()
|
||||
if client == nil {
|
||||
log.Error("Invalid packet received, no clientID")
|
||||
return nil, true
|
||||
}
|
||||
|
||||
// Each request IA_PD requires an IA_PD response
|
||||
for _, iapd := range msg.Options.IAPD() {
|
||||
if err != nil {
|
||||
log.Errorf("Malformed IAPD received: %v", err)
|
||||
resp.AddOption(&dhcpv6.OptStatusCode{StatusCode: dhcpIana.StatusMalformedQuery})
|
||||
return resp, true
|
||||
}
|
||||
|
||||
iapdResp := &dhcpv6.OptIAPD{
|
||||
IaId: iapd.IaId,
|
||||
}
|
||||
|
||||
// First figure out what prefixes the client wants
|
||||
hints := iapd.Options.Prefixes()
|
||||
if len(hints) == 0 {
|
||||
// If there are no IAPrefix hints, this is still a valid IA_PD request (just
|
||||
// unspecified) and we must attempt to allocate a prefix; so we include an empty hint
|
||||
// which is equivalent to no hint
|
||||
hints = []*dhcpv6.OptIAPrefix{{Prefix: &net.IPNet{}}}
|
||||
}
|
||||
|
||||
// Bitmap to track which requests are already satisfied or not
|
||||
satisfied := bitset.New(uint(len(hints)))
|
||||
|
||||
// A possible simple optimization here would be to be able to lock single map values
|
||||
// individually instead of the whole map, since we lock for some amount of time
|
||||
h.Lock()
|
||||
knownLeases := h.Records[recordKey(client)]
|
||||
// Bitmap to track which leases are already given in this exchange
|
||||
givenOut := bitset.New(uint(len(knownLeases)))
|
||||
|
||||
// This is, for now, a set of heuristics, to reconcile the requests (prefix hints asked
|
||||
// by the clients) with what's on offer (existing leases for this client, plus new blocks)
|
||||
|
||||
// Try to find leases that exactly match a hint, and extend them to satisfy the request
|
||||
// This is the safest heuristic, if the lease matches exactly we know we aren't missing
|
||||
// assigning it to a better candidate request
|
||||
for hintIdx, h := range hints {
|
||||
for leaseIdx := range knownLeases {
|
||||
if samePrefix(h.Prefix, &knownLeases[leaseIdx].Prefix) {
|
||||
expire := time.Now().Add(leaseDuration)
|
||||
if knownLeases[leaseIdx].Expire.Before(expire) {
|
||||
knownLeases[leaseIdx].Expire = expire
|
||||
}
|
||||
satisfied.Set(uint(hintIdx))
|
||||
givenOut.Set(uint(leaseIdx))
|
||||
addPrefix(iapdResp, knownLeases[leaseIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then handle the empty hints, by giving out any remaining lease we
|
||||
// have already assigned to this client
|
||||
for hintIdx, h := range hints {
|
||||
if satisfied.Test(uint(hintIdx)) ||
|
||||
(h.Prefix != nil && !h.Prefix.IP.Equal(net.IPv6zero)) {
|
||||
continue
|
||||
}
|
||||
for leaseIdx, l := range knownLeases {
|
||||
if givenOut.Test(uint(leaseIdx)) {
|
||||
continue
|
||||
}
|
||||
|
||||
// If a length was requested, only give out prefixes of that length
|
||||
// This is a bad heuristic depending on the allocator behavior, to be improved
|
||||
if hintPrefixLen, _ := h.Prefix.Mask.Size(); hintPrefixLen != 0 {
|
||||
leasePrefixLen, _ := l.Prefix.Mask.Size()
|
||||
if hintPrefixLen != leasePrefixLen {
|
||||
continue
|
||||
}
|
||||
}
|
||||
expire := time.Now().Add(leaseDuration)
|
||||
if knownLeases[leaseIdx].Expire.Before(expire) {
|
||||
knownLeases[leaseIdx].Expire = expire
|
||||
}
|
||||
satisfied.Set(uint(hintIdx))
|
||||
givenOut.Set(uint(leaseIdx))
|
||||
addPrefix(iapdResp, knownLeases[leaseIdx])
|
||||
}
|
||||
}
|
||||
|
||||
// Now remains requests with a hint that we can't trivially satisfy, and possibly expired
|
||||
// leases that haven't been explicitly requested again.
|
||||
// A possible improvement here would be to try to widen existing leases, to satisfy wider
|
||||
// requests that contain an existing leases; and to try to break down existing leases into
|
||||
// smaller allocations, to satisfy requests for a subnet of an existing lease
|
||||
// We probably don't need such complex behavior (the vast majority of requests will come
|
||||
// with an empty, or length-only hint)
|
||||
|
||||
// Assign a new lease to satisfy the request
|
||||
var newLeases []lease
|
||||
for i, prefix := range hints {
|
||||
if satisfied.Test(uint(i)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if prefix.Prefix == nil {
|
||||
// XXX: replace usage of dhcp.OptIAPrefix with a better struct in this inner
|
||||
// function to avoid repeated nullpointer checks
|
||||
prefix.Prefix = &net.IPNet{}
|
||||
}
|
||||
allocated, err := h.allocator.Allocate(*prefix.Prefix)
|
||||
if err != nil {
|
||||
log.Debugf("Nothing allocated for hinted prefix %s", prefix)
|
||||
continue
|
||||
}
|
||||
l := lease{
|
||||
Expire: time.Now().Add(leaseDuration),
|
||||
Prefix: allocated,
|
||||
}
|
||||
|
||||
addPrefix(iapdResp, l)
|
||||
newLeases = append(knownLeases, l)
|
||||
log.Debugf("Allocated %s to %s (IAID: %x)", &allocated, client, iapd.IaId)
|
||||
}
|
||||
|
||||
if newLeases != nil {
|
||||
h.Records[recordKey(client)] = newLeases
|
||||
}
|
||||
h.Unlock()
|
||||
|
||||
if len(iapdResp.Options.Options) == 0 {
|
||||
log.Debugf("No valid prefix to return for IAID %x", iapd.IaId)
|
||||
iapdResp.Options.Add(&dhcpv6.OptStatusCode{
|
||||
StatusCode: dhcpIana.StatusNoPrefixAvail,
|
||||
})
|
||||
}
|
||||
|
||||
resp.AddOption(iapdResp)
|
||||
}
|
||||
|
||||
return resp, false
|
||||
}
|
||||
|
||||
func addPrefix(resp *dhcpv6.OptIAPD, l lease) {
|
||||
lifetime := time.Until(l.Expire)
|
||||
|
||||
resp.Options.Add(&dhcpv6.OptIAPrefix{
|
||||
PreferredLifetime: lifetime,
|
||||
ValidLifetime: lifetime,
|
||||
Prefix: dup(&l.Prefix),
|
||||
})
|
||||
}
|
||||
|
||||
func dup(src *net.IPNet) (dst *net.IPNet) {
|
||||
dst = &net.IPNet{
|
||||
IP: make(net.IP, net.IPv6len),
|
||||
Mask: make(net.IPMask, net.IPv6len),
|
||||
}
|
||||
copy(dst.IP, src.IP)
|
||||
copy(dst.Mask, src.Mask)
|
||||
return dst
|
||||
}
|
||||
93
plugins/prefix/plugin_test.go
Normal file
93
plugins/prefix/plugin_test.go
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package prefix
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
dhcpIana "github.com/insomniacslk/dhcp/iana"
|
||||
)
|
||||
|
||||
func TestRoundTrip(t *testing.T) {
|
||||
reqIAID := [4]uint8{0x12, 0x34, 0x56, 0x78}
|
||||
|
||||
req, err := dhcpv6.NewMessage()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.AddOption(dhcpv6.OptClientID(&dhcpv6.DUIDLL{
|
||||
HWType: dhcpIana.HWTypeEthernet,
|
||||
LinkLayerAddr: net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff},
|
||||
}))
|
||||
req.AddOption(&dhcpv6.OptIAPD{
|
||||
IaId: reqIAID,
|
||||
T1: 0,
|
||||
T2: 0,
|
||||
})
|
||||
|
||||
resp, err := dhcpv6.NewAdvertiseFromSolicit(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
handler, err := setupPrefix("2001:db8::/48", "64")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
result, final := handler(req, resp)
|
||||
if final {
|
||||
t.Log("Handler declared final")
|
||||
}
|
||||
t.Logf("%#v", result)
|
||||
|
||||
// Sanity checks on the response
|
||||
success := result.GetOption(dhcpv6.OptionStatusCode)
|
||||
var mo dhcpv6.MessageOptions
|
||||
if len(success) > 1 {
|
||||
t.Fatal("Got multiple StatusCode options")
|
||||
} else if len(success) == 0 { // Everything OK
|
||||
} else if err := mo.FromBytes(success[0].ToBytes()); err != nil || mo.Status().StatusCode != dhcpIana.StatusSuccess {
|
||||
t.Fatalf("Did not get a (implicit or explicit) success status code: %v", success)
|
||||
}
|
||||
|
||||
var iapd *dhcpv6.OptIAPD
|
||||
{
|
||||
// Check for IA_PD
|
||||
iapds := result.(*dhcpv6.Message).Options.IAPD()
|
||||
if len(iapds) != 1 {
|
||||
t.Fatal("Malformed response, expected exactly 1 IAPD")
|
||||
}
|
||||
iapd = iapds[0]
|
||||
}
|
||||
if iapd.IaId != reqIAID {
|
||||
t.Fatalf("IAID doesn't match: request %x, response: %x", iapd.IaId, reqIAID)
|
||||
}
|
||||
|
||||
// Check the status code
|
||||
if status := result.(*dhcpv6.Message).Options.Status(); status != nil && status.StatusCode != dhcpIana.StatusSuccess {
|
||||
t.Fatalf("Did not get a (implicit or explicit) success status code: %v", success)
|
||||
}
|
||||
|
||||
t.Logf("%#v", iapd)
|
||||
// Check IAPrefix within IAPD
|
||||
if len(iapd.Options.Prefixes()) != 1 {
|
||||
t.Fatalf("Response did not contain exactly one prefix in the IA_PD option (found %s)",
|
||||
iapd.Options.Prefixes())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDup(t *testing.T) {
|
||||
_, prefix, err := net.ParseCIDR("2001:db8::/48")
|
||||
if err != nil {
|
||||
panic("bad cidr")
|
||||
}
|
||||
dupPrefix := dup(prefix)
|
||||
if !samePrefix(dupPrefix, prefix) {
|
||||
t.Fatalf("dup doesn't work: got %v expected %v", dupPrefix, prefix)
|
||||
}
|
||||
}
|
||||
193
plugins/range/plugin.go
Normal file
193
plugins/range/plugin.go
Normal file
@@ -0,0 +1,193 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package rangeplugin
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coredhcp/coredhcp/grpc_server/dhcpServer"
|
||||
"github.com/coredhcp/coredhcp/handler"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
"github.com/coredhcp/coredhcp/plugins/allocators"
|
||||
"github.com/coredhcp/coredhcp/plugins/allocators/bitmap"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("plugins/range")
|
||||
|
||||
// Plugin wraps plugin registration information
|
||||
var Plugin = plugins.Plugin{
|
||||
Name: "range",
|
||||
Setup4: setupRange,
|
||||
}
|
||||
|
||||
//Record holds an IP lease record
|
||||
type Record struct {
|
||||
IP net.IP
|
||||
Static bool
|
||||
Expires int
|
||||
Hostname string
|
||||
}
|
||||
|
||||
// PluginState is the data held by an instance of the range plugin
|
||||
type PluginState struct {
|
||||
// Rough lock for the whole plugin, we'll get better performance once we use leasestorage
|
||||
sync.Mutex
|
||||
// Recordsv4 holds a MAC -> IP address and lease time mapping
|
||||
Recordsv4 map[string]*Record
|
||||
LeaseTime time.Duration
|
||||
leasedb *sql.DB
|
||||
allocator allocators.Allocator
|
||||
}
|
||||
|
||||
var p PluginState
|
||||
|
||||
// Handler4 handles DHCPv4 packets for the range plugin
|
||||
func (p *PluginState) Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
record, ok := p.Recordsv4[req.ClientHWAddr.String()]
|
||||
hostname := req.HostName()
|
||||
if !ok {
|
||||
// Allocating new address since there isn't one allocated
|
||||
log.Printf("MAC address %s is new, leasing new IPv4 address", req.ClientHWAddr.String())
|
||||
var netIp net.IP
|
||||
var isStatic bool
|
||||
if addr := GetStaticIp(req.ClientHWAddr.String()); addr != "" {
|
||||
netIp = net.ParseIP(addr)
|
||||
isStatic = true
|
||||
} else {
|
||||
ip, err := p.allocator.Allocate(net.IPNet{})
|
||||
if err != nil {
|
||||
log.Errorf("Could not allocate IP for MAC %s: %v", req.ClientHWAddr.String(), err)
|
||||
return nil, true
|
||||
}
|
||||
netIp = ip.IP.To4()
|
||||
}
|
||||
rec := Record{
|
||||
IP: netIp,
|
||||
Static: isStatic,
|
||||
Expires: int(time.Now().Add(p.LeaseTime).Unix()),
|
||||
Hostname: hostname,
|
||||
}
|
||||
err := p.saveIPAddress(req.ClientHWAddr, &rec)
|
||||
if err != nil {
|
||||
log.Errorf("SaveIPAddress for MAC %s failed: %v", req.ClientHWAddr.String(), err)
|
||||
}
|
||||
p.Recordsv4[req.ClientHWAddr.String()] = &rec
|
||||
record = &rec
|
||||
} else {
|
||||
if addr := GetStaticIp(req.ClientHWAddr.String()); addr != "" {
|
||||
record.IP = net.ParseIP(addr)
|
||||
record.Static = true
|
||||
}
|
||||
// Ensure we extend the existing lease at least past when the one we're giving expires
|
||||
expiry := time.Unix(int64(record.Expires), 0)
|
||||
if expiry.Before(time.Now().Add(p.LeaseTime)) {
|
||||
record.Expires = int(time.Now().Add(p.LeaseTime).Round(time.Second).Unix())
|
||||
record.Hostname = hostname
|
||||
err := p.saveIPAddress(req.ClientHWAddr, record)
|
||||
if err != nil {
|
||||
log.Errorf("Could not persist lease for MAC %s: %v", req.ClientHWAddr.String(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
resp.YourIPAddr = record.IP
|
||||
resp.Options.Update(dhcpv4.OptIPAddressLeaseTime(p.LeaseTime.Round(time.Second)))
|
||||
log.Printf("found IP address %s for MAC %s", record.IP, req.ClientHWAddr.String())
|
||||
return resp, false
|
||||
}
|
||||
|
||||
func GetRecord(clientHWAddr string) *Record {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
return p.Recordsv4[clientHWAddr]
|
||||
}
|
||||
|
||||
func GetDhcpInfo() (*dhcpServer.DhcpInfo, error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
var dhcpInfo dhcpServer.DhcpInfo
|
||||
for mac, record := range p.Recordsv4 {
|
||||
if record.Static { continue }
|
||||
endTime := time.Unix(int64(record.Expires), 0)
|
||||
startTime := endTime.Add(-p.LeaseTime)
|
||||
dhcpInfo.UeInfo = append(dhcpInfo.UeInfo, &dhcpServer.UeInfo{
|
||||
Ip: record.IP.String(),
|
||||
Mac: mac,
|
||||
Hostname: record.Hostname,
|
||||
StartTime: startTime.Format(time.DateTime),
|
||||
EndTime: endTime.Format(time.DateTime),
|
||||
})
|
||||
}
|
||||
return &dhcpInfo, nil
|
||||
}
|
||||
|
||||
func setupRange(args ...string) (handler.Handler4, error) {
|
||||
var err error
|
||||
|
||||
if len(args) < 4 {
|
||||
return nil, fmt.Errorf("invalid number of arguments, want: 4 (file name, start IP, end IP, lease time), got: %d", len(args))
|
||||
}
|
||||
filename := args[0]
|
||||
if filename == "" {
|
||||
return nil, errors.New("file name cannot be empty")
|
||||
}
|
||||
ipRangeStart := net.ParseIP(args[1])
|
||||
if ipRangeStart.To4() == nil {
|
||||
return nil, fmt.Errorf("invalid IPv4 address: %v", args[1])
|
||||
}
|
||||
ipRangeEnd := net.ParseIP(args[2])
|
||||
if ipRangeEnd.To4() == nil {
|
||||
return nil, fmt.Errorf("invalid IPv4 address: %v", args[2])
|
||||
}
|
||||
if binary.BigEndian.Uint32(ipRangeStart.To4()) >= binary.BigEndian.Uint32(ipRangeEnd.To4()) {
|
||||
return nil, errors.New("start of IP range has to be lower than the end of an IP range")
|
||||
}
|
||||
|
||||
p.allocator, err = bitmap.NewIPv4Allocator(ipRangeStart, ipRangeEnd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create an allocator: %w", err)
|
||||
}
|
||||
|
||||
p.LeaseTime, err = time.ParseDuration(args[3])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid lease duration: %v", args[3])
|
||||
}
|
||||
|
||||
if err := p.registerBackingDB(filename); err != nil {
|
||||
return nil, fmt.Errorf("could not setup lease storage: %w", err)
|
||||
}
|
||||
p.Recordsv4, err = loadRecords(p.leasedb)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load records from file: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Loaded %d DHCPv4 leases from %s", len(p.Recordsv4), filename)
|
||||
|
||||
for _, v := range p.Recordsv4 {
|
||||
if v.Static {
|
||||
continue
|
||||
}
|
||||
ip, err := p.allocator.Allocate(net.IPNet{IP: v.IP})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to re-allocate leased ip %v: %v", v.IP.String(), err)
|
||||
}
|
||||
if ip.IP.String() != v.IP.String() {
|
||||
return nil, fmt.Errorf("allocator did not re-allocate requested leased ip %v: %v", v.IP.String(), ip.String())
|
||||
}
|
||||
}
|
||||
|
||||
importStaticIpFile()
|
||||
|
||||
return p.Handler4, nil
|
||||
}
|
||||
169
plugins/range/plugin.go.new
Normal file
169
plugins/range/plugin.go.new
Normal file
@@ -0,0 +1,169 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package rangeplugin
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coredhcp/coredhcp/handler"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
"github.com/coredhcp/coredhcp/plugins/allocators"
|
||||
"github.com/coredhcp/coredhcp/plugins/allocators/bitmap"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("plugins/range")
|
||||
|
||||
// Plugin wraps plugin registration information
|
||||
var Plugin = plugins.Plugin{
|
||||
Name: "range",
|
||||
Setup4: setupRange,
|
||||
}
|
||||
|
||||
//Record holds an IP lease record
|
||||
type Record struct {
|
||||
IP net.IP
|
||||
expires int
|
||||
hostname string
|
||||
}
|
||||
|
||||
// PluginState is the data held by an instance of the range plugin
|
||||
type PluginState struct {
|
||||
// Rough lock for the whole plugin, we'll get better performance once we use leasestorage
|
||||
sync.Mutex
|
||||
// Recordsv4 holds a MAC -> IP address and lease time mapping
|
||||
Recordsv4 map[string]*Record
|
||||
LeaseTime time.Duration
|
||||
leasedb *sql.DB
|
||||
allocator allocators.Allocator
|
||||
}
|
||||
|
||||
var p PluginState
|
||||
|
||||
// Handler4 handles DHCPv4 packets for the range plugin
|
||||
func (p *PluginState) Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
if ip := GetStaticIp(req.ClientHWAddr.String()); ip != "" {
|
||||
resp.YourIPAddr = net.ParseIP(ip)
|
||||
resp.Options.Update(dhcpv4.OptIPAddressLeaseTime(p.LeaseTime.Round(time.Second)))
|
||||
log.Printf("found static IP address %s for MAC %s", ip, req.ClientHWAddr.String())
|
||||
return resp, false
|
||||
}
|
||||
|
||||
record, ok := p.Recordsv4[req.ClientHWAddr.String()]
|
||||
hostname := req.HostName()
|
||||
if !ok {
|
||||
// Allocating new address since there isn't one allocated
|
||||
log.Printf("MAC address %s is new, leasing new IPv4 address", req.ClientHWAddr.String())
|
||||
ip, err := p.allocator.Allocate(net.IPNet{})
|
||||
if err != nil {
|
||||
log.Errorf("Could not allocate IP for MAC %s: %v", req.ClientHWAddr.String(), err)
|
||||
return nil, true
|
||||
}
|
||||
rec := Record{
|
||||
IP: ip.IP.To4(),
|
||||
expires: int(time.Now().Add(p.LeaseTime).Unix()),
|
||||
hostname: hostname,
|
||||
}
|
||||
err = p.saveIPAddress(req.ClientHWAddr, &rec)
|
||||
if err != nil {
|
||||
log.Errorf("SaveIPAddress for MAC %s failed: %v", req.ClientHWAddr.String(), err)
|
||||
}
|
||||
p.Recordsv4[req.ClientHWAddr.String()] = &rec
|
||||
record = &rec
|
||||
} else {
|
||||
// Ensure we extend the existing lease at least past when the one we're giving expires
|
||||
expiry := time.Unix(int64(record.expires), 0)
|
||||
if expiry.Before(time.Now().Add(p.LeaseTime)) {
|
||||
record.expires = int(time.Now().Add(p.LeaseTime).Round(time.Second).Unix())
|
||||
record.hostname = hostname
|
||||
err := p.saveIPAddress(req.ClientHWAddr, record)
|
||||
if err != nil {
|
||||
log.Errorf("Could not persist lease for MAC %s: %v", req.ClientHWAddr.String(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
resp.YourIPAddr = record.IP
|
||||
resp.Options.Update(dhcpv4.OptIPAddressLeaseTime(p.LeaseTime.Round(time.Second)))
|
||||
log.Printf("found IP address %s for MAC %s", record.IP, req.ClientHWAddr.String())
|
||||
return resp, false
|
||||
}
|
||||
|
||||
func GetRecord(clientHWAddr string) (string, string) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
record := p.Recordsv4[clientHWAddr]
|
||||
if record != nil {
|
||||
return record.IP.String(), record.hostname
|
||||
} else {
|
||||
return "-", "-"
|
||||
}
|
||||
}
|
||||
|
||||
func setupRange(args ...string) (handler.Handler4, error) {
|
||||
var err error
|
||||
|
||||
if len(args) < 4 {
|
||||
return nil, fmt.Errorf("invalid number of arguments, want: 4 (file name, start IP, end IP, lease time), got: %d", len(args))
|
||||
}
|
||||
filename := args[0]
|
||||
if filename == "" {
|
||||
return nil, errors.New("file name cannot be empty")
|
||||
}
|
||||
ipRangeStart := net.ParseIP(args[1])
|
||||
if ipRangeStart.To4() == nil {
|
||||
return nil, fmt.Errorf("invalid IPv4 address: %v", args[1])
|
||||
}
|
||||
ipRangeEnd := net.ParseIP(args[2])
|
||||
if ipRangeEnd.To4() == nil {
|
||||
return nil, fmt.Errorf("invalid IPv4 address: %v", args[2])
|
||||
}
|
||||
if binary.BigEndian.Uint32(ipRangeStart.To4()) >= binary.BigEndian.Uint32(ipRangeEnd.To4()) {
|
||||
return nil, errors.New("start of IP range has to be lower than the end of an IP range")
|
||||
}
|
||||
|
||||
p.allocator, err = bitmap.NewIPv4Allocator(ipRangeStart, ipRangeEnd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create an allocator: %w", err)
|
||||
}
|
||||
|
||||
p.LeaseTime, err = time.ParseDuration(args[3])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid lease duration: %v", args[3])
|
||||
}
|
||||
|
||||
if err := p.registerBackingDB(filename); err != nil {
|
||||
return nil, fmt.Errorf("could not setup lease storage: %w", err)
|
||||
}
|
||||
p.Recordsv4, err = loadRecords(p.leasedb)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load records from file: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Loaded %d DHCPv4 leases from %s", len(p.Recordsv4), filename)
|
||||
|
||||
for _, v := range p.Recordsv4 {
|
||||
ip, err := p.allocator.Allocate(net.IPNet{IP: v.IP})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to re-allocate leased ip %v: %v", v.IP.String(), err)
|
||||
}
|
||||
if ip.IP.String() != v.IP.String() {
|
||||
return nil, fmt.Errorf("allocator did not re-allocate requested leased ip %v: %v", v.IP.String(), ip.String())
|
||||
}
|
||||
}
|
||||
|
||||
importStaticIpFile()
|
||||
|
||||
return p.Handler4, nil
|
||||
}
|
||||
50
plugins/range/static_ip.go
Normal file
50
plugins/range/static_ip.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package rangeplugin
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
const staticIpFile string = "./static_ip.csv"
|
||||
|
||||
var staticIpPool map[string]string // mac as key
|
||||
var macPool map[string]string // ip as key
|
||||
|
||||
func importStaticIpFile() {
|
||||
fs, err := os.Open(staticIpFile)
|
||||
if err != nil {
|
||||
//fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer fs.Close()
|
||||
|
||||
staticIpPool = make(map[string]string)
|
||||
macPool = make(map[string]string)
|
||||
|
||||
r := csv.NewReader(fs)
|
||||
for {
|
||||
row, err := r.Read()
|
||||
if err != nil && err != io.EOF {
|
||||
fmt.Printf("Can not read, err is %+v", err)
|
||||
}
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
//fmt.Println(row)
|
||||
if len(row) > 1 {
|
||||
staticIpPool[row[0]] = row[1]
|
||||
macPool[row[1]] = row[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetStaticIp(mac string) string {
|
||||
return staticIpPool[mac]
|
||||
}
|
||||
|
||||
func IsStaticIp(ip string) bool {
|
||||
_, ok := macPool[ip]
|
||||
return ok
|
||||
}
|
||||
93
plugins/range/storage.go
Normal file
93
plugins/range/storage.go
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package rangeplugin
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
func loadDB(path string) (*sql.DB, error) {
|
||||
db, err := sql.Open("sqlite3", fmt.Sprintf("file:%s", path))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open database (%T): %w", err, err)
|
||||
}
|
||||
if _, err := db.Exec("create table if not exists leases4 (mac string not null, ip string not null, static bool, expiry int, hostname string not null, primary key (mac, ip))"); err != nil {
|
||||
return nil, fmt.Errorf("table creation failed: %w", err)
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// loadRecords loads the DHCPv6/v4 Records global map with records stored on
|
||||
// the specified file. The records have to be one per line, a mac address and an
|
||||
// IP address.
|
||||
func loadRecords(db *sql.DB) (map[string]*Record, error) {
|
||||
rows, err := db.Query("select mac, ip, static, expiry, hostname from leases4")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query leases database: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
var (
|
||||
mac, ip, hostname string
|
||||
static bool
|
||||
expiry int
|
||||
records = make(map[string]*Record)
|
||||
)
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&mac, &ip, &static, &expiry, &hostname); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan row: %w", err)
|
||||
}
|
||||
hwaddr, err := net.ParseMAC(mac)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed hardware address: %s", mac)
|
||||
}
|
||||
ipaddr := net.ParseIP(ip)
|
||||
if ipaddr.To4() == nil {
|
||||
return nil, fmt.Errorf("expected an IPv4 address, got: %v", ipaddr)
|
||||
}
|
||||
records[hwaddr.String()] = &Record{IP: ipaddr, Static: static, Expires: expiry, Hostname: hostname}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("failed lease database row scanning: %w", err)
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// saveIPAddress writes out a lease to storage
|
||||
func (p *PluginState) saveIPAddress(mac net.HardwareAddr, record *Record) error {
|
||||
stmt, err := p.leasedb.Prepare(`insert or replace into leases4(mac, ip, static, expiry, hostname) values (?, ?, ?, ?, ?)`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("statement preparation failed: %w", err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
if _, err := stmt.Exec(
|
||||
mac.String(),
|
||||
record.IP.String(),
|
||||
record.Static,
|
||||
record.Expires,
|
||||
record.Hostname,
|
||||
); err != nil {
|
||||
return fmt.Errorf("record insert/update failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// registerBackingDB installs a database connection string as the backing store for leases
|
||||
func (p *PluginState) registerBackingDB(filename string) error {
|
||||
if p.leasedb != nil {
|
||||
return errors.New("cannot swap out a lease database while running")
|
||||
}
|
||||
// We never close this, but that's ok because plugins are never stopped/unregistered
|
||||
newLeaseDB, err := loadDB(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open lease database %s: %w", filename, err)
|
||||
}
|
||||
p.leasedb = newLeaseDB
|
||||
return nil
|
||||
}
|
||||
99
plugins/range/storage_test.go
Normal file
99
plugins/range/storage_test.go
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package rangeplugin
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testDBSetup() (*sql.DB, error) {
|
||||
db, err := loadDB(":memory:")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, record := range records {
|
||||
stmt, err := db.Prepare("insert into leases4(mac, ip, expiry, hostname) values (?, ?, ?, ?)")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to prepare insert statement: %w", err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
if _, err := stmt.Exec(record.mac, record.ip.IP.String(), record.ip.expires, record.ip.hostname); err != nil {
|
||||
return nil, fmt.Errorf("failed to insert record into test db: %w", err)
|
||||
}
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
var expire = int(time.Date(2000, 01, 01, 00, 00, 00, 00, time.UTC).Unix())
|
||||
var records = []struct {
|
||||
mac string
|
||||
ip *Record
|
||||
}{
|
||||
{"02:00:00:00:00:00", &Record{IP: net.IPv4(10, 0, 0, 0), expires: expire, hostname: "zero"}},
|
||||
{"02:00:00:00:00:01", &Record{IP: net.IPv4(10, 0, 0, 1), expires: expire, hostname: "one"}},
|
||||
{"02:00:00:00:00:02", &Record{IP: net.IPv4(10, 0, 0, 2), expires: expire, hostname: "two"}},
|
||||
{"02:00:00:00:00:03", &Record{IP: net.IPv4(10, 0, 0, 3), expires: expire, hostname: "three"}},
|
||||
{"02:00:00:00:00:04", &Record{IP: net.IPv4(10, 0, 0, 4), expires: expire, hostname: "four"}},
|
||||
{"02:00:00:00:00:05", &Record{IP: net.IPv4(10, 0, 0, 5), expires: expire, hostname: "five"}},
|
||||
}
|
||||
|
||||
func TestLoadRecords(t *testing.T) {
|
||||
db, err := testDBSetup()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to set up test DB: %v", err)
|
||||
}
|
||||
|
||||
parsedRec, err := loadRecords(db)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load records from file: %v", err)
|
||||
}
|
||||
|
||||
mapRec := make(map[string]*Record)
|
||||
for _, rec := range records {
|
||||
var (
|
||||
ip, mac, hostname string
|
||||
expiry int
|
||||
)
|
||||
if err := db.QueryRow("select mac, ip, expiry, hostname from leases4 where mac = ?", rec.mac).Scan(&mac, &ip, &expiry, &hostname); err != nil {
|
||||
t.Fatalf("record not found for mac=%s: %v", rec.mac, err)
|
||||
}
|
||||
mapRec[mac] = &Record{IP: net.ParseIP(ip), expires: expiry, hostname: hostname}
|
||||
}
|
||||
|
||||
assert.Equal(t, mapRec, parsedRec, "Loaded records differ from what's in the DB")
|
||||
}
|
||||
|
||||
func TestWriteRecords(t *testing.T) {
|
||||
pl := PluginState{}
|
||||
if err := pl.registerBackingDB(":memory:"); err != nil {
|
||||
t.Fatalf("Could not setup file")
|
||||
}
|
||||
|
||||
mapRec := make(map[string]*Record)
|
||||
for _, rec := range records {
|
||||
hwaddr, err := net.ParseMAC(rec.mac)
|
||||
if err != nil {
|
||||
// bug in testdata
|
||||
panic(err)
|
||||
}
|
||||
if err := pl.saveIPAddress(hwaddr, rec.ip); err != nil {
|
||||
t.Errorf("Failed to save ip for %s: %v", hwaddr, err)
|
||||
}
|
||||
mapRec[hwaddr.String()] = &Record{IP: rec.ip.IP, expires: rec.ip.expires, hostname: rec.ip.hostname}
|
||||
}
|
||||
|
||||
parsedRec, err := loadRecords(pl.leasedb)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, mapRec, parsedRec, "Loaded records differ from what's in the DB")
|
||||
}
|
||||
49
plugins/router/plugin.go
Normal file
49
plugins/router/plugin.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package router
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/coredhcp/coredhcp/handler"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("plugins/router")
|
||||
|
||||
// Plugin wraps plugin registration information
|
||||
var Plugin = plugins.Plugin{
|
||||
Name: "router",
|
||||
Setup4: setup4,
|
||||
}
|
||||
|
||||
var (
|
||||
routers []net.IP
|
||||
)
|
||||
|
||||
func setup4(args ...string) (handler.Handler4, error) {
|
||||
log.Printf("Loaded plugin for DHCPv4.")
|
||||
if len(args) < 1 {
|
||||
return nil, errors.New("need at least one router IP address")
|
||||
}
|
||||
for _, arg := range args {
|
||||
router := net.ParseIP(arg)
|
||||
if router.To4() == nil {
|
||||
return Handler4, errors.New("expected an router IP address, got: " + arg)
|
||||
}
|
||||
routers = append(routers, router)
|
||||
}
|
||||
log.Infof("loaded %d router IP addresses.", len(routers))
|
||||
return Handler4, nil
|
||||
}
|
||||
|
||||
//Handler4 handles DHCPv4 packets for the router plugin
|
||||
func Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
|
||||
resp.Options.Update(dhcpv4.OptRouter(routers...))
|
||||
return resp, false
|
||||
}
|
||||
77
plugins/searchdomains/plugin.go
Normal file
77
plugins/searchdomains/plugin.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package searchdomains
|
||||
|
||||
// This is an searchdomains plugin that adds default DNS search domains.
|
||||
|
||||
import (
|
||||
"github.com/coredhcp/coredhcp/handler"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
"github.com/insomniacslk/dhcp/rfc1035label"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("plugins/searchdomains")
|
||||
|
||||
// Plugin wraps the default DNS search domain options.
|
||||
// Note that importing the plugin is not enough to use it: you have to
|
||||
// explicitly specify the intention to use it in the `config.yml` file, in the
|
||||
// plugins section. For searchdomains:
|
||||
//
|
||||
// server6:
|
||||
// listen: '[::]547'
|
||||
// - searchdomains: domain.a domain.b
|
||||
// - server_id: LL aa:bb:cc:dd:ee:ff
|
||||
// - file: "leases.txt"
|
||||
//
|
||||
var Plugin = plugins.Plugin{
|
||||
Name: "searchdomains",
|
||||
Setup6: setup6,
|
||||
Setup4: setup4,
|
||||
}
|
||||
|
||||
// These are the DNS search domains that are set by the plugin.
|
||||
// Note that DHCPv4 and DHCPv6 options are totally independent.
|
||||
// If you need the same settings for both, you'll need to configure
|
||||
// this plugin once for the v4 and once for the v6 server.
|
||||
var v4SearchList []string
|
||||
var v6SearchList []string
|
||||
|
||||
// copySlice creates a new copy of a string slice in memory.
|
||||
// This helps to ensure that downstream plugins can't corrupt
|
||||
// this plugin's configuration
|
||||
func copySlice(original []string) []string {
|
||||
copied := make([]string, len(original))
|
||||
copy(copied, original)
|
||||
return copied
|
||||
}
|
||||
|
||||
func setup6(args ...string) (handler.Handler6, error) {
|
||||
v6SearchList = args
|
||||
log.Printf("Registered domain search list (DHCPv6) %s", v6SearchList)
|
||||
return domainSearchListHandler6, nil
|
||||
}
|
||||
|
||||
func setup4(args ...string) (handler.Handler4, error) {
|
||||
v4SearchList = args
|
||||
log.Printf("Registered domain search list (DHCPv4) %s", v4SearchList)
|
||||
return domainSearchListHandler4, nil
|
||||
}
|
||||
|
||||
func domainSearchListHandler6(req, resp dhcpv6.DHCPv6) (dhcpv6.DHCPv6, bool) {
|
||||
resp.UpdateOption(dhcpv6.OptDomainSearchList(&rfc1035label.Labels{
|
||||
Labels: copySlice(v6SearchList),
|
||||
}))
|
||||
return resp, false
|
||||
}
|
||||
|
||||
func domainSearchListHandler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
|
||||
resp.UpdateOption(dhcpv4.OptDomainSearch(&rfc1035label.Labels{
|
||||
Labels: copySlice(v4SearchList),
|
||||
}))
|
||||
return resp, false
|
||||
}
|
||||
96
plugins/searchdomains/plugin_test.go
Normal file
96
plugins/searchdomains/plugin_test.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package searchdomains
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAddDomains6(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// Search domains we will expect the DHCP server to assign
|
||||
searchDomains := []string{"domain.a", "domain.b"}
|
||||
|
||||
// Init plugin
|
||||
handler6, err := Plugin.Setup6(searchDomains...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Fake request
|
||||
req, err := dhcpv6.NewMessage()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.MessageType = dhcpv6.MessageTypeRequest
|
||||
req.AddOption(dhcpv6.OptRequestedOption(dhcpv6.OptionDNSRecursiveNameServer))
|
||||
|
||||
// Fake response input
|
||||
stub, err := dhcpv6.NewMessage()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stub.MessageType = dhcpv6.MessageTypeReply
|
||||
|
||||
// Call plugin
|
||||
resp, stop := handler6(req, stub)
|
||||
if resp == nil {
|
||||
t.Fatal("plugin did not return a message")
|
||||
}
|
||||
if stop {
|
||||
t.Error("plugin interrupted processing")
|
||||
}
|
||||
|
||||
searchLabels := resp.(*dhcpv6.Message).Options.DomainSearchList().Labels
|
||||
assert.Equal(searchDomains, searchLabels)
|
||||
}
|
||||
|
||||
func TestAddDomains4(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
// Search domains we will expect the DHCP server to assign
|
||||
// NOTE: these domains should be different from the v6 test domains;
|
||||
// this tests that we haven't accidentally set the v6 domains in the
|
||||
// v4 plugin handler or vice versa.
|
||||
searchDomains := []string{"domain.b", "domain.c"}
|
||||
|
||||
// Init plugin
|
||||
handler4, err := Plugin.Setup4(searchDomains...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Fake request
|
||||
req, err := dhcpv4.NewDiscovery(net.HardwareAddr{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Fake response input
|
||||
stub, err := dhcpv4.NewReplyFromRequest(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Call plugin
|
||||
resp, stop := handler4(req, stub)
|
||||
if resp == nil {
|
||||
t.Fatal("plugin did not return a message")
|
||||
}
|
||||
if stop {
|
||||
t.Error("plugin interrupted processing")
|
||||
}
|
||||
|
||||
searchLabels := resp.DomainSearch().Labels
|
||||
assert.Equal(searchDomains, searchLabels)
|
||||
|
||||
}
|
||||
155
plugins/serverid/plugin.go
Normal file
155
plugins/serverid/plugin.go
Normal file
@@ -0,0 +1,155 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package serverid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/coredhcp/coredhcp/handler"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
"github.com/insomniacslk/dhcp/iana"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("plugins/server_id")
|
||||
|
||||
// Plugin wraps plugin registration information
|
||||
var Plugin = plugins.Plugin{
|
||||
Name: "server_id",
|
||||
Setup6: setup6,
|
||||
Setup4: setup4,
|
||||
}
|
||||
|
||||
// v6ServerID is the DUID of the v6 server
|
||||
var (
|
||||
v6ServerID dhcpv6.DUID
|
||||
v4ServerID net.IP
|
||||
)
|
||||
|
||||
// Handler6 handles DHCPv6 packets for the server_id plugin.
|
||||
func Handler6(req, resp dhcpv6.DHCPv6) (dhcpv6.DHCPv6, bool) {
|
||||
if v6ServerID == nil {
|
||||
log.Fatal("BUG: Plugin is running uninitialized!")
|
||||
return nil, true
|
||||
}
|
||||
|
||||
msg, err := req.GetInnerMessage()
|
||||
if err != nil {
|
||||
// BUG: this should already have failed in the main handler. Abort
|
||||
log.Error(err)
|
||||
return nil, true
|
||||
}
|
||||
|
||||
if sid := msg.Options.ServerID(); sid != nil {
|
||||
// RFC8415 §16.{2,5,7}
|
||||
// These message types MUST be discarded if they contain *any* ServerID option
|
||||
if msg.MessageType == dhcpv6.MessageTypeSolicit ||
|
||||
msg.MessageType == dhcpv6.MessageTypeConfirm ||
|
||||
msg.MessageType == dhcpv6.MessageTypeRebind {
|
||||
return nil, true
|
||||
}
|
||||
|
||||
// Approximately all others MUST be discarded if the ServerID doesn't match
|
||||
if !sid.Equal(v6ServerID) {
|
||||
log.Infof("requested server ID does not match this server's ID. Got %v, want %v", sid, v6ServerID)
|
||||
return nil, true
|
||||
}
|
||||
} else if msg.MessageType == dhcpv6.MessageTypeRequest ||
|
||||
msg.MessageType == dhcpv6.MessageTypeRenew ||
|
||||
msg.MessageType == dhcpv6.MessageTypeDecline ||
|
||||
msg.MessageType == dhcpv6.MessageTypeRelease {
|
||||
// RFC8415 §16.{6,8,10,11}
|
||||
// These message types MUST be discarded if they *don't* contain a ServerID option
|
||||
return nil, true
|
||||
}
|
||||
dhcpv6.WithServerID(v6ServerID)(resp)
|
||||
return resp, false
|
||||
}
|
||||
|
||||
// Handler4 handles DHCPv4 packets for the server_id plugin.
|
||||
func Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
|
||||
if v4ServerID == nil {
|
||||
log.Fatal("BUG: Plugin is running uninitialized!")
|
||||
return nil, true
|
||||
}
|
||||
if req.OpCode != dhcpv4.OpcodeBootRequest {
|
||||
log.Warningf("not a BootRequest, ignoring")
|
||||
return resp, false
|
||||
}
|
||||
if req.ServerIPAddr != nil &&
|
||||
!req.ServerIPAddr.Equal(net.IPv4zero) &&
|
||||
!req.ServerIPAddr.Equal(v4ServerID) {
|
||||
// This request is not for us, drop it.
|
||||
log.Infof("requested server ID does not match this server's ID. Got %v, want %v", req.ServerIPAddr, v4ServerID)
|
||||
return nil, true
|
||||
}
|
||||
resp.ServerIPAddr = make(net.IP, net.IPv4len)
|
||||
copy(resp.ServerIPAddr[:], v4ServerID)
|
||||
resp.UpdateOption(dhcpv4.OptServerIdentifier(v4ServerID))
|
||||
return resp, false
|
||||
}
|
||||
|
||||
func setup4(args ...string) (handler.Handler4, error) {
|
||||
log.Printf("loading `server_id` plugin for DHCPv4 with args: %v", args)
|
||||
if len(args) < 1 {
|
||||
return nil, errors.New("need an argument")
|
||||
}
|
||||
serverID := net.ParseIP(args[0])
|
||||
if serverID == nil {
|
||||
return nil, errors.New("invalid or empty IP address")
|
||||
}
|
||||
if serverID.To4() == nil {
|
||||
return nil, errors.New("not a valid IPv4 address")
|
||||
}
|
||||
v4ServerID = serverID.To4()
|
||||
return Handler4, nil
|
||||
}
|
||||
|
||||
func setup6(args ...string) (handler.Handler6, error) {
|
||||
log.Printf("loading `server_id` plugin for DHCPv6 with args: %v", args)
|
||||
if len(args) < 2 {
|
||||
return nil, errors.New("need a DUID type and value")
|
||||
}
|
||||
duidType := args[0]
|
||||
if duidType == "" {
|
||||
return nil, errors.New("got empty DUID type")
|
||||
}
|
||||
duidValue := args[1]
|
||||
if duidValue == "" {
|
||||
return nil, errors.New("got empty DUID value")
|
||||
}
|
||||
duidType = strings.ToLower(duidType)
|
||||
hwaddr, err := net.ParseMAC(duidValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch duidType {
|
||||
case "ll", "duid-ll", "duid_ll":
|
||||
v6ServerID = &dhcpv6.DUIDLL{
|
||||
// sorry, only ethernet for now
|
||||
HWType: iana.HWTypeEthernet,
|
||||
LinkLayerAddr: hwaddr,
|
||||
}
|
||||
case "llt", "duid-llt", "duid_llt":
|
||||
v6ServerID = &dhcpv6.DUIDLLT{
|
||||
// sorry, zero-time for now
|
||||
Time: 0,
|
||||
// sorry, only ethernet for now
|
||||
HWType: iana.HWTypeEthernet,
|
||||
LinkLayerAddr: hwaddr,
|
||||
}
|
||||
case "en", "uuid":
|
||||
return nil, errors.New("EN/UUID DUID type not supported yet")
|
||||
default:
|
||||
return nil, errors.New("Opaque DUID type not supported yet")
|
||||
}
|
||||
log.Printf("using %s %s", duidType, duidValue)
|
||||
|
||||
return Handler6, nil
|
||||
}
|
||||
127
plugins/serverid/plugin_test.go
Normal file
127
plugins/serverid/plugin_test.go
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package serverid
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
)
|
||||
|
||||
func makeTestDUID(uuid string) dhcpv6.DUID {
|
||||
var uuidb [16]byte
|
||||
copy(uuidb[:], uuid)
|
||||
return &dhcpv6.DUIDUUID{
|
||||
UUID: uuidb,
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectBadServerIDV6(t *testing.T) {
|
||||
req, err := dhcpv6.NewMessage()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v6ServerID = makeTestDUID("0000000000000000")
|
||||
|
||||
req.MessageType = dhcpv6.MessageTypeRenew
|
||||
dhcpv6.WithClientID(makeTestDUID("1000000000000000"))(req)
|
||||
dhcpv6.WithServerID(makeTestDUID("0000000000000001"))(req)
|
||||
|
||||
stub, err := dhcpv6.NewReplyFromMessage(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, stop := Handler6(req, stub)
|
||||
if resp != nil {
|
||||
t.Error("server_id is sending a response message to a request with mismatched ServerID")
|
||||
}
|
||||
if !stop {
|
||||
t.Error("server_id did not interrupt processing on a request with mismatched ServerID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectUnexpectedServerIDV6(t *testing.T) {
|
||||
req, err := dhcpv6.NewMessage()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v6ServerID = makeTestDUID("0000000000000000")
|
||||
|
||||
req.MessageType = dhcpv6.MessageTypeSolicit
|
||||
dhcpv6.WithClientID(makeTestDUID("1000000000000000"))(req)
|
||||
dhcpv6.WithServerID(makeTestDUID("0000000000000000"))(req)
|
||||
|
||||
stub, err := dhcpv6.NewAdvertiseFromSolicit(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, stop := Handler6(req, stub)
|
||||
if resp != nil {
|
||||
t.Error("server_id is sending a response message to a solicit with a ServerID")
|
||||
}
|
||||
if !stop {
|
||||
t.Error("server_id did not interrupt processing on a solicit with a ServerID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddServerIDV6(t *testing.T) {
|
||||
req, err := dhcpv6.NewMessage()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v6ServerID = makeTestDUID("0000000000000000")
|
||||
|
||||
req.MessageType = dhcpv6.MessageTypeRebind
|
||||
dhcpv6.WithClientID(makeTestDUID("1000000000000000"))(req)
|
||||
|
||||
stub, err := dhcpv6.NewReplyFromMessage(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, _ := Handler6(req, stub)
|
||||
if resp == nil {
|
||||
t.Fatal("plugin did not return an answer")
|
||||
}
|
||||
|
||||
if opt := resp.(*dhcpv6.Message).Options.ServerID(); opt == nil {
|
||||
t.Fatal("plugin did not add a ServerID option")
|
||||
} else if !opt.Equal(v6ServerID) {
|
||||
t.Fatalf("Got unexpected DUID: expected %v, got %v", v6ServerID, opt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectInnerMessageServerID(t *testing.T) {
|
||||
req, err := dhcpv6.NewMessage()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v6ServerID = makeTestDUID("0000000000000000")
|
||||
|
||||
req.MessageType = dhcpv6.MessageTypeSolicit
|
||||
dhcpv6.WithClientID(makeTestDUID("1000000000000000"))(req)
|
||||
dhcpv6.WithServerID(makeTestDUID("0000000000000000"))(req)
|
||||
|
||||
stub, err := dhcpv6.NewAdvertiseFromSolicit(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
relayedRequest, err := dhcpv6.EncapsulateRelay(req, dhcpv6.MessageTypeRelayForward, net.IPv6loopback, net.IPv6loopback)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, stop := Handler6(relayedRequest, stub)
|
||||
if resp != nil {
|
||||
t.Error("server_id is sending a response message to a relayed solicit with a ServerID")
|
||||
}
|
||||
if !stop {
|
||||
t.Error("server_id did not interrupt processing on a relayed solicit with a ServerID")
|
||||
}
|
||||
}
|
||||
89
plugins/sleep/plugin.go
Normal file
89
plugins/sleep/plugin.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package sleep
|
||||
|
||||
// This plugin introduces a delay in the DHCP response.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/coredhcp/coredhcp/handler"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
)
|
||||
|
||||
var (
|
||||
pluginName = "sleep"
|
||||
log = logger.GetLogger("plugins/" + pluginName)
|
||||
)
|
||||
|
||||
// Example configuration of the `sleep` plugin:
|
||||
//
|
||||
// server4:
|
||||
// plugins:
|
||||
// - sleep 300ms
|
||||
// - file: "leases4.txt"
|
||||
//
|
||||
// server6:
|
||||
// plugins:
|
||||
// - sleep 1s
|
||||
// - file: "leases6.txt"
|
||||
//
|
||||
// For the duration format, see the documentation of `time.ParseDuration`,
|
||||
// https://golang.org/pkg/time/#ParseDuration .
|
||||
|
||||
// Plugin contains the `sleep` plugin data.
|
||||
var Plugin = plugins.Plugin{
|
||||
Name: pluginName,
|
||||
Setup6: setup6,
|
||||
Setup4: setup4,
|
||||
}
|
||||
|
||||
func setup6(args ...string) (handler.Handler6, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, fmt.Errorf("want exactly one argument, got %d", len(args))
|
||||
}
|
||||
delay, err := time.ParseDuration(args[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse duration: %w", err)
|
||||
}
|
||||
log.Printf("loaded plugin for DHCPv6.")
|
||||
return makeSleepHandler6(delay), nil
|
||||
}
|
||||
|
||||
func setup4(args ...string) (handler.Handler4, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, fmt.Errorf("want exactly one argument, got %d", len(args))
|
||||
}
|
||||
delay, err := time.ParseDuration(args[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse duration: %w", err)
|
||||
}
|
||||
log.Printf("loaded plugin for DHCPv4.")
|
||||
return makeSleepHandler4(delay), nil
|
||||
}
|
||||
|
||||
func makeSleepHandler6(delay time.Duration) handler.Handler6 {
|
||||
return func(req, resp dhcpv6.DHCPv6) (dhcpv6.DHCPv6, bool) {
|
||||
log.Printf("introducing delay of %s in response", delay)
|
||||
// return the unmodified response, and instruct coredhcp to continue to
|
||||
// the next plugin.
|
||||
time.Sleep(delay)
|
||||
return resp, false
|
||||
}
|
||||
}
|
||||
|
||||
func makeSleepHandler4(delay time.Duration) handler.Handler4 {
|
||||
return func(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
|
||||
log.Printf("introducing delay of %s in response", delay)
|
||||
// return the unmodified response, and instruct coredhcp to continue to
|
||||
// the next plugin.
|
||||
time.Sleep(delay)
|
||||
return resp, false
|
||||
}
|
||||
}
|
||||
73
plugins/staticroute/plugin.go
Normal file
73
plugins/staticroute/plugin.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package staticroute
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/coredhcp/coredhcp/handler"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("plugins/staticroute")
|
||||
|
||||
// Plugin wraps the information necessary to register a plugin.
|
||||
var Plugin = plugins.Plugin{
|
||||
Name: "staticroute",
|
||||
Setup4: setup4,
|
||||
}
|
||||
|
||||
var routes dhcpv4.Routes
|
||||
|
||||
func setup4(args ...string) (handler.Handler4, error) {
|
||||
log.Printf("loaded plugin for DHCPv4.")
|
||||
routes = make(dhcpv4.Routes, 0)
|
||||
|
||||
if len(args) < 1 {
|
||||
return nil, errors.New("need at least one static route")
|
||||
}
|
||||
|
||||
var err error
|
||||
for _, arg := range args {
|
||||
fields := strings.Split(arg, ",")
|
||||
if len(fields) != 2 {
|
||||
return Handler4, errors.New("expected a destination/gateway pair, got: " + arg)
|
||||
}
|
||||
|
||||
route := &dhcpv4.Route{}
|
||||
_, route.Dest, err = net.ParseCIDR(fields[0])
|
||||
if err != nil {
|
||||
return Handler4, errors.New("expected a destination subnet, got: " + fields[0])
|
||||
}
|
||||
|
||||
route.Router = net.ParseIP(fields[1])
|
||||
if route.Router == nil {
|
||||
return Handler4, errors.New("expected a gateway address, got: " + fields[1])
|
||||
}
|
||||
|
||||
routes = append(routes, route)
|
||||
log.Debugf("adding static route %s", route)
|
||||
}
|
||||
|
||||
log.Printf("loaded %d static routes.", len(routes))
|
||||
|
||||
return Handler4, nil
|
||||
}
|
||||
|
||||
// Handler4 handles DHCPv4 packets for the static routes plugin
|
||||
func Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
|
||||
if len(routes) > 0 {
|
||||
resp.Options.Update(dhcpv4.Option{
|
||||
Code: dhcpv4.OptionCode(dhcpv4.OptionClasslessStaticRoute),
|
||||
Value: routes,
|
||||
})
|
||||
}
|
||||
|
||||
return resp, false
|
||||
}
|
||||
60
plugins/staticroute/plugin_test.go
Normal file
60
plugins/staticroute/plugin_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package staticroute
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSetup4(t *testing.T) {
|
||||
assert.Empty(t, routes)
|
||||
|
||||
var err error
|
||||
// no args
|
||||
_, err = setup4()
|
||||
if assert.Error(t, err) {
|
||||
assert.Equal(t, "need at least one static route", err.Error())
|
||||
}
|
||||
|
||||
// invalid arg
|
||||
_, err = setup4("foo")
|
||||
if assert.Error(t, err) {
|
||||
assert.Equal(t, "expected a destination/gateway pair, got: foo", err.Error())
|
||||
}
|
||||
|
||||
// invalid destination
|
||||
_, err = setup4("foo,")
|
||||
if assert.Error(t, err) {
|
||||
assert.Equal(t, "expected a destination subnet, got: foo", err.Error())
|
||||
}
|
||||
|
||||
// invalid gateway
|
||||
_, err = setup4("10.0.0.0/8,foo")
|
||||
if assert.Error(t, err) {
|
||||
assert.Equal(t, "expected a gateway address, got: foo", err.Error())
|
||||
}
|
||||
|
||||
// valid route
|
||||
_, err = setup4("10.0.0.0/8,192.168.1.1")
|
||||
if assert.NoError(t, err) {
|
||||
if assert.Equal(t, 1, len(routes)) {
|
||||
assert.Equal(t, "10.0.0.0/8", routes[0].Dest.String())
|
||||
assert.Equal(t, "192.168.1.1", routes[0].Router.String())
|
||||
}
|
||||
}
|
||||
|
||||
// multiple valid routes
|
||||
_, err = setup4("10.0.0.0/8,192.168.1.1", "192.168.2.0/24,192.168.1.100")
|
||||
if assert.NoError(t, err) {
|
||||
if assert.Equal(t, 2, len(routes)) {
|
||||
assert.Equal(t, "10.0.0.0/8", routes[0].Dest.String())
|
||||
assert.Equal(t, "192.168.1.1", routes[0].Router.String())
|
||||
assert.Equal(t, "192.168.2.0/24", routes[1].Dest.String())
|
||||
assert.Equal(t, "192.168.1.100", routes[1].Router.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
241
server/handle.go
Normal file
241
server/handle.go
Normal file
@@ -0,0 +1,241 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||
)
|
||||
|
||||
// HandleMsg6 runs for every received DHCPv6 packet. It will run every
|
||||
// registered handler in sequence, and reply with the resulting response.
|
||||
// It will not reply if the resulting response is `nil`.
|
||||
func (l *listener6) HandleMsg6(buf []byte, oob *ipv6.ControlMessage, peer *net.UDPAddr) {
|
||||
d, err := dhcpv6.FromBytes(buf)
|
||||
bufpool.Put(&buf)
|
||||
if err != nil {
|
||||
log.Printf("Error parsing DHCPv6 request: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// decapsulate the relay message
|
||||
msg, err := d.GetInnerMessage()
|
||||
if err != nil {
|
||||
log.Warningf("DHCPv6: cannot get inner message: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create a suitable basic response packet
|
||||
var resp dhcpv6.DHCPv6
|
||||
switch msg.Type() {
|
||||
case dhcpv6.MessageTypeSolicit:
|
||||
if msg.GetOneOption(dhcpv6.OptionRapidCommit) != nil {
|
||||
resp, err = dhcpv6.NewReplyFromMessage(msg)
|
||||
} else {
|
||||
resp, err = dhcpv6.NewAdvertiseFromSolicit(msg)
|
||||
}
|
||||
case dhcpv6.MessageTypeRequest, dhcpv6.MessageTypeConfirm, dhcpv6.MessageTypeRenew,
|
||||
dhcpv6.MessageTypeRebind, dhcpv6.MessageTypeRelease, dhcpv6.MessageTypeInformationRequest:
|
||||
resp, err = dhcpv6.NewReplyFromMessage(msg)
|
||||
default:
|
||||
err = fmt.Errorf("MainHandler6: message type %d not supported", msg.Type())
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("MainHandler6: NewReplyFromDHCPv6Message failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var stop bool
|
||||
for _, handler := range l.handlers {
|
||||
resp, stop = handler(d, resp)
|
||||
if stop {
|
||||
break
|
||||
}
|
||||
}
|
||||
if resp == nil {
|
||||
log.Print("MainHandler6: dropping request because response is nil")
|
||||
return
|
||||
}
|
||||
|
||||
// if the request was relayed, re-encapsulate the response
|
||||
if d.IsRelay() {
|
||||
if rmsg, ok := resp.(*dhcpv6.Message); !ok {
|
||||
log.Warningf("DHCPv6: response is a relayed message, not reencapsulating")
|
||||
} else {
|
||||
tmp, err := dhcpv6.NewRelayReplFromRelayForw(d.(*dhcpv6.RelayMessage), rmsg)
|
||||
if err != nil {
|
||||
log.Warningf("DHCPv6: cannot create relay-repl from relay-forw: %v", err)
|
||||
return
|
||||
}
|
||||
resp = tmp
|
||||
}
|
||||
}
|
||||
|
||||
var woob *ipv6.ControlMessage
|
||||
if peer.IP.IsLinkLocalUnicast() {
|
||||
// LL need to be directed to the correct interface. Globally reachable
|
||||
// addresses should use the default route, in case of asymetric routing.
|
||||
switch {
|
||||
case l.Interface.Index != 0:
|
||||
woob = &ipv6.ControlMessage{IfIndex: l.Interface.Index}
|
||||
case oob != nil && oob.IfIndex != 0:
|
||||
woob = &ipv6.ControlMessage{IfIndex: oob.IfIndex}
|
||||
default:
|
||||
log.Errorf("HandleMsg6: Did not receive interface information")
|
||||
}
|
||||
}
|
||||
if _, err := l.WriteTo(resp.ToBytes(), woob, peer); err != nil {
|
||||
log.Printf("MainHandler6: conn.Write to %v failed: %v", peer, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *listener4) HandleMsg4(buf []byte, oob *ipv4.ControlMessage, _peer net.Addr) {
|
||||
var (
|
||||
resp, tmp *dhcpv4.DHCPv4
|
||||
err error
|
||||
stop bool
|
||||
)
|
||||
|
||||
req, err := dhcpv4.FromBytes(buf)
|
||||
bufpool.Put(&buf)
|
||||
if err != nil {
|
||||
log.Printf("Error parsing DHCPv4 request: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if req.OpCode != dhcpv4.OpcodeBootRequest {
|
||||
log.Printf("MainHandler4: unsupported opcode %d. Only BootRequest (%d) is supported", req.OpCode, dhcpv4.OpcodeBootRequest)
|
||||
return
|
||||
}
|
||||
tmp, err = dhcpv4.NewReplyFromRequest(req)
|
||||
if err != nil {
|
||||
log.Printf("MainHandler4: failed to build reply: %v", err)
|
||||
return
|
||||
}
|
||||
switch mt := req.MessageType(); mt {
|
||||
case dhcpv4.MessageTypeDiscover:
|
||||
tmp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer))
|
||||
case dhcpv4.MessageTypeRequest:
|
||||
tmp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck))
|
||||
default:
|
||||
log.Printf("plugins/server: Unhandled message type: %v", mt)
|
||||
return
|
||||
}
|
||||
|
||||
resp = tmp
|
||||
for _, handler := range l.handlers {
|
||||
resp, stop = handler(req, resp)
|
||||
if stop {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
useEthernet := false
|
||||
var peer *net.UDPAddr
|
||||
if !req.GatewayIPAddr.IsUnspecified() {
|
||||
// TODO: make RFC8357 compliant
|
||||
peer = &net.UDPAddr{IP: req.GatewayIPAddr, Port: dhcpv4.ServerPort}
|
||||
} else if resp.MessageType() == dhcpv4.MessageTypeNak {
|
||||
peer = &net.UDPAddr{IP: net.IPv4bcast, Port: dhcpv4.ClientPort}
|
||||
} else if !req.ClientIPAddr.IsUnspecified() {
|
||||
peer = &net.UDPAddr{IP: req.ClientIPAddr, Port: dhcpv4.ClientPort}
|
||||
} else if req.IsBroadcast() {
|
||||
peer = &net.UDPAddr{IP: net.IPv4bcast, Port: dhcpv4.ClientPort}
|
||||
} else {
|
||||
//sends a layer2 frame so that we can define the destination MAC address
|
||||
peer = &net.UDPAddr{IP: resp.YourIPAddr, Port: dhcpv4.ClientPort}
|
||||
useEthernet = true
|
||||
}
|
||||
|
||||
var woob *ipv4.ControlMessage
|
||||
if peer.IP.Equal(net.IPv4bcast) || peer.IP.IsLinkLocalUnicast() || useEthernet {
|
||||
// Direct broadcasts, link-local and layer2 unicasts to the interface the request was
|
||||
// received on. Other packets should use the normal routing table in
|
||||
// case of asymetric routing
|
||||
switch {
|
||||
case l.Interface.Index != 0:
|
||||
woob = &ipv4.ControlMessage{IfIndex: l.Interface.Index}
|
||||
case oob != nil && oob.IfIndex != 0:
|
||||
woob = &ipv4.ControlMessage{IfIndex: oob.IfIndex}
|
||||
default:
|
||||
log.Errorf("HandleMsg4: Did not receive interface information")
|
||||
}
|
||||
}
|
||||
|
||||
if useEthernet {
|
||||
intf, err := net.InterfaceByIndex(woob.IfIndex)
|
||||
if err != nil {
|
||||
log.Errorf("MainHandler4: Can not get Interface for index %d %v", woob.IfIndex, err)
|
||||
return
|
||||
}
|
||||
err = sendEthernet(*intf, resp)
|
||||
if err != nil {
|
||||
log.Errorf("MainHandler4: Cannot send Ethernet packet: %v", err)
|
||||
}
|
||||
} else {
|
||||
if _, err := l.WriteTo(resp.ToBytes(), woob, peer); err != nil {
|
||||
log.Errorf("MainHandler4: conn.Write to %v failed: %v", peer, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Print("MainHandler4: dropping request because response is nil")
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: performance-wise, Pool may or may not be good (see https://github.com/golang/go/issues/23199)
|
||||
// Interface is good for what we want. Maybe "just" trust the GC and we'll be fine ?
|
||||
var bufpool = sync.Pool{New: func() interface{} { r := make([]byte, MaxDatagram); return &r }}
|
||||
|
||||
// MaxDatagram is the maximum length of message that can be received.
|
||||
const MaxDatagram = 1 << 16
|
||||
|
||||
// XXX: investigate using RecvMsgs to batch messages and reduce syscalls
|
||||
|
||||
// Serve6 handles datagrams received on conn and passes them to the pluginchain
|
||||
func (l *listener6) Serve() error {
|
||||
log.Printf("Listen %s", l.LocalAddr())
|
||||
for {
|
||||
b := *bufpool.Get().(*[]byte)
|
||||
b = b[:MaxDatagram] //Reslice to max capacity in case the buffer in pool was resliced smaller
|
||||
|
||||
n, oob, peer, err := l.ReadFrom(b)
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
// Server is quitting
|
||||
return nil
|
||||
} else if err != nil {
|
||||
log.Printf("Error reading from connection: %v", err)
|
||||
return err
|
||||
}
|
||||
go l.HandleMsg6(b[:n], oob, peer.(*net.UDPAddr))
|
||||
}
|
||||
}
|
||||
|
||||
// Serve6 handles datagrams received on conn and passes them to the pluginchain
|
||||
func (l *listener4) Serve() error {
|
||||
log.Printf("Listen %s", l.LocalAddr())
|
||||
for {
|
||||
b := *bufpool.Get().(*[]byte)
|
||||
b = b[:MaxDatagram] //Reslice to max capacity in case the buffer in pool was resliced smaller
|
||||
|
||||
n, oob, peer, err := l.ReadFrom(b)
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
// Server is quitting
|
||||
return nil
|
||||
} else if err != nil {
|
||||
log.Printf("Error reading from connection: %v", err)
|
||||
return err
|
||||
}
|
||||
go l.HandleMsg4(b[:n], oob, peer.(*net.UDPAddr))
|
||||
}
|
||||
}
|
||||
96
server/sendEthernet.go
Normal file
96
server/sendEthernet.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
// +build linux
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
)
|
||||
|
||||
//this function sends an unicast to the hardware address defined in resp.ClientHWAddr,
|
||||
//the layer3 destination address is still the broadcast address;
|
||||
//iface: the interface where the DHCP message should be sent;
|
||||
//resp: DHCPv4 struct, which should be sent;
|
||||
func sendEthernet(iface net.Interface, resp *dhcpv4.DHCPv4) error {
|
||||
|
||||
eth := layers.Ethernet{
|
||||
EthernetType: layers.EthernetTypeIPv4,
|
||||
SrcMAC: iface.HardwareAddr,
|
||||
DstMAC: resp.ClientHWAddr,
|
||||
}
|
||||
ip := layers.IPv4{
|
||||
Version: 4,
|
||||
TTL: 64,
|
||||
SrcIP: resp.ServerIPAddr,
|
||||
DstIP: resp.YourIPAddr,
|
||||
Protocol: layers.IPProtocolUDP,
|
||||
Flags: layers.IPv4DontFragment,
|
||||
}
|
||||
udp := layers.UDP{
|
||||
SrcPort: dhcpv4.ServerPort,
|
||||
DstPort: dhcpv4.ClientPort,
|
||||
}
|
||||
|
||||
err := udp.SetNetworkLayerForChecksum(&ip)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Send Ethernet: Couldn't set network layer: %v", err)
|
||||
}
|
||||
|
||||
buf := gopacket.NewSerializeBuffer()
|
||||
opts := gopacket.SerializeOptions{
|
||||
ComputeChecksums: true,
|
||||
FixLengths: true,
|
||||
}
|
||||
|
||||
// Decode a packet
|
||||
packet := gopacket.NewPacket(resp.ToBytes(), layers.LayerTypeDHCPv4, gopacket.NoCopy)
|
||||
dhcpLayer := packet.Layer(layers.LayerTypeDHCPv4)
|
||||
dhcp, ok := dhcpLayer.(gopacket.SerializableLayer)
|
||||
if !ok {
|
||||
return fmt.Errorf("Layer %s is not serializable", dhcpLayer.LayerType().String())
|
||||
}
|
||||
err = gopacket.SerializeLayers(buf, opts, ð, &ip, &udp, dhcp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot serialize layer: %v", err)
|
||||
}
|
||||
data := buf.Bytes()
|
||||
|
||||
fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Send Ethernet: Cannot open socket: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
err = syscall.Close(fd)
|
||||
if err != nil {
|
||||
log.Errorf("Send Ethernet: Cannot close socket: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||
if err != nil {
|
||||
log.Errorf("Send Ethernet: Cannot set option for socket: %v", err)
|
||||
}
|
||||
|
||||
var hwAddr [8]byte
|
||||
copy(hwAddr[0:6], resp.ClientHWAddr[0:6])
|
||||
ethAddr := syscall.SockaddrLinklayer{
|
||||
Protocol: 0,
|
||||
Ifindex: iface.Index,
|
||||
Halen: 6,
|
||||
Addr: hwAddr, //not used
|
||||
}
|
||||
err = syscall.Sendto(fd, data, 0, ðAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot send frame via socket: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
185
server/serve.go
Normal file
185
server/serve.go
Normal file
@@ -0,0 +1,185 @@
|
||||
// Copyright 2018-present the CoreDHCP Authors. All rights reserved
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
|
||||
"github.com/coredhcp/coredhcp/config"
|
||||
"github.com/coredhcp/coredhcp/handler"
|
||||
"github.com/coredhcp/coredhcp/logger"
|
||||
"github.com/coredhcp/coredhcp/plugins"
|
||||
"github.com/insomniacslk/dhcp/dhcpv4/server4"
|
||||
"github.com/insomniacslk/dhcp/dhcpv6/server6"
|
||||
)
|
||||
|
||||
var log = logger.GetLogger("server")
|
||||
|
||||
type listener6 struct {
|
||||
*ipv6.PacketConn
|
||||
net.Interface
|
||||
handlers []handler.Handler6
|
||||
}
|
||||
|
||||
type listener4 struct {
|
||||
*ipv4.PacketConn
|
||||
net.Interface
|
||||
handlers []handler.Handler4
|
||||
}
|
||||
|
||||
type listener interface {
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// Servers contains state for a running server (with possibly multiple interfaces/listeners)
|
||||
type Servers struct {
|
||||
listeners []listener
|
||||
errors chan error
|
||||
}
|
||||
|
||||
func listen4(a *net.UDPAddr) (*listener4, error) {
|
||||
var err error
|
||||
l4 := listener4{}
|
||||
udpConn, err := server4.NewIPv4UDPConn(a.Zone, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l4.PacketConn = ipv4.NewPacketConn(udpConn)
|
||||
var ifi *net.Interface
|
||||
if a.Zone != "" {
|
||||
ifi, err = net.InterfaceByName(a.Zone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DHCPv4: Listen could not find interface %s: %v", a.Zone, err)
|
||||
}
|
||||
l4.Interface = *ifi
|
||||
} else {
|
||||
|
||||
// When not bound to an interface, we need the information in each
|
||||
// packet to know which interface it came on
|
||||
err = l4.SetControlMessage(ipv4.FlagInterface, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if a.IP.IsMulticast() {
|
||||
err = l4.JoinGroup(ifi, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &l4, nil
|
||||
}
|
||||
|
||||
func listen6(a *net.UDPAddr) (*listener6, error) {
|
||||
l6 := listener6{}
|
||||
udpconn, err := server6.NewIPv6UDPConn(a.Zone, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l6.PacketConn = ipv6.NewPacketConn(udpconn)
|
||||
var ifi *net.Interface
|
||||
if a.Zone != "" {
|
||||
ifi, err = net.InterfaceByName(a.Zone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("DHCPv4: Listen could not find interface %s: %v", a.Zone, err)
|
||||
}
|
||||
l6.Interface = *ifi
|
||||
} else {
|
||||
// When not bound to an interface, we need the information in each
|
||||
// packet to know which interface it came on
|
||||
err = l6.SetControlMessage(ipv6.FlagInterface, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if a.IP.IsMulticast() {
|
||||
err = l6.JoinGroup(ifi, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &l6, nil
|
||||
}
|
||||
|
||||
// Start will start the server asynchronously. See `Wait` to wait until
|
||||
// the execution ends.
|
||||
func Start(config *config.Config) (*Servers, error) {
|
||||
handlers4, handlers6, err := plugins.LoadPlugins(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srv := Servers{
|
||||
errors: make(chan error),
|
||||
}
|
||||
|
||||
// listen
|
||||
if config.Server6 != nil {
|
||||
log.Println("Starting DHCPv6 server")
|
||||
for _, addr := range config.Server6.Addresses {
|
||||
var l6 *listener6
|
||||
l6, err = listen6(&addr)
|
||||
if err != nil {
|
||||
goto cleanup
|
||||
}
|
||||
l6.handlers = handlers6
|
||||
srv.listeners = append(srv.listeners, l6)
|
||||
go func() {
|
||||
srv.errors <- l6.Serve()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
if config.Server4 != nil {
|
||||
log.Println("Starting DHCPv4 server")
|
||||
for _, addr := range config.Server4.Addresses {
|
||||
var l4 *listener4
|
||||
l4, err = listen4(&addr)
|
||||
if err != nil {
|
||||
goto cleanup
|
||||
}
|
||||
l4.handlers = handlers4
|
||||
srv.listeners = append(srv.listeners, l4)
|
||||
go func() {
|
||||
srv.errors <- l4.Serve()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
return &srv, nil
|
||||
|
||||
cleanup:
|
||||
srv.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wait waits until the end of the execution of the server.
|
||||
func (s *Servers) Wait() error {
|
||||
log.Debug("Waiting")
|
||||
errs := make([]error, 1, len(s.listeners))
|
||||
errs[0] = <-s.errors
|
||||
s.Close()
|
||||
// Wait for the other listeners to close
|
||||
for i := 1; i < len(s.listeners); i++ {
|
||||
errs = append(errs, <-s.errors)
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// Close closes all listening connections
|
||||
func (s *Servers) Close() {
|
||||
for _, srv := range s.listeners {
|
||||
if srv != nil {
|
||||
srv.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user