package plugin import ( "fmt" "net" "net/http" "regexp" "time" docker "github.com/docker/docker/client" "github.com/gorilla/handlers" "github.com/mitchellh/mapstructure" "github.com/vishvananda/netlink" "github.com/devplayer0/docker-net-dhcp/pkg/util" ) // DriverName is the name of the Docker Network Driver const DriverName string = "net-dhcp" // Network mode constants const ( NetworkModeBridge = "bridge" NetworkModeMacvlan = "macvlan" NetworkModeIPvlan = "ipvlan" ) const defaultLeaseTimeout = 10 * time.Second var driverRegexp = regexp.MustCompile(`^ghcr\.io/devplayer0/docker-net-dhcp:.+$`) // IsDHCPPlugin checks if a Docker network driver is an instance of this plugin func IsDHCPPlugin(driver string) bool { return driverRegexp.MatchString(driver) } // DHCPNetworkOptions contains options for the DHCP network driver type DHCPNetworkOptions struct { Bridge string Mode string IPv6 bool LeaseTimeout time.Duration `mapstructure:"lease_timeout"` IgnoreConflicts bool `mapstructure:"ignore_conflicts"` SkipRoutes bool `mapstructure:"skip_routes"` } // NetMode returns the normalised network mode, defaulting to "bridge". func (opts DHCPNetworkOptions) NetMode() string { if opts.Mode == "" { return NetworkModeBridge } return opts.Mode } func decodeOpts(input interface{}) (DHCPNetworkOptions, error) { var opts DHCPNetworkOptions optsDecoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ Result: &opts, ErrorUnused: true, WeaklyTypedInput: true, DecodeHook: mapstructure.ComposeDecodeHookFunc( mapstructure.StringToTimeDurationHookFunc(), ), }) if err != nil { return opts, fmt.Errorf("failed to create options decoder: %w", err) } if err := optsDecoder.Decode(input); err != nil { return opts, err } return opts, nil } type joinHint struct { IPv4 *netlink.Addr IPv6 *netlink.Addr Gateway string // IfIndex is the interface index for macvlan/ipvlan endpoints, used to locate // the interface after Docker moves it into the container network namespace. IfIndex int } // Plugin is the DHCP network plugin type Plugin struct { awaitTimeout time.Duration docker *docker.Client server http.Server joinHints map[string]joinHint persistentDHCP map[string]*dhcpManager } // NewPlugin creates a new Plugin func NewPlugin(awaitTimeout time.Duration) (*Plugin, error) { client, err := docker.NewClientWithOpts( docker.WithAPIVersionNegotiation(), docker.WithTimeout(2*time.Second), docker.FromEnv) if err != nil { return nil, fmt.Errorf("failed to create docker client: %w", err) } p := Plugin{ awaitTimeout: awaitTimeout, docker: client, joinHints: make(map[string]joinHint), persistentDHCP: make(map[string]*dhcpManager), } mux := http.NewServeMux() mux.HandleFunc("/NetworkDriver.GetCapabilities", p.apiGetCapabilities) mux.HandleFunc("/NetworkDriver.CreateNetwork", p.apiCreateNetwork) mux.HandleFunc("/NetworkDriver.DeleteNetwork", p.apiDeleteNetwork) mux.HandleFunc("/NetworkDriver.CreateEndpoint", p.apiCreateEndpoint) mux.HandleFunc("/NetworkDriver.EndpointOperInfo", p.apiEndpointOperInfo) mux.HandleFunc("/NetworkDriver.DeleteEndpoint", p.apiDeleteEndpoint) mux.HandleFunc("/NetworkDriver.Join", p.apiJoin) mux.HandleFunc("/NetworkDriver.Leave", p.apiLeave) p.server = http.Server{ Handler: handlers.CustomLoggingHandler(nil, mux, util.WriteAccessLog), } return &p, nil } // Listen starts the plugin server func (p *Plugin) Listen(bindSock string) error { l, err := net.Listen("unix", bindSock) if err != nil { return err } return p.server.Serve(l) } // Close stops the plugin server func (p *Plugin) Close() error { if err := p.docker.Close(); err != nil { return fmt.Errorf("failed to close docker client: %w", err) } if err := p.server.Close(); err != nil { return fmt.Errorf("failed to close http server: %w", err) } return nil }