mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
Define an intermediate struct for marshaling/unmarshaling; this struct already uses netip types (in case we want to migrate the actual struct to use these types as well). Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
557 lines
16 KiB
Go
557 lines
16 KiB
Go
package libnetwork
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"slices"
|
|
|
|
"github.com/moby/moby/v2/daemon/internal/netiputil"
|
|
"github.com/moby/moby/v2/daemon/libnetwork/driverapi"
|
|
"github.com/moby/moby/v2/daemon/libnetwork/types"
|
|
)
|
|
|
|
// EndpointInfo provides an interface to retrieve network resources bound to the endpoint.
|
|
type EndpointInfo interface {
|
|
// Iface returns information about the interface which was assigned to
|
|
// the endpoint by the driver. This can be used after the
|
|
// endpoint has been created.
|
|
Iface() *EndpointInterface
|
|
|
|
// Gateway returns the IPv4 gateway assigned by the driver.
|
|
// This will only return a valid value if a container has joined the endpoint.
|
|
Gateway() net.IP
|
|
|
|
// GatewayIPv6 returns the IPv6 gateway assigned by the driver.
|
|
// This will only return a valid value if a container has joined the endpoint.
|
|
GatewayIPv6() net.IP
|
|
|
|
// StaticRoutes returns the list of static routes configured by the network
|
|
// driver when the container joins a network
|
|
StaticRoutes() []*types.StaticRoute
|
|
|
|
// Sandbox returns the attached sandbox if there, nil otherwise.
|
|
Sandbox() *Sandbox
|
|
|
|
// LoadBalancer returns whether the endpoint is the load balancer endpoint for the network.
|
|
LoadBalancer() bool
|
|
}
|
|
|
|
// EndpointInterface holds interface addresses bound to the endpoint.
|
|
type EndpointInterface struct {
|
|
mac net.HardwareAddr
|
|
addr *net.IPNet
|
|
addrv6 *net.IPNet
|
|
llAddrs []*net.IPNet
|
|
srcName string
|
|
dstPrefix string
|
|
dstName string // dstName is the name of the interface in the container namespace. It takes precedence over dstPrefix.
|
|
routes []*net.IPNet
|
|
v4PoolID string
|
|
v6PoolID string
|
|
netnsPath string
|
|
createdInContainer bool
|
|
}
|
|
|
|
// endpointInterface is an intermediate struct used to marshal/unmarshal
|
|
// am [EndpointInterface] to JSON.
|
|
//
|
|
// TODO(thaJeztah): use "omitzero" for all fields once we no longer have to consider downgrades to < v29.0; see https://github.com/moby/moby/pull/51223#discussion_r2445826610
|
|
type endpointInterface struct {
|
|
MAC string `json:"mac"`
|
|
Addr netip.Prefix `json:"addr"`
|
|
AddrV6 netip.Prefix `json:"addrv6"`
|
|
LLAddrs []netip.Prefix `json:"llAddrs"`
|
|
Routes []netip.Prefix `json:"routes"`
|
|
SrcName string `json:"srcName"`
|
|
DstPrefix string `json:"dstPrefix"`
|
|
DstName string `json:"dstName"`
|
|
V4PoolID string `json:"v4PoolID"`
|
|
V6PoolID string `json:"v6PoolID"`
|
|
CreatedInContainer bool `json:"createdInContainer"`
|
|
}
|
|
|
|
func (epi *EndpointInterface) MarshalJSON() ([]byte, error) {
|
|
mac := ""
|
|
if epi.mac != nil {
|
|
mac = epi.mac.String()
|
|
}
|
|
|
|
return json.Marshal(&endpointInterface{
|
|
MAC: mac,
|
|
Addr: toPrefix(epi.addr),
|
|
AddrV6: toPrefix(epi.addrv6),
|
|
LLAddrs: toPrefixes(epi.llAddrs),
|
|
Routes: toPrefixes(epi.routes),
|
|
SrcName: epi.srcName,
|
|
DstPrefix: epi.dstPrefix,
|
|
DstName: epi.dstName,
|
|
V4PoolID: epi.v4PoolID,
|
|
V6PoolID: epi.v6PoolID,
|
|
CreatedInContainer: epi.createdInContainer,
|
|
})
|
|
}
|
|
|
|
func (epi *EndpointInterface) UnmarshalJSON(b []byte) error {
|
|
var epiTmp endpointInterface
|
|
if err := json.Unmarshal(b, &epiTmp); err != nil {
|
|
return err
|
|
}
|
|
|
|
var mac net.HardwareAddr
|
|
if epiTmp.MAC != "" {
|
|
hw, err := net.ParseMAC(epiTmp.MAC)
|
|
if err != nil {
|
|
return types.InternalErrorf("invalid mac %q: %v", epiTmp.MAC, err)
|
|
}
|
|
mac = hw
|
|
}
|
|
|
|
*epi = EndpointInterface{
|
|
mac: mac,
|
|
addr: netiputil.ToIPNet(epiTmp.Addr),
|
|
addrv6: netiputil.ToIPNet(epiTmp.AddrV6),
|
|
llAddrs: toIPNets(epiTmp.LLAddrs),
|
|
routes: toIPNets(epiTmp.Routes),
|
|
srcName: epiTmp.SrcName,
|
|
dstPrefix: epiTmp.DstPrefix,
|
|
dstName: epiTmp.DstName,
|
|
v4PoolID: epiTmp.V4PoolID,
|
|
v6PoolID: epiTmp.V6PoolID,
|
|
createdInContainer: epiTmp.CreatedInContainer,
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Copy returns a deep copy of [EndpointInterface]. If the receiver is nil,
|
|
// Copy returns nil.
|
|
func (epi *EndpointInterface) Copy() *EndpointInterface {
|
|
if epi == nil {
|
|
return nil
|
|
}
|
|
|
|
var routes []*net.IPNet
|
|
for _, route := range epi.routes {
|
|
routes = append(routes, types.GetIPNetCopy(route))
|
|
}
|
|
|
|
return &EndpointInterface{
|
|
mac: slices.Clone(epi.mac),
|
|
addr: types.GetIPNetCopy(epi.addr),
|
|
addrv6: types.GetIPNetCopy(epi.addrv6),
|
|
llAddrs: slices.Clone(epi.llAddrs),
|
|
srcName: epi.srcName,
|
|
dstPrefix: epi.dstPrefix,
|
|
dstName: epi.dstName,
|
|
routes: routes,
|
|
v4PoolID: epi.v4PoolID,
|
|
v6PoolID: epi.v6PoolID,
|
|
netnsPath: epi.netnsPath,
|
|
createdInContainer: epi.createdInContainer,
|
|
}
|
|
}
|
|
|
|
type endpointJoinInfo struct {
|
|
gw net.IP
|
|
gw6 net.IP
|
|
forceGw4 bool
|
|
forceGw6 bool
|
|
StaticRoutes []*types.StaticRoute
|
|
driverTableEntries []*tableEntry
|
|
disableGatewayService bool
|
|
}
|
|
|
|
type tableEntry struct {
|
|
tableName string
|
|
key string
|
|
value []byte
|
|
}
|
|
|
|
// Info hydrates the endpoint and returns certain operational data belonging
|
|
// to this endpoint.
|
|
//
|
|
// TODO(thaJeztah): make sure that Endpoint is always fully hydrated, and remove the EndpointInfo interface, and use Endpoint directly.
|
|
func (ep *Endpoint) Info() EndpointInfo {
|
|
if ep.sandboxID != "" {
|
|
return ep
|
|
}
|
|
n, err := ep.getNetworkFromStore()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
ep, err = n.getEndpointFromStore(ep.ID())
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
sb, ok := ep.getSandbox()
|
|
if !ok {
|
|
// endpoint hasn't joined any sandbox.
|
|
// Just return the endpoint
|
|
return ep
|
|
}
|
|
|
|
return sb.GetEndpoint(ep.ID())
|
|
}
|
|
|
|
// Iface returns information about the interface which was assigned to
|
|
// the endpoint by the driver. This can be used after the
|
|
// endpoint has been created.
|
|
func (ep *Endpoint) Iface() *EndpointInterface {
|
|
ep.mu.Lock()
|
|
defer ep.mu.Unlock()
|
|
return ep.iface
|
|
}
|
|
|
|
// SetMacAddress allows the driver to set the mac address to the endpoint interface
|
|
// during the call to CreateEndpoint, if the mac address is not already set.
|
|
func (epi *EndpointInterface) SetMacAddress(mac net.HardwareAddr) error {
|
|
if epi.mac != nil {
|
|
return types.ForbiddenErrorf("endpoint interface MAC address present (%s). Cannot be modified with %s.", epi.mac, mac)
|
|
}
|
|
if mac == nil {
|
|
return types.InvalidParameterErrorf("tried to set nil MAC address to endpoint interface")
|
|
}
|
|
epi.mac = slices.Clone(mac)
|
|
return nil
|
|
}
|
|
|
|
func (epi *EndpointInterface) SetIPAddress(address *net.IPNet) error {
|
|
if address.IP == nil {
|
|
return types.InvalidParameterErrorf("tried to set nil IP address to endpoint interface")
|
|
}
|
|
if address.IP.To4() == nil {
|
|
return setAddress(&epi.addrv6, address)
|
|
}
|
|
return setAddress(&epi.addr, address)
|
|
}
|
|
|
|
func setAddress(ifaceAddr **net.IPNet, address *net.IPNet) error {
|
|
if *ifaceAddr != nil {
|
|
return types.ForbiddenErrorf("endpoint interface IP present (%s). Cannot be modified with (%s).", *ifaceAddr, address)
|
|
}
|
|
*ifaceAddr = types.GetIPNetCopy(address)
|
|
return nil
|
|
}
|
|
|
|
// MacAddress returns the MAC address assigned to the endpoint.
|
|
func (epi *EndpointInterface) MacAddress() net.HardwareAddr {
|
|
return slices.Clone(epi.mac)
|
|
}
|
|
|
|
// Address returns the IPv4 address assigned to the endpoint.
|
|
func (epi *EndpointInterface) Address() *net.IPNet {
|
|
return types.GetIPNetCopy(epi.addr)
|
|
}
|
|
|
|
func (epi *EndpointInterface) Addr() netip.Prefix {
|
|
p, _ := netiputil.ToPrefix(epi.addr)
|
|
return p
|
|
}
|
|
|
|
// AddressIPv6 returns the IPv6 address assigned to the endpoint.
|
|
func (epi *EndpointInterface) AddressIPv6() *net.IPNet {
|
|
return types.GetIPNetCopy(epi.addrv6)
|
|
}
|
|
|
|
func (epi *EndpointInterface) AddrIPv6() netip.Prefix {
|
|
p, _ := netiputil.ToPrefix(epi.addrv6)
|
|
return p
|
|
}
|
|
|
|
// LinkLocalAddresses returns the list of link-local (IPv4/IPv6) addresses assigned to the endpoint.
|
|
func (epi *EndpointInterface) LinkLocalAddresses() []*net.IPNet {
|
|
return epi.llAddrs
|
|
}
|
|
|
|
// SrcName returns the name of the interface w/in the container
|
|
func (epi *EndpointInterface) SrcName() string {
|
|
return epi.srcName
|
|
}
|
|
|
|
// SetNames method assigns the srcName, dstName, and dstPrefix for the
|
|
// interface. If both dstName and dstPrefix are set, dstName takes precedence.
|
|
func (epi *EndpointInterface) SetNames(srcName, dstPrefix, dstName string) error {
|
|
epi.srcName = srcName
|
|
epi.dstPrefix = dstPrefix
|
|
epi.dstName = dstName
|
|
return nil
|
|
}
|
|
|
|
// NetnsPath returns the path of the network namespace, if there is one. Else "".
|
|
func (epi *EndpointInterface) NetnsPath() string {
|
|
return epi.netnsPath
|
|
}
|
|
|
|
// SetCreatedInContainer can be called by the driver to indicate that it's
|
|
// created the network interface in the container's network namespace (so,
|
|
// it doesn't need to be moved there).
|
|
func (epi *EndpointInterface) SetCreatedInContainer(cic bool) {
|
|
epi.createdInContainer = cic
|
|
}
|
|
|
|
func (ep *Endpoint) InterfaceName() driverapi.InterfaceNameInfo {
|
|
ep.mu.Lock()
|
|
defer ep.mu.Unlock()
|
|
return ep.iface
|
|
}
|
|
|
|
// AddStaticRoute adds a route to the sandbox.
|
|
// It may be used in addition to or instead of a default gateway (as above).
|
|
func (ep *Endpoint) AddStaticRoute(destination *net.IPNet, routeType types.RouteType, nextHop net.IP) error {
|
|
ep.mu.Lock()
|
|
defer ep.mu.Unlock()
|
|
if routeType == types.NEXTHOP {
|
|
// If the route specifies a next-hop, then it's loosely routed (i.e. not bound to a particular interface).
|
|
ep.joinInfo.StaticRoutes = append(ep.joinInfo.StaticRoutes, &types.StaticRoute{
|
|
Destination: destination,
|
|
RouteType: routeType,
|
|
NextHop: nextHop,
|
|
})
|
|
} else {
|
|
// If the route doesn't specify a next-hop, it must be a connected route, bound to an interface.
|
|
ep.iface.routes = append(ep.iface.routes, destination)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AddTableEntry adds a table entry to the gossip layer
|
|
// passing the table name, key and an opaque value.
|
|
func (ep *Endpoint) AddTableEntry(tableName, key string, value []byte) error {
|
|
ep.mu.Lock()
|
|
defer ep.mu.Unlock()
|
|
|
|
ep.joinInfo.driverTableEntries = append(ep.joinInfo.driverTableEntries, &tableEntry{
|
|
tableName: tableName,
|
|
key: key,
|
|
value: value,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// Sandbox returns the attached sandbox if there, nil otherwise.
|
|
func (ep *Endpoint) Sandbox() *Sandbox {
|
|
cnt, ok := ep.getSandbox()
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return cnt
|
|
}
|
|
|
|
// LoadBalancer returns whether the endpoint is the load balancer endpoint for the network.
|
|
func (ep *Endpoint) LoadBalancer() bool {
|
|
ep.mu.Lock()
|
|
defer ep.mu.Unlock()
|
|
return ep.loadBalancer
|
|
}
|
|
|
|
// StaticRoutes returns the list of static routes configured by the network
|
|
// driver when the container joins a network
|
|
func (ep *Endpoint) StaticRoutes() []*types.StaticRoute {
|
|
ep.mu.Lock()
|
|
defer ep.mu.Unlock()
|
|
|
|
if ep.joinInfo == nil {
|
|
return nil
|
|
}
|
|
|
|
return ep.joinInfo.StaticRoutes
|
|
}
|
|
|
|
// Gateway returns the IPv4 gateway assigned by the driver.
|
|
// This will only return a valid value if a container has joined the endpoint.
|
|
func (ep *Endpoint) Gateway() net.IP {
|
|
ep.mu.Lock()
|
|
defer ep.mu.Unlock()
|
|
|
|
if ep.joinInfo == nil {
|
|
return net.IP{}
|
|
}
|
|
|
|
return slices.Clone(ep.joinInfo.gw)
|
|
}
|
|
|
|
// GatewayIPv6 returns the IPv6 gateway assigned by the driver.
|
|
// This will only return a valid value if a container has joined the endpoint.
|
|
func (ep *Endpoint) GatewayIPv6() net.IP {
|
|
ep.mu.Lock()
|
|
defer ep.mu.Unlock()
|
|
|
|
if ep.joinInfo == nil {
|
|
return net.IP{}
|
|
}
|
|
|
|
return slices.Clone(ep.joinInfo.gw6)
|
|
}
|
|
|
|
// SetGateway sets the default IPv4 gateway when a container joins the endpoint.
|
|
func (ep *Endpoint) SetGateway(gw net.IP) error {
|
|
ep.mu.Lock()
|
|
defer ep.mu.Unlock()
|
|
|
|
ep.joinInfo.gw = slices.Clone(gw)
|
|
return nil
|
|
}
|
|
|
|
// SetGatewayIPv6 sets the default IPv6 gateway when a container joins the endpoint.
|
|
func (ep *Endpoint) SetGatewayIPv6(gw6 net.IP) error {
|
|
ep.mu.Lock()
|
|
defer ep.mu.Unlock()
|
|
|
|
ep.joinInfo.gw6 = slices.Clone(gw6)
|
|
return nil
|
|
}
|
|
|
|
func (ep *Endpoint) ForceGw4() {
|
|
ep.mu.Lock()
|
|
defer ep.mu.Unlock()
|
|
ep.joinInfo.forceGw4 = true
|
|
}
|
|
|
|
func (ep *Endpoint) ForceGw6() {
|
|
ep.mu.Lock()
|
|
defer ep.mu.Unlock()
|
|
ep.joinInfo.forceGw6 = true
|
|
}
|
|
|
|
// hasGatewayOrDefaultRoute returns true if ep has a gateway, or a route to '0.0.0.0'/'::'.
|
|
func (ep *Endpoint) hasGatewayOrDefaultRoute() (v4, v6 bool) {
|
|
ep.mu.Lock()
|
|
defer ep.mu.Unlock()
|
|
|
|
if ep.joinInfo != nil {
|
|
v4 = len(ep.joinInfo.gw) > 0 || ep.joinInfo.forceGw4
|
|
v6 = len(ep.joinInfo.gw6) > 0 || ep.joinInfo.forceGw6
|
|
if !v4 || !v6 {
|
|
for _, route := range ep.joinInfo.StaticRoutes {
|
|
if route.Destination.IP.IsUnspecified() && net.IP(route.Destination.Mask).IsUnspecified() {
|
|
if route.Destination.IP.To4() == nil {
|
|
v6 = true
|
|
} else {
|
|
v4 = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ep.iface != nil && (!v4 || !v6) {
|
|
for _, route := range ep.iface.routes {
|
|
if route.IP.IsUnspecified() && net.IP(route.Mask).IsUnspecified() {
|
|
if route.IP.To4() == nil {
|
|
v6 = true
|
|
} else {
|
|
v4 = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return v4, v6
|
|
}
|
|
|
|
func (ep *Endpoint) retrieveFromStore() (*Endpoint, error) {
|
|
n, err := ep.getNetworkFromStore()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not find network in store to get latest endpoint %s: %v", ep.Name(), err)
|
|
}
|
|
return n.getEndpointFromStore(ep.ID())
|
|
}
|
|
|
|
// DisableGatewayService tells libnetwork not to provide Default GW for the container
|
|
func (ep *Endpoint) DisableGatewayService() {
|
|
ep.mu.Lock()
|
|
defer ep.mu.Unlock()
|
|
|
|
ep.joinInfo.disableGatewayService = true
|
|
}
|
|
|
|
func (epj *endpointJoinInfo) MarshalJSON() ([]byte, error) {
|
|
epMap := make(map[string]any)
|
|
if epj.gw != nil {
|
|
epMap["gw"] = epj.gw.String()
|
|
}
|
|
if epj.gw6 != nil {
|
|
epMap["gw6"] = epj.gw6.String()
|
|
}
|
|
epMap["disableGatewayService"] = epj.disableGatewayService
|
|
epMap["StaticRoutes"] = epj.StaticRoutes
|
|
return json.Marshal(epMap)
|
|
}
|
|
|
|
func (epj *endpointJoinInfo) UnmarshalJSON(b []byte) error {
|
|
var (
|
|
err error
|
|
epMap map[string]any
|
|
)
|
|
if err = json.Unmarshal(b, &epMap); err != nil {
|
|
return err
|
|
}
|
|
if v, ok := epMap["gw"]; ok {
|
|
epj.gw = net.ParseIP(v.(string))
|
|
}
|
|
if v, ok := epMap["gw6"]; ok {
|
|
epj.gw6 = net.ParseIP(v.(string))
|
|
}
|
|
epj.disableGatewayService = epMap["disableGatewayService"].(bool)
|
|
|
|
var tStaticRoute []types.StaticRoute
|
|
if v, ok := epMap["StaticRoutes"]; ok {
|
|
// TODO(cpuguy83): Linter caught that we aren't checking errors here
|
|
// I don't know why we aren't other than potentially the data is not always expected to be right?
|
|
// This is why I'm not adding the error check.
|
|
//
|
|
// In any case for posterity please if you figure this out document it or check the error
|
|
tb, _ := json.Marshal(v) //nolint:errchkjson // FIXME: handle json (Un)Marshal errors (see above)
|
|
_ = json.Unmarshal(tb, &tStaticRoute) //nolint:errcheck
|
|
}
|
|
var StaticRoutes []*types.StaticRoute
|
|
for _, r := range tStaticRoute {
|
|
StaticRoutes = append(StaticRoutes, &r)
|
|
}
|
|
epj.StaticRoutes = StaticRoutes
|
|
|
|
return nil
|
|
}
|
|
|
|
func (epj *endpointJoinInfo) Copy() *endpointJoinInfo {
|
|
if epj == nil {
|
|
return nil
|
|
}
|
|
|
|
return &endpointJoinInfo{
|
|
gw: slices.Clone(epj.gw),
|
|
gw6: slices.Clone(epj.gw6),
|
|
StaticRoutes: slices.Clone(epj.StaticRoutes),
|
|
driverTableEntries: slices.Clone(epj.driverTableEntries),
|
|
disableGatewayService: epj.disableGatewayService,
|
|
}
|
|
}
|
|
|
|
func toPrefix(n *net.IPNet) netip.Prefix {
|
|
p, _ := netiputil.ToPrefix(n)
|
|
return p
|
|
}
|
|
|
|
func toIPNets(ps []netip.Prefix) []*net.IPNet {
|
|
out := make([]*net.IPNet, 0, len(ps))
|
|
for _, p := range ps {
|
|
if n := netiputil.ToIPNet(p); n != nil {
|
|
out = append(out, n)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func toPrefixes(nets []*net.IPNet) []netip.Prefix {
|
|
out := make([]netip.Prefix, 0, len(nets))
|
|
for _, n := range nets {
|
|
if prefix, ok := netiputil.ToPrefix(n); ok {
|
|
out = append(out, prefix)
|
|
}
|
|
}
|
|
return out
|
|
}
|