소스 검색

Initial import

Andrea Barberio 7 년 전
부모
커밋
8092679242
11개의 변경된 파일683개의 추가작업 그리고 0개의 파일을 삭제
  1. 4 0
      .gitignore
  2. 55 0
      client/main.go
  3. 9 0
      config.yml.example
  4. 135 0
      config/config.go
  5. 28 0
      config/errors.go
  6. 19 0
      handler/handler.go
  7. 1 0
      leases.txt.example
  8. 160 0
      main.go
  9. 122 0
      plugins/file/plugin.go
  10. 41 0
      plugins/plugin.go
  11. 109 0
      plugins/server_id/plugin.go

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+leases.txt
+coredhcp
+client/client
+config.yml

+ 55 - 0
client/main.go

@@ -0,0 +1,55 @@
+package main
+
+/*
+ * Sample DHCPv6 client to test on the local interface
+ */
+
+import (
+	"flag"
+	"log"
+	"net"
+
+	"github.com/insomniacslk/dhcp/dhcpv6"
+	"github.com/insomniacslk/dhcp/iana"
+)
+
+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 := dhcpv6.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.Duid{
+		Type:          dhcpv6.DUID_LLT,
+		HwType:        iana.HwTypeEthernet,
+		Time:          dhcpv6.GetTime(),
+		LinkLayerAddr: mac,
+	}
+
+	conv, err := c.Exchange("lo", nil, dhcpv6.WithClientID(duid))
+	for _, p := range conv {
+		log.Print(p.Summary())
+	}
+	if err != nil {
+		log.Fatal(err)
+	}
+}

+ 9 - 0
config.yml.example

@@ -0,0 +1,9 @@
+server6:
+    listen: '[::]:547'
+    plugins:
+        - server_id: LL 00:de:ad:be:ef:00
+        - file: "leases.txt"
+        # - dns: 8.8.8.8 8.8.4.4 2001:4860:4860::8888 2001:4860:4860::8844
+
+#server4:
+#    listen: '127.0.0.1:67'

+ 135 - 0
config/config.go

