| package remote | |
| import ( | |
| "context" | |
| "fmt" | |
| "net" | |
| "github.com/containerd/log" | |
| "github.com/docker/docker/libnetwork/discoverapi" | |
| "github.com/docker/docker/libnetwork/driverapi" | |
| "github.com/docker/docker/libnetwork/drivers/remote/api" | |
| "github.com/docker/docker/libnetwork/scope" | |
| "github.com/docker/docker/libnetwork/types" | |
| "github.com/docker/docker/pkg/plugingetter" | |
| "github.com/docker/docker/pkg/plugins" | |
| "github.com/pkg/errors" | |
| ) | |
| // remote driver must implement the discover-API. | |
| var _ discoverapi.Discover = (*driver)(nil) | |
| type driver struct { | |
| endpoint *plugins.Client | |
| networkType string | |
| } | |
| type maybeError interface { | |
| GetError() string | |
| } | |
| func newDriver(name string, client *plugins.Client) *driver { | |
| return &driver{networkType: name, endpoint: client} | |
| } | |
| // Register makes sure a remote driver is registered with r when a network | |
| // driver plugin is activated. | |
| func Register(r driverapi.Registerer, pg plugingetter.PluginGetter) error { | |
| newPluginHandler := func(name string, client *plugins.Client) { | |
| // negotiate driver capability with client | |
| d := newDriver(name, client) | |
| c, err := d.getCapabilities() | |
| if err != nil { | |
| log.G(context.TODO()).Errorf("error getting capability for %s due to %v", name, err) | |
| return | |
| } | |
| if err = r.RegisterDriver(name, d, *c); err != nil { | |
| log.G(context.TODO()).Errorf("error registering driver for %s due to %v", name, err) | |
| } | |
| } | |
| // Unit test code is unaware of a true PluginStore. So we fall back to v1 plugins. | |
| handleFunc := plugins.Handle | |
| if pg != nil { | |
| handleFunc = pg.Handle | |
| activePlugins := pg.GetAllManagedPluginsByCap(driverapi.NetworkPluginEndpointType) | |
| for _, ap := range activePlugins { | |
| client, err := getPluginClient(ap) | |
| if err != nil { | |
| return err | |
| } | |
| newPluginHandler(ap.Name(), client) | |
| } | |
| } | |
| handleFunc(driverapi.NetworkPluginEndpointType, newPluginHandler) | |
| return nil | |
| } | |
| func getPluginClient(p plugingetter.CompatPlugin) (*plugins.Client, error) { | |
| if v1, ok := p.(plugingetter.PluginWithV1Client); ok { | |
| return v1.Client(), nil | |
| } | |
| pa, ok := p.(plugingetter.PluginAddr) | |
| if !ok { | |
| return nil, errors.Errorf("unknown plugin type %T", p) | |
| } | |
| if pa.Protocol() != plugins.ProtocolSchemeHTTPV1 { | |
| return nil, errors.Errorf("unsupported plugin protocol %s", pa.Protocol()) | |
| } | |
| addr := pa.Addr() | |
| client, err := plugins.NewClientWithTimeout(addr.Network()+"://"+addr.String(), nil, pa.Timeout()) | |
| if err != nil { | |
| return nil, errors.Wrap(err, "error creating plugin client") | |
| } | |
| return client, nil | |
| } | |
| // Get capability from client | |
| func (d *driver) getCapabilities() (*driverapi.Capability, error) { | |
| var capResp api.GetCapabilityResponse | |
| if err := d.call("GetCapabilities", nil, &capResp); err != nil { | |
| return nil, err | |
| } | |
| c := &driverapi.Capability{} | |
| switch capResp.Scope { | |
| case scope.Global, scope.Local: | |
| c.DataScope = capResp.Scope | |
| default: | |
| return nil, fmt.Errorf("invalid capability: expecting 'local' or 'global', got %s", capResp.Scope) | |
| } | |
| switch capResp.ConnectivityScope { | |
| case scope.Global, scope.Local: | |
| c.ConnectivityScope = capResp.ConnectivityScope | |
| case "": | |
| c.ConnectivityScope = c.DataScope | |
| default: | |
| return nil, fmt.Errorf("invalid capability: expecting 'local' or 'global', got %s", capResp.Scope) | |
| } | |
| return c, nil | |
| } | |
| // Config is not implemented for remote drivers, since it is assumed | |
| // to be supplied to the remote process out-of-band (e.g., as command | |
| // line arguments). | |
| func (d *driver) Config(option map[string]interface{}) error { | |
| return &driverapi.ErrNotImplemented{} | |
| } | |
| func (d *driver) call(methodName string, arg interface{}, retVal maybeError) error { | |
| method := driverapi.NetworkPluginEndpointType + "." + methodName | |
| err := d.endpoint.Call(method, arg, retVal) | |
| if err != nil { | |
| return err | |
| } | |
| if e := retVal.GetError(); e != "" { | |
| return fmt.Errorf("remote: %s", e) | |
| } | |
| return nil | |
| } | |
| func (d *driver) NetworkAllocate(id string, options map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) { | |
| create := &api.AllocateNetworkRequest{ | |
| NetworkID: id, | |
| Options: options, | |
| IPv4Data: ipV4Data, | |
| IPv6Data: ipV6Data, | |
| } | |
| retVal := api.AllocateNetworkResponse{} | |
| err := d.call("AllocateNetwork", create, &retVal) | |
| return retVal.Options, err | |
| } | |
| func (d *driver) NetworkFree(id string) error { | |
| fr := &api.FreeNetworkRequest{NetworkID: id} | |
| return d.call("FreeNetwork", fr, &api.FreeNetworkResponse{}) | |
| } | |
| func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) { | |
| } | |
| func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) { | |
| return "", nil | |
| } | |
| func (d *driver) CreateNetwork(id string, options map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error { | |
| create := &api.CreateNetworkRequest{ | |
| NetworkID: id, | |
| Options: options, | |
| IPv4Data: ipV4Data, | |
| IPv6Data: ipV6Data, | |
| } | |
| return d.call("CreateNetwork", create, &api.CreateNetworkResponse{}) | |
| } | |
| func (d *driver) DeleteNetwork(nid string) error { | |
| return d.call("DeleteNetwork", &api.DeleteNetworkRequest{NetworkID: nid}, &api.DeleteNetworkResponse{}) | |
| } | |
| func (d *driver) CreateEndpoint(_ context.Context, nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) (retErr error) { | |
| if ifInfo == nil { | |
| return errors.New("must not be called with nil InterfaceInfo") | |
| } | |
| reqIface := &api.EndpointInterface{} | |
| if ifInfo.Address() != nil { | |
| reqIface.Address = ifInfo.Address().String() | |
| } | |
| if ifInfo.AddressIPv6() != nil { | |
| reqIface.AddressIPv6 = ifInfo.AddressIPv6().String() | |
| } | |
| if ifInfo.MacAddress() != nil { | |
| reqIface.MacAddress = ifInfo.MacAddress().String() | |
| } | |
| create := &api.CreateEndpointRequest{ | |
| NetworkID: nid, | |
| EndpointID: eid, | |
| Interface: reqIface, | |
| Options: epOptions, | |
| } | |
| var res api.CreateEndpointResponse | |
| if err := d.call("CreateEndpoint", create, &res); err != nil { | |
| return err | |
| } | |
| defer func() { | |
| if retErr != nil { | |
| if err := d.DeleteEndpoint(nid, eid); err != nil { | |
| retErr = fmt.Errorf("%w; failed to roll back: %w", err, retErr) | |
| } else { | |
| retErr = fmt.Errorf("%w; rolled back", retErr) | |
| } | |
| } | |
| }() | |
| inIface, err := parseInterface(res) | |
| if err != nil { | |
| return err | |
| } | |
| if inIface == nil { | |
| // Remote driver did not set any field | |
| return nil | |
| } | |
| if inIface.MacAddress != nil { | |
| if err := ifInfo.SetMacAddress(inIface.MacAddress); err != nil { | |
| return fmt.Errorf("driver modified interface MAC address: %v", err) | |
| } | |
| } | |
| if inIface.Address != nil { | |
| if err := ifInfo.SetIPAddress(inIface.Address); err != nil { | |
| return fmt.Errorf("driver modified interface address: %v", err) | |
| } | |
| } | |
| if inIface.AddressIPv6 != nil { | |
| if err := ifInfo.SetIPAddress(inIface.AddressIPv6); err != nil { | |
| return fmt.Errorf("driver modified interface address: %v", err) | |
| } | |
| } | |
| return nil | |
| } | |
| func (d *driver) DeleteEndpoint(nid, eid string) error { | |
| deleteRequest := &api.DeleteEndpointRequest{ | |
| NetworkID: nid, | |
| EndpointID: eid, | |
| } | |
| return d.call("DeleteEndpoint", deleteRequest, &api.DeleteEndpointResponse{}) | |
| } | |
| func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) { | |
| info := &api.EndpointInfoRequest{ | |
| NetworkID: nid, | |
| EndpointID: eid, | |
| } | |
| var res api.EndpointInfoResponse | |
| if err := d.call("EndpointOperInfo", info, &res); err != nil { | |
| return nil, err | |
| } | |
| return res.Value, nil | |
| } | |
| // Join method is invoked when a Sandbox is attached to an endpoint. | |
| func (d *driver) Join(_ context.Context, nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, options map[string]interface{}) (retErr error) { | |
| join := &api.JoinRequest{ | |
| NetworkID: nid, | |
| EndpointID: eid, | |
| SandboxKey: sboxKey, | |
| Options: options, | |
| } | |
| var ( | |
| res api.JoinResponse | |
| err error | |
| ) | |
| if err = d.call("Join", join, &res); err != nil { | |
| return err | |
| } | |
| defer func() { | |
| if retErr != nil { | |
| if err := d.Leave(nid, eid); err != nil { | |
| retErr = fmt.Errorf("%w; failed to roll back: %w", err, retErr) | |
| } else { | |
| retErr = fmt.Errorf("%w; rolled back", retErr) | |
| } | |
| } | |
| }() | |
| ifaceName := res.InterfaceName | |
| if iface := jinfo.InterfaceName(); iface != nil && ifaceName != nil { | |
| if err := iface.SetNames(ifaceName.SrcName, ifaceName.DstPrefix); err != nil { | |
| return fmt.Errorf("failed to set interface name: %s", err) | |
| } | |
| } | |
| var addr net.IP | |
| if res.Gateway != "" { | |
| if addr = net.ParseIP(res.Gateway); addr == nil { | |
| return fmt.Errorf(`unable to parse Gateway "%s"`, res.Gateway) | |
| } | |
| if jinfo.SetGateway(addr) != nil { | |
| return fmt.Errorf("failed to set gateway: %v", addr) | |
| } | |
| } | |
| if res.GatewayIPv6 != "" { | |
| if addr = net.ParseIP(res.GatewayIPv6); addr == nil { | |
| return fmt.Errorf(`unable to parse GatewayIPv6 "%s"`, res.GatewayIPv6) | |
| } | |
| if jinfo.SetGatewayIPv6(addr) != nil { | |
| return fmt.Errorf("failed to set gateway IPv6: %v", addr) | |
| } | |
| } | |
| if len(res.StaticRoutes) > 0 { | |
| routes, err := parseStaticRoutes(res) | |
| if err != nil { | |
| return err | |
| } | |
| for _, route := range routes { | |
| if jinfo.AddStaticRoute(route.Destination, route.RouteType, route.NextHop) != nil { | |
| return fmt.Errorf("failed to set static route: %v", route) | |
| } | |
| } | |
| } | |
| if res.DisableGatewayService { | |
| jinfo.DisableGatewayService() | |
| } | |
| return nil | |
| } | |
| // Leave method is invoked when a Sandbox detaches from an endpoint. | |
| func (d *driver) Leave(nid, eid string) error { | |
| leave := &api.LeaveRequest{ | |
| NetworkID: nid, | |
| EndpointID: eid, | |
| } | |
| return d.call("Leave", leave, &api.LeaveResponse{}) | |
| } | |
| // ProgramExternalConnectivity is invoked to program the rules to allow external connectivity for the endpoint. | |
| func (d *driver) ProgramExternalConnectivity(_ context.Context, nid, eid string, options map[string]interface{}) error { | |
| data := &api.ProgramExternalConnectivityRequest{ | |
| NetworkID: nid, | |
| EndpointID: eid, | |
| Options: options, | |
| } | |
| err := d.call("ProgramExternalConnectivity", data, &api.ProgramExternalConnectivityResponse{}) | |
| if err != nil && plugins.IsNotFound(err) { | |
| // It is not mandatory yet to support this method | |
| return nil | |
| } | |
| return err | |
| } | |
| // RevokeExternalConnectivity method is invoked to remove any external connectivity programming related to the endpoint. | |
| func (d *driver) RevokeExternalConnectivity(nid, eid string) error { | |
| data := &api.RevokeExternalConnectivityRequest{ | |
| NetworkID: nid, | |
| EndpointID: eid, | |
| } | |
| err := d.call("RevokeExternalConnectivity", data, &api.RevokeExternalConnectivityResponse{}) | |
| if err != nil && plugins.IsNotFound(err) { | |
| // It is not mandatory yet to support this method | |
| return nil | |
| } | |
| return err | |
| } | |
| func (d *driver) Type() string { | |
| return d.networkType | |
| } | |
| func (d *driver) IsBuiltIn() bool { | |
| return false | |
| } | |
| // DiscoverNew is a notification for a new discovery event, such as a new node joining a cluster | |
| func (d *driver) DiscoverNew(dType discoverapi.DiscoveryType, data interface{}) error { | |
| if dType != discoverapi.NodeDiscovery { | |
| return nil | |
| } | |
| notif := &api.DiscoveryNotification{ | |
| DiscoveryType: dType, | |
| DiscoveryData: data, | |
| } | |
| return d.call("DiscoverNew", notif, &api.DiscoveryResponse{}) | |
| } | |
| // DiscoverDelete is a notification for a discovery delete event, such as a node leaving a cluster | |
| func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{}) error { | |
| if dType != discoverapi.NodeDiscovery { | |
| return nil | |
| } | |
| notif := &api.DiscoveryNotification{ | |
| DiscoveryType: dType, | |
| DiscoveryData: data, | |
| } | |
| return d.call("DiscoverDelete", notif, &api.DiscoveryResponse{}) | |
| } | |
| func parseStaticRoutes(r api.JoinResponse) ([]*types.StaticRoute, error) { | |
| routes := make([]*types.StaticRoute, len(r.StaticRoutes)) | |
| for i, inRoute := range r.StaticRoutes { | |
| var err error | |
| outRoute := &types.StaticRoute{RouteType: inRoute.RouteType} | |
| if inRoute.Destination != "" { | |
| if outRoute.Destination, err = types.ParseCIDR(inRoute.Destination); err != nil { | |
| return nil, err | |
| } | |
| } | |
| if inRoute.NextHop != "" { | |
| outRoute.NextHop = net.ParseIP(inRoute.NextHop) | |
| if outRoute.NextHop == nil { | |
| return nil, fmt.Errorf("failed to parse nexthop IP %s", inRoute.NextHop) | |
| } | |
| } | |
| routes[i] = outRoute | |
| } | |
| return routes, nil | |
| } | |
| // parseInterface validates all the parameters of an Interface and returns them. | |
| func parseInterface(r api.CreateEndpointResponse) (*api.Interface, error) { | |
| var outIf *api.Interface | |
| inIf := r.Interface | |
| if inIf != nil { | |
| var err error | |
| outIf = &api.Interface{} | |
| if inIf.Address != "" { | |
| if outIf.Address, err = types.ParseCIDR(inIf.Address); err != nil { | |
| return nil, err | |
| } | |
| } | |
| if inIf.AddressIPv6 != "" { | |
| if outIf.AddressIPv6, err = types.ParseCIDR(inIf.AddressIPv6); err != nil { | |
| return nil, err | |
| } | |
| } | |
| if inIf.MacAddress != "" { | |
| if outIf.MacAddress, err = net.ParseMAC(inIf.MacAddress); err != nil { | |
| return nil, err | |
| } | |
| } | |
| } | |
| return outIf, nil | |
| } |