// 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 redisplugin import ( "errors" "fmt" "net" "strings" "time" "github.com/coredhcp/coredhcp/handler" "github.com/coredhcp/coredhcp/logger" "github.com/coredhcp/coredhcp/plugins" "github.com/gomodule/redigo/redis" "github.com/insomniacslk/dhcp/dhcpv4" "github.com/insomniacslk/dhcp/dhcpv6" ) // various global variables var ( log = logger.GetLogger("plugins/redis") pool *redis.Pool ) func init() { plugins.RegisterPlugin("redis", setupRedis6, setupRedis4) } // Handler6 handles DHCPv6 packets for the redis plugin func Handler6(req, resp dhcpv6.DHCPv6) (dhcpv6.DHCPv6, bool) { // TODO add IPv6 support return nil, true } // Handler4 handles DHCPv4 packets for the redis plugin func Handler4(req, resp *dhcpv4.DHCPv4) (*dhcpv4.DHCPv4, bool) { // Get redis connection from pool conn := pool.Get() // defer redis connection close so we don't leak connections defer conn.Close() // Get all options for a MAC options, err := redis.StringMap(conn.Do("HGETALL", "mac:"+req.ClientHWAddr.String())) // Handle redis error if err != nil { log.Printf("Redis error: %s...dropping request", err) return nil, true } // Handle no hash found if len(options) == 0 { log.Printf("MAC %s not found...dropping request", req.ClientHWAddr.String()) return nil, true } // Handle no ipv4 field if options["ipv4"] == "" { log.Printf("MAC %s has no ipv4 field...dropping request", req.ClientHWAddr.String()) return nil, true } // Loop through options returned and assign as needed for option, value := range options { switch option { case "ipv4": ipaddr, ipnet, err := net.ParseCIDR(value) if err != nil { log.Printf("MAC %s malformed IP %s error: %s...dropping request", req.ClientHWAddr.String(), value, err) return nil, true } resp.YourIPAddr = ipaddr resp.Options.Update(dhcpv4.OptSubnetMask(ipnet.Mask)) log.Printf("MAC %s assigned IPv4 address %s", req.ClientHWAddr.String(), value) case "router": router := net.ParseIP(value) if router.To4() == nil { log.Printf("MAC %s Invalid router option: %s...option skipped", req.ClientHWAddr.String(), value) break } resp.Options.Update(dhcpv4.OptRouter(router)) case "dns": var dnsServers4 []net.IP servers := strings.Split(value, ",") for _, server := range servers { DNSServer := net.ParseIP(server) if DNSServer.To4() == nil { log.Printf("MAC %s Invalid dns server: %s...dropping request", req.ClientHWAddr.String(), server) return nil, true } dnsServers4 = append(dnsServers4, DNSServer) } if req.IsOptionRequested(dhcpv4.OptionDomainNameServer) { resp.Options.Update(dhcpv4.OptDNS(dnsServers4...)) } case "leaseTime": lt, err := time.ParseDuration(value) if err != nil { log.Printf("MAC %s invalid lease time %s...option skipped", req.ClientHWAddr.String(), value) break } // Set lease time resp.Options.Update(dhcpv4.OptIPAddressLeaseTime(lt)) default: log.Printf("MAC %s found un-handled option %s...option skipped", req.ClientHWAddr.String(), option) } } return resp, false } func setupRedis6(args ...string) (handler.Handler6, error) { // TODO setup function for IPv6 log.Warning("not implemented for IPv6") return Handler6, nil } func setupRedis4(args ...string) (handler.Handler4, error) { _, h4, err := setupRedis(false, args...) return h4, err } func setupRedis(v6 bool, args ...string) (handler.Handler6, handler.Handler4, error) { if len(args) < 1 { return nil, nil, fmt.Errorf("invalid number of arguments, want: 1 (redis server:port), got: %d", len(args)) } if args[0] == "" { return nil, nil, errors.New("Redis server can't be empty") } if v6 { log.Printf("Using redis server %s for DHCPv6 static leases", args[0]) } else { log.Printf("Using redis server %s for DHCPv4 static leases", args[0]) } // Initialize Redis Pool pool = &redis.Pool{ MaxIdle: 10, IdleTimeout: 240 * time.Second, Dial: func() (redis.Conn, error) { return redis.Dial("tcp", args[0]) }, } return Handler6, Handler4, nil }