@@ -0,0 +1,135 @@
+package config
+
+import (
+	"errors"
+	"log"
+	"net"
+	"strconv"
+	"strings"
+
+	"github.com/spf13/cast"
+	"github.com/spf13/viper"
+)
+
+// Config holds the DHCPv6/v4 server configuration
+type Config struct {
+	v       *viper.Viper
+	Server6 *ServerConfig
+	Server4 *ServerConfig
+}
+
+// ServerConfig holds a server configuration that is specific to either the
+// DHCPv6 server or the DHCPv4 server.
+type ServerConfig struct {
+	Listener *net.UDPAddr
+	Plugins  []*PluginConfig
+}
+
+// PluginConfig holds the configuration of a single plugin
+type PluginConfig struct {
+	Name string
+	Args []string
+}
+
+// Parse returns a Config object after reading a configuration file.
+// It returns an error if the file is invalid or not found.
+func Parse() (*Config, error) {
+	log.Print("Loading configuration")
+	v := viper.New()
+	v.SetConfigType("yml")
+	v.SetConfigName("config")
+	v.AddConfigPath(".")
+	v.AddConfigPath("$HOME/.coredhcp/")
+	v.AddConfigPath("/etc/coredhcp/")
+	if err := v.ReadInConfig(); err != nil {
+		return nil, err
+	}
+	v6, err := parseV6Config(v)
+	if err != nil {
+		return nil, err
+	}
+	v4, err := parseV4Config(v)
+	if err != nil {
+		return nil, err
+	}
+	if v6 == nil && v4 == nil {
+		return nil, ConfigErrorFromString("need at least one valid config for DHCPv6 or DHCPv4")
+	}
+	return &Config{
+		v:       v,
+		Server6: v6,
+		Server4: v4,
+	}, nil
+}
+
+func parseV6Config(v *viper.Viper) (*ServerConfig, error) {
+	if exists := v.Get("server6"); exists == nil {
+		// it is valid to have no DHCPv6 configuration defined, so no
+		// server and no error are returned
+		return nil, nil
+	}
+	addr := v.GetString("server6.listen")
+	if addr == "" {
+		return nil, ConfigErrorFromString("dhcpv6: missing `server6.listen` directive")
+	}
+	ipStr, portStr, err := net.SplitHostPort(addr)
+	if err != nil {
+		return nil, ConfigErrorFromString("dhcpv6: %v", err)
+	}
+	ip := net.ParseIP(ipStr)
+	if ip.To4() != nil {
+		return nil, ConfigErrorFromString("dhcpv6: missing or invalid `listen` address")
+	}
+	port, err := strconv.Atoi(portStr)
+	if err != nil {
+		return nil, ConfigErrorFromString("dhcpv6: invalid `listen` port")
+	}
+	listener := net.UDPAddr{
+		IP:   ip,
+		Port: port,
+	}
+	sc := ServerConfig{
+		Listener: &listener,
+		Plugins:  nil,
+	}
+	// load plugins
+	pluginList := cast.ToSlice(v.Get("server6.plugins"))
+	if pluginList == nil {
+		return nil, ConfigErrorFromString("dhcpv6: invalid plugins section, not a list")
+	}
+	if len(pluginList) == 0 {
+		return &sc, nil
+	}
+	for name, v := range pluginList {
+		conf := cast.ToStringMap(v)
+		if conf == nil {
+			return nil, ConfigErrorFromString("dhcpv6: plugin `%s` is not a string map", name)
+		}
+		// 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
+		}
+		log.Printf("Found plugin: `%s` with %d args, `%v`", name, len(args), args)
+		sc.Plugins = append(sc.Plugins, &PluginConfig{Name: name, Args: args})
+
+	}
+	return &sc, nil
+}
+
+func parseV4Config(v *viper.Viper) (*ServerConfig, error) {
+	if exists := v.Get("server4"); exists != nil {
+		return nil, errors.New("DHCPv4 config parser not implemented yet")
+	}
+	return nil, nil
+}

+ 28 - 0
config/errors.go

@@ -0,0 +1,28 @@
+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)
+}

+ 19 - 0
handler/handler.go

@@ -0,0 +1,19 @@
+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 false, the returned packet may be nil or
+// invalid.
+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 - 0
leases.txt.example

@@ -0,0 +1 @@
+00:11:22:33:44:55 2001:2::1

+ 160 - 0
main.go

@@ -0,0 +1,160 @@
+package main
+
+import (
+	"errors"
+	"log"
+	"net"
+	"time"
+
+	"bitbucket.org/insomniacslk/coredhcp/config"
+	"bitbucket.org/insomniacslk/coredhcp/handler"
+	"bitbucket.org/insomniacslk/coredhcp/plugins"
+	_ "bitbucket.org/insomniacslk/coredhcp/plugins/file"
+	_ "bitbucket.org/insomniacslk/coredhcp/plugins/server_id"
+	"github.com/insomniacslk/dhcp/dhcpv4"
+	"github.com/insomniacslk/dhcp/dhcpv6"
+)
+
+// Application variables
+var (
+	AppName    = "CoreDHCP"
+	AppVersion = "v0.1"
+)
+
+// Server is a CoreDHCP server structure that holds information about
+// DHCPv6 and DHCPv4 servers, and their respective handlers.
+type Server struct {
+	Handlers6 []handler.Handler6
+	Handlers4 []handler.Handler4
+	Config    *config.Config
+	Server6   *dhcpv6.Server
+	Server4   *dhcpv4.Server
+	errors    chan error
+}
+
+// LoadPlugins reads a Viper configuration 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.
+func (s *Server) LoadPlugins(conf *config.Config) ([]*plugins.Plugin, error) {
+	log.Print("Loading plugins...")
+	loadedPlugins := make([]*plugins.Plugin, 0)
+
+	if conf.Server4 != nil {
+		return nil, errors.New("plugin loading for DHCPv4 not implemented yet")
+	}
+	// load v6 plugins
+	if conf.Server6 == nil {
+		return nil, errors.New("no configuration found for DHCPv6 server")
+	}
+	// now load the plugins. We need to call its setup function with
+	// the arguments extracted above. The setup function is mapped in
+	// plugins.RegisteredPlugins .
+	for _, pluginConf := range conf.Server6.Plugins {
+		if plugin, ok := plugins.RegisteredPlugins[pluginConf.Name]; ok {
+			log.Printf("Loading plugin `%s`", pluginConf.Name)
+			h6, err := plugin.Setup6(pluginConf.Args...)
+			if err != nil {
+				return nil, err
+			}
+			loadedPlugins = append(loadedPlugins, plugin)
+			if h6 == nil {
+				return nil, config.ConfigErrorFromString("no DHCPv6 handler for plugin %s", pluginConf.Name)
+			}
+			s.Handlers6 = append(s.Handlers6, h6)
+			//s.Handlers4 = append(s.Handlers4, h4)
+		} else {
+			return nil, config.ConfigErrorFromString("unknown plugin `%s`", pluginConf.Name)
+		}
+	}
+
+	return loadedPlugins, nil
+}
+
+// MainHandler6 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 (s *Server) MainHandler6(conn net.PacketConn, peer net.Addr, req dhcpv6.DHCPv6) {
+	var (
+		resp dhcpv6.DHCPv6
+		stop bool
+	)
+	for _, handler := range s.Handlers6 {
+		resp, stop = handler(req, resp)
+		if stop {
+			break
+		}
+	}
+	if resp != nil {
+		if _, err := conn.WriteTo(resp.ToBytes(), peer); err != nil {
+			log.Printf("conn.Write to %v failed: %v", peer, err)
+		}
+	} else {
+		log.Print("Dropping request because response is nil")
+	}
+}
+
+// MainHandler4 is like MainHandler6, but for DHCPv4 packets.
+func (s *Server) MainHandler4(conn net.PacketConn, peer net.Addr, d *dhcpv4.DHCPv4) {
+	log.Print(d.Summary())
+}
+
+// Start will start the server asynchronously. See `Wait` to wait until
+// the execution ends.
+func (s *Server) Start() error {
+	_, err := s.LoadPlugins(s.Config)
+	if err != nil {
+		return err
+	}
+
+	// listen
+	if s.Config.Server6 != nil {
+		log.Printf("Starting DHCPv6 listener on %v", s.Config.Server6.Listener)
+		s.Server6 = dhcpv6.NewServer(*s.Config.Server6.Listener, s.MainHandler6)
+		go func() {
+			s.errors <- s.Server6.ActivateAndServe()
+		}()
+	}
+
+	if s.Config.Server4 != nil {
+		log.Printf("Starting DHCPv4 listener on %v", s.Config.Server6.Listener)
+		s.Server4 = dhcpv4.NewServer(*s.Config.Server4.Listener, s.MainHandler4)
+		go func() {
+			s.errors <- s.Server4.ActivateAndServe()
+		}()
+	}
+
+	return nil
+}
+
+// Wait waits until the end of the execution of the server.
+func (s *Server) Wait() error {
+	log.Print("Waiting")
+	if s.Server6 != nil {
+		s.Server6.Close()
+	}
+	if s.Server4 != nil {
+		s.Server4.Close()
+	}
+	return <-s.errors
+}
+
+// NewServer creates a Server instance with the provided configuration.
+func NewServer(config *config.Config) *Server {
+	return &Server{Config: config, errors: make(chan error, 1)}
+}
+
+func main() {
+	config, err := config.Parse()
+	if err != nil {
+		log.Fatal(err)
+	}
+	server := NewServer(config)
+	if err := server.Start(); err != nil {
+		log.Fatal(err)
+	}
+	if err := server.Wait(); err != nil {
+		log.Print(err)
+	}
+	time.Sleep(time.Second)
+}

+ 122 - 0
plugins/file/plugin.go

@@ -0,0 +1,122 @@
+package clientport
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net"
+	"strings"
+
+	"bitbucket.org/insomniacslk/coredhcp/handler"
+	"bitbucket.org/insomniacslk/coredhcp/plugins"
+	"github.com/insomniacslk/dhcp/dhcpv4"
+	"github.com/insomniacslk/dhcp/dhcpv6"
+)
+
+func init() {
+	plugins.RegisterPlugin("file", setupFile6, setupFile4)
+}
+
+// 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
+)
+var serverID *dhcpv6.OptServerId
+
+// 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.Printf("plugins/file: reading leases from %s", filename)
+	data, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return nil, err
+	}
+	records := make(map[string]net.IP, 0)
+	// TODO ignore comments
+	for _, lineBytes := range bytes.Split(data, []byte{'\n'}) {
+		line := string(lineBytes)
+		if len(line) == 0 {
+			continue
+		}
+		tokens := strings.Fields(line)
+		if len(tokens) != 2 {
+			return nil, fmt.Errorf("plugins/file: malformed line: %s", line)
+		}
+		hwaddr, err := net.ParseMAC(tokens[0])
+		if err != nil {
+			return nil, fmt.Errorf("plugins/file: malformed hardware address: %s", tokens[0])
+		}
+		ipaddr := net.ParseIP(tokens[1])
+		if ipaddr.To16() == nil {
+			return nil, fmt.Errorf("plugins/file: 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) {
+	mac, err := dhcpv6.ExtractMAC(req)
+	if err != nil {
+		return nil, false
+	}
+
+	ipaddr, ok := StaticRecords[mac.String()]
+	if !ok {
+		return nil, false
+	}
+	log.Printf("Found IP address %s for MAC %s", ipaddr, mac)
+	if resp == nil {
+		resp, err = dhcpv6.NewAdvertiseFromSolicit(req)
+		if err != nil {
+			return nil, false
+		}
+	}
+	// TODO add an OptIANA based on the above data
+	return resp, true
+}
+
+// Handler4 handles DHCPv4 packets for the file plugin
+func Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
+	// TODO check the MAC address in the request
+	//      if it is present in StaticRecords, forge a response
+	//      and stop processing.
+	return resp, true
+}
+
+func setupFile6(args ...string) (handler.Handler6, error) {
+	h6, _, err := setupFile(true, args...)
+	return h6, err
+}
+
+func setupFile4(args ...string) (handler.Handler4, error) {
+	log.Print("plugins/file: loading `file` plugin for DHCPv4")
+	return nil, nil
+}
+
+func setupFile(v6 bool, args ...string) (handler.Handler6, handler.Handler4, error) {
+	if len(args) < 1 {
+		return nil, nil, errors.New("plugins/file: need a file name")
+	}
+	filename := args[0]
+	if filename == "" {
+		return nil, nil, errors.New("plugins/file: got empty file name")
+	}
+	records, err := LoadDHCPv6Records(filename)
+	if err != nil {
+		return nil, nil, fmt.Errorf("plugins/file: failed to load DHCPv6 records: %v", err)
+	}
+	log.Printf("plugins/file: loaded %d leases from %s", len(records), filename)
+	StaticRecords = records
+
+	return Handler6, Handler4, nil
+}

+ 41 - 0
plugins/plugin.go

@@ -0,0 +1,41 @@
+package plugins
+
+import (
+	"fmt"
+	"log"
+
+	"bitbucket.org/insomniacslk/coredhcp/handler"
+)
+
+// 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, 0)
+
+// 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 by its name and setup functions.
+func RegisterPlugin(name string, setup6 SetupFunc6, setup4 SetupFunc4) error {
+	log.Printf("Registering plugin \"%s\"", name)
+	if _, ok := RegisteredPlugins[name]; ok {
+		return fmt.Errorf("Plugin \"%s\" already registered", name)
+	}
+	plugin := Plugin{
+		Name:   name,
+		Setup6: setup6,
+		Setup4: setup4,
+	}
+	RegisteredPlugins[name] = &plugin
+	return nil
+}

+ 109 - 0
plugins/server_id/plugin.go

@@ -0,0 +1,109 @@
+package clientport
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"net"
+	"strings"
+
+	"bitbucket.org/insomniacslk/coredhcp/handler"
+	"bitbucket.org/insomniacslk/coredhcp/plugins"
+	"github.com/insomniacslk/dhcp/dhcpv4"
+	"github.com/insomniacslk/dhcp/dhcpv6"
+	"github.com/insomniacslk/dhcp/iana"
+)
+
+func init() {
+	plugins.RegisterPlugin("server_id", setupServerID6, setupServerID4)
+}
+
+// V6ServerID is the DUID of the v6 server
+var V6ServerID *dhcpv6.Duid
+
+// Handler6 handles DHCPv6 packets for the file plugin
+func Handler6(req, resp dhcpv6.DHCPv6) (dhcpv6.DHCPv6, bool) {
+	if V6ServerID == nil {
+		return resp, false
+	}
+	if resp == nil {
+		var (
+			tmp dhcpv6.DHCPv6
+			err error
+		)
+
+		switch req.Type() {
+		case dhcpv6.MessageTypeSolicit:
+			tmp, err = dhcpv6.NewAdvertiseFromSolicit(req)
+		case dhcpv6.MessageTypeRequest, dhcpv6.MessageTypeConfirm, dhcpv6.MessageTypeRenew,
+			dhcpv6.MessageTypeRebind, dhcpv6.MessageTypeRelease, dhcpv6.MessageTypeInformationRequest:
+			tmp, err = dhcpv6.NewReplyFromDHCPv6Message(req)
+		default:
+			err = fmt.Errorf("plugins/server_id: message type %d not supported", req.Type())
+		}
+
+		if err != nil {
+			log.Printf("plugins/server_id: NewReplyFromDHCPv6Message failed: %v", err)
+			return resp, false
+		}
+		resp = tmp
+	}
+	resp = dhcpv6.WithServerID(*V6ServerID)(resp)
+	return resp, false
+}
+
+// Handler4 handles DHCPv4 packets for the file plugin
+func Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) {
+	// do nothing
+	return resp, false
+}
+
+func setupServerID4(args ...string) (handler.Handler4, error) {
+	// TODO implement this function
+	return nil, errors.New("plugins/server_id: not implemented for DHCPv4")
+}
+
+func setupServerID6(args ...string) (handler.Handler6, error) {
+	log.Print("plugins/server_id: loading `server_id` plugin")
+	if len(args) < 2 {
+		return nil, errors.New("plugins/server_id: need a DUID type and value")
+	}
+	duidType := args[0]
+	if duidType == "" {
+		return nil, errors.New("plugins/server_id: got empty DUID type")
+	}
+	duidValue := args[1]
+	if duidValue == "" {
+		return nil, errors.New("plugins/server_id: 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.Duid{
+			Type: dhcpv6.DUID_LL,
+			// sorry, only ethernet for now
+			HwType:        iana.HwTypeEthernet,
+			LinkLayerAddr: hwaddr,
+		}
+	case "llt", "duid-llt", "duid_llt":
+		V6ServerID = &dhcpv6.Duid{
+			Type: dhcpv6.DUID_LLT,
+			// 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("plugins/server_id: using %s %s", duidType, duidValue)
+
+	return Handler6, nil
+}