diff --git a/links/endpoint_bridge.go b/links/endpoint_bridge.go index c160adc91..61a3e39e7 100644 --- a/links/endpoint_bridge.go +++ b/links/endpoint_bridge.go @@ -12,6 +12,12 @@ type EndpointBridge struct { EndpointGeneric } +func NewEndpointBridge(eg *EndpointGeneric) *EndpointBridge { + return &EndpointBridge{ + EndpointGeneric: *eg, + } +} + func (e *EndpointBridge) Verify(p *VerifyLinkParams) error { errs := []error{} err := CheckEndpointUniqueness(e) diff --git a/links/endpoint_host.go b/links/endpoint_host.go index 65534ae1b..dd885c96e 100644 --- a/links/endpoint_host.go +++ b/links/endpoint_host.go @@ -6,6 +6,12 @@ type EndpointHost struct { EndpointGeneric } +func NewEndpointHost(eg *EndpointGeneric) *EndpointHost { + return &EndpointHost{ + EndpointGeneric: *eg, + } +} + func (e *EndpointHost) Verify(_ *VerifyLinkParams) error { errs := []error{} err := CheckEndpointUniqueness(e) diff --git a/links/endpoint_macvlan.go b/links/endpoint_macvlan.go index 6b6f75298..d535565fb 100644 --- a/links/endpoint_macvlan.go +++ b/links/endpoint_macvlan.go @@ -4,6 +4,12 @@ type EndpointMacVlan struct { EndpointGeneric } +func NewEndpointMacVlan(eg *EndpointGeneric) *EndpointMacVlan { + return &EndpointMacVlan{ + EndpointGeneric: *eg, + } +} + // Verify verifies the veth based deployment pre-conditions func (e *EndpointMacVlan) Verify(_ *VerifyLinkParams) error { return CheckEndpointExists(e) diff --git a/links/endpoint_raw.go b/links/endpoint_raw.go index 6e0fdceb9..8e719e329 100644 --- a/links/endpoint_raw.go +++ b/links/endpoint_raw.go @@ -59,17 +59,11 @@ func (er *EndpointRaw) Resolve(params *ResolveParams, l Link) (Endpoint, error) switch node.GetLinkEndpointType() { case LinkEndpointTypeBridge: - e = &EndpointBridge{ - EndpointGeneric: *genericEndpoint, - } + e = NewEndpointBridge(genericEndpoint) case LinkEndpointTypeHost: - e = &EndpointHost{ - EndpointGeneric: *genericEndpoint, - } + e = NewEndpointHost(genericEndpoint) case LinkEndpointTypeVeth: - e = &EndpointVeth{ - EndpointGeneric: *genericEndpoint, - } + e = NewEndpointVeth(genericEndpoint) } // also add the endpoint to the node diff --git a/links/endpoint_veth.go b/links/endpoint_veth.go index 0531a48d7..04a613634 100644 --- a/links/endpoint_veth.go +++ b/links/endpoint_veth.go @@ -4,6 +4,12 @@ type EndpointVeth struct { EndpointGeneric } +func NewEndpointVeth(eg *EndpointGeneric) *EndpointVeth { + return &EndpointVeth{ + EndpointGeneric: *eg, + } +} + // Verify verifies the veth based deployment pre-conditions func (e *EndpointVeth) Verify(_ *VerifyLinkParams) error { return CheckEndpointUniqueness(e) diff --git a/links/link.go b/links/link.go index e3e391fb1..9ec094162 100644 --- a/links/link.go +++ b/links/link.go @@ -37,11 +37,12 @@ type LinkDefinition struct { type LinkType string const ( - LinkTypeVEth LinkType = "veth" - LinkTypeMgmtNet LinkType = "mgmt-net" - LinkTypeMacVLan LinkType = "macvlan" - LinkTypeHost LinkType = "host" - LinkTypeVxlan LinkType = "vxlan" + LinkTypeVEth LinkType = "veth" + LinkTypeMgmtNet LinkType = "mgmt-net" + LinkTypeMacVLan LinkType = "macvlan" + LinkTypeHost LinkType = "host" + LinkTypeVxlan LinkType = "vxlan" + LinkTypeVxlanStitch LinkType = "vxlan-stitch" // LinkTypeBrief is a link definition where link types // are encoded in the endpoint definition as string and allow users @@ -64,6 +65,8 @@ func parseLinkType(s string) (LinkType, error) { return LinkTypeBrief, nil case string(LinkTypeVxlan): return LinkTypeVxlan, nil + case string(LinkTypeVxlanStitch): + return LinkTypeVxlanStitch, nil default: return "", fmt.Errorf("unable to parse %q as LinkType", s) } @@ -158,6 +161,18 @@ func (ld *LinkDefinition) UnmarshalYAML(unmarshal func(interface{}) error) error if err != nil { return err } + l.LinkVxlanRaw.LinkType = LinkTypeVxlan + ld.Link = &l.LinkVxlanRaw + case LinkTypeVxlanStitch: + var l struct { + Type string `yaml:"type"` + LinkVxlanRaw `yaml:",inline"` + } + err := unmarshal(&l) + if err != nil { + return err + } + l.LinkVxlanRaw.LinkType = LinkTypeVxlanStitch ld.Link = &l.LinkVxlanRaw case LinkTypeBrief: // brief link's endpoint format diff --git a/links/link_host.go b/links/link_host.go index e1739c12c..aa6e0fe60 100644 --- a/links/link_host.go +++ b/links/link_host.go @@ -70,7 +70,7 @@ func (r *LinkHostRaw) Resolve(params *ResolveParams) (Link, error) { return nil, err } // set the end point in the link - link.Endpoints = []Endpoint{ep, hostEp} + link.endpoints = []Endpoint{ep, hostEp} // add the link to the endpoints node hostEp.GetNode().AddLink(link) diff --git a/links/link_mgmt-net.go b/links/link_mgmt-net.go index a758b4e32..fc4722270 100644 --- a/links/link_mgmt-net.go +++ b/links/link_mgmt-net.go @@ -56,7 +56,7 @@ func (r *LinkMgmtNetRaw) Resolve(params *ResolveParams) (Link, error) { return nil, err } - link.Endpoints = []Endpoint{bridgeEp, contEp} + link.endpoints = []Endpoint{bridgeEp, contEp} // add link to respective endpoint nodes bridgeEp.GetNode().AddLink(link) diff --git a/links/link_veth.go b/links/link_veth.go index d54f0714b..c52266fc5 100644 --- a/links/link_veth.go +++ b/links/link_veth.go @@ -43,10 +43,8 @@ func (r *LinkVEthRaw) GetType() LinkType { // Resolving a veth link resolves its endpoints. func (r *LinkVEthRaw) Resolve(params *ResolveParams) (Link, error) { // create LinkVEth struct - l := &LinkVEth{ - LinkCommonParams: r.LinkCommonParams, - Endpoints: make([]Endpoint, 0, 2), - } + l := NewLinkVEth() + l.LinkCommonParams = r.LinkCommonParams // resolve raw endpoints (epr) to endpoints (ep) for _, epr := range r.Endpoints { @@ -55,7 +53,7 @@ func (r *LinkVEthRaw) Resolve(params *ResolveParams) (Link, error) { return nil, err } // add endpoint to the link endpoints - l.Endpoints = append(l.Endpoints, ep) + l.endpoints = append(l.endpoints, ep) // add link to endpoint node ep.GetNode().AddLink(l) } @@ -83,12 +81,19 @@ func linkVEthRawFromLinkBriefRaw(lb *LinkBriefRaw) (*LinkVEthRaw, error) { type LinkVEth struct { LinkCommonParams - Endpoints []Endpoint + endpoints []Endpoint deploymentState LinkDeploymentState stateMutex sync.RWMutex } +func NewLinkVEth() *LinkVEth { + return &LinkVEth{ + endpoints: make([]Endpoint, 0, 2), + deploymentState: LinkDeploymentStateNotDeployed, + } +} + func (*LinkVEth) GetType() LinkType { return LinkTypeVEth } @@ -113,11 +118,11 @@ func (l *LinkVEth) Deploy(ctx context.Context) error { // build the netlink.Veth struct for the link provisioning linkA := &netlink.Veth{ LinkAttrs: netlink.LinkAttrs{ - Name: l.Endpoints[0].GetRandIfaceName(), + Name: l.endpoints[0].GetRandIfaceName(), MTU: l.MTU, // Mac address is set later on }, - PeerName: l.Endpoints[1].GetRandIfaceName(), + PeerName: l.endpoints[1].GetRandIfaceName(), // PeerMac address is set later on } @@ -128,7 +133,7 @@ func (l *LinkVEth) Deploy(ctx context.Context) error { } // retrieve the netlink.Link for the B / Peer side of the link - linkB, err := netlink.LinkByName(l.Endpoints[1].GetRandIfaceName()) + linkB, err := netlink.LinkByName(l.endpoints[1].GetRandIfaceName()) if err != nil { return err } @@ -139,23 +144,23 @@ func (l *LinkVEth) Deploy(ctx context.Context) error { // linkSetupFunc is executed in a netns of a node. for idx, link := range []netlink.Link{linkA, linkB} { var linkSetupFunc func(ns.NetNS) error - switch l.Endpoints[idx].GetNode().GetLinkEndpointType() { + switch l.endpoints[idx].GetNode().GetLinkEndpointType() { // if the endpoint is a bridge we also need to set the master of the interface to the bridge case LinkEndpointTypeBridge: - bridgeName := l.Endpoints[idx].GetNode().GetShortName() + bridgeName := l.endpoints[idx].GetNode().GetShortName() // set the adjustmentFunc to the function that, besides the name, mac and up state // also sets the Master of the interface to the bridge - linkSetupFunc = SetNameMACMasterAndUpInterface(link, l.Endpoints[idx], bridgeName) + linkSetupFunc = SetNameMACMasterAndUpInterface(link, l.endpoints[idx], bridgeName) default: // default case is a regular veth link where both ends are regular linux interfaces // in the relevant containers. - linkSetupFunc = SetNameMACAndUpInterface(link, l.Endpoints[idx]) + linkSetupFunc = SetNameMACAndUpInterface(link, l.endpoints[idx]) } // if the node is a regular namespace node // add link to node, rename, set mac and Up - err = l.Endpoints[idx].GetNode().AddLinkToContainer(ctx, link, linkSetupFunc) + err = l.endpoints[idx].GetNode().AddLinkToContainer(ctx, link, linkSetupFunc) if err != nil { return err } @@ -174,5 +179,5 @@ func (l *LinkVEth) Remove(_ context.Context) error { } func (l *LinkVEth) GetEndpoints() []Endpoint { - return l.Endpoints + return l.endpoints } diff --git a/links/link_veth_test.go b/links/link_veth_test.go index 34db44265..ab1e45e27 100644 --- a/links/link_veth_test.go +++ b/links/link_veth_test.go @@ -148,7 +148,7 @@ func TestLinkVEthRaw_Resolve(t *testing.T) { Labels: map[string]string{"foo": "bar"}, Vars: map[string]any{"foo": "bar"}, }, - Endpoints: []Endpoint{ + endpoints: []Endpoint{ &EndpointVeth{ EndpointGeneric: EndpointGeneric{ Node: fn1, @@ -183,13 +183,13 @@ func TestLinkVEthRaw_Resolve(t *testing.T) { t.Errorf("LinkVEthRaw.Resolve() LinkCommonParams diff = %s", d) } - for i, e := range l.Endpoints { - if e.(*EndpointVeth).IfaceName != tt.want.Endpoints[i].(*EndpointVeth).IfaceName { - t.Errorf("LinkVEthRaw.Resolve() EndpointVeth got %s, want %s", e.(*EndpointVeth).IfaceName, tt.want.Endpoints[i].(*EndpointVeth).IfaceName) + for i, e := range l.endpoints { + if e.(*EndpointVeth).IfaceName != tt.want.endpoints[i].(*EndpointVeth).IfaceName { + t.Errorf("LinkVEthRaw.Resolve() EndpointVeth got %s, want %s", e.(*EndpointVeth).IfaceName, tt.want.endpoints[i].(*EndpointVeth).IfaceName) } - if e.(*EndpointVeth).Node != tt.want.Endpoints[i].(*EndpointVeth).Node { - t.Errorf("LinkVEthRaw.Resolve() EndpointVeth got %s, want %s", e.(*EndpointVeth).Node, tt.want.Endpoints[i].(*EndpointVeth).Node) + if e.(*EndpointVeth).Node != tt.want.endpoints[i].(*EndpointVeth).Node { + t.Errorf("LinkVEthRaw.Resolve() EndpointVeth got %s, want %s", e.(*EndpointVeth).Node, tt.want.endpoints[i].(*EndpointVeth).Node) } } }) diff --git a/links/link_vxlan.go b/links/link_vxlan.go index 572975625..f16e9021c 100644 --- a/links/link_vxlan.go +++ b/links/link_vxlan.go @@ -8,6 +8,7 @@ import ( "github.com/jsimonetti/rtnetlink/rtnl" log "github.com/sirupsen/logrus" + "github.com/srl-labs/containerlab/utils" "github.com/vishvananda/netlink" ) @@ -19,17 +20,171 @@ type LinkVxlanRaw struct { Endpoint EndpointRaw `yaml:"endpoint"` UdpPort int `yaml:"udp-port,omitempty"` ParentInterface string `yaml:"parent-interface,omitempty"` + NoLearning bool `yaml:"no-learning,omitempty"` + NoL2Miss bool `yaml:"no-l2miss,omitempty"` + NoL3Miss bool `yaml:"no-l3miss,omitempty"` + + // we use the same struct for vxlan and vxlan stitch, so we need to differentiate them in the raw format + LinkType LinkType } func (lr *LinkVxlanRaw) Resolve(params *ResolveParams) (Link, error) { + switch lr.LinkType { + case LinkTypeVxlan: + return lr.resolveRegular(params) + case LinkTypeVxlanStitch: + return lr.resolveStitched(params) + default: + return nil, fmt.Errorf("unexpected LinkType %s for Vxlan based link", lr.LinkType) + } +} + +func (lr *LinkVxlanRaw) resolveStitchedVxlan(params *ResolveParams) (*LinkVxlan, error) { + var err error + link := &LinkVxlan{ + deploymentState: LinkDeploymentStateNotDeployed, + LinkCommonParams: lr.LinkCommonParams, + noLearning: lr.NoLearning, + noL2Miss: lr.NoL2Miss, + noL3Miss: lr.NoL3Miss, + } + + // point the vxlan endpoint to the host system + vxlanRawEp := lr.Endpoint + vxlanRawEp.Iface = fmt.Sprintf("vx-%s-%s", lr.Endpoint.Node, lr.Endpoint.Iface) + vxlanRawEp.Node = "host" + vxlanRawEp.MAC = "" + if err != nil { + return nil, err + } + + // resolve local Endpoint + link.localEndpoint, err = vxlanRawEp.Resolve(params, link) + if err != nil { + return nil, err + } + + ip := net.ParseIP(lr.Remote) + // if the returned ip is nil, an error occured. + // we consider, that we maybe have a textual hostname + // e.g. dns name so we try to resolve the string next + if ip == nil { + ips, err := net.LookupIP(lr.Remote) + if err != nil { + return nil, err + } + + // prepare log message + sb := strings.Builder{} + for _, ip := range ips { + sb.WriteString(", ") + sb.WriteString(ip.String()) + } + log.Debugf("looked up hostname %s, received IP addresses [%s]", lr.Remote, sb.String()[2:]) + + // always use the first address + if len(ips) <= 0 { + return nil, fmt.Errorf("unable to resolve %s", lr.Remote) + } + ip = ips[0] + } + + parentIf := lr.ParentInterface + + if parentIf == "" { + conn, err := rtnl.Dial(nil) + if err != nil { + return nil, fmt.Errorf("can't establish netlink connection: %s", err) + } + defer conn.Close() + r, err := conn.RouteGet(ip) + if err != nil { + return nil, fmt.Errorf("failed to find a route to VxLAN remote address %s", ip.String()) + } + parentIf = r.Interface.Name + } + + // resolve remote endpoint + link.remoteEndpoint = NewEndpointVxlan(params.Nodes["host"], link) + link.remoteEndpoint.parentIface = parentIf + link.remoteEndpoint.udpPort = lr.UdpPort + link.remoteEndpoint.remote = ip + link.remoteEndpoint.vni = lr.Vni + link.remoteEndpoint.mac, err = utils.GenMac(ClabOUI) + if err != nil { + return nil, err + } + + // add link to local endpoints node + link.localEndpoint.GetNode().AddLink(link) + + return link, nil +} + +// resolveStitchedVEth creates the veth link and return it, the endpoint that is +// supposed to be stitched is returned seperately for further processing +func (lr *LinkVxlanRaw) resolveStitchedVEth(params *ResolveParams) (*LinkVEth, Endpoint, error) { + var err error + + veth := NewLinkVEth() + veth.LinkCommonParams = lr.LinkCommonParams + + hostEpRaw := &EndpointRaw{ + Node: "host", + Iface: fmt.Sprintf("ve-%s-%s", lr.Endpoint.Node, lr.Endpoint.Iface), + } + + hostEp, err := hostEpRaw.Resolve(params, veth) + if err != nil { + return nil, nil, err + } + + containerEpRaw := lr.Endpoint + + containerEp, err := containerEpRaw.Resolve(params, veth) + if err != nil { + return nil, nil, err + } + + veth.endpoints = append(veth.endpoints, hostEp, containerEp) + + return veth, hostEp, nil +} + +func (lr *LinkVxlanRaw) resolveStitched(params *ResolveParams) (Link, error) { + // prepare the vxlan struct + vxlanLink, err := lr.resolveStitchedVxlan(params) + if err != nil { + return nil, err + } + + // prepare the veth struct + vethLink, stitchEp, err := lr.resolveStitchedVEth(params) + if err != nil { + return nil, err + } + + // return the stitched vxlan link + stitchedLink := NewVxlanStitched(vxlanLink, vethLink, stitchEp) + + // add stitched link to node + params.Nodes[lr.Endpoint.Node].AddLink(stitchedLink) + + return stitchedLink, nil +} + +func (lr *LinkVxlanRaw) resolveRegular(params *ResolveParams) (Link, error) { var err error link := &LinkVxlan{ deploymentState: LinkDeploymentStateNotDeployed, LinkCommonParams: lr.LinkCommonParams, + noLearning: lr.NoLearning, + noL2Miss: lr.NoL2Miss, + noL3Miss: lr.NoL3Miss, } // resolve local Endpoint - link.LocalEndpoint, err = lr.Endpoint.Resolve(params, link) + link.localEndpoint, err = lr.Endpoint.Resolve(params, link) if err != nil { return nil, err } @@ -75,14 +230,14 @@ func (lr *LinkVxlanRaw) Resolve(params *ResolveParams) (Link, error) { } // resolve remote endpoint - link.RemoteEndpoint = NewEndpointVxlan(params.Nodes["host"], link) - link.RemoteEndpoint.parentIface = parentIf - link.RemoteEndpoint.udpPort = lr.UdpPort - link.RemoteEndpoint.remote = ip - link.RemoteEndpoint.vni = lr.Vni + link.remoteEndpoint = NewEndpointVxlan(params.Nodes["host"], link) + link.remoteEndpoint.parentIface = parentIf + link.remoteEndpoint.udpPort = lr.UdpPort + link.remoteEndpoint.remote = ip + link.remoteEndpoint.vni = lr.Vni // add link to local endpoints node - link.LocalEndpoint.GetNode().AddLink(link) + link.localEndpoint.GetNode().AddLink(link) return link, nil } @@ -93,16 +248,36 @@ func (lr *LinkVxlanRaw) GetType() LinkType { type LinkVxlan struct { LinkCommonParams - LocalEndpoint Endpoint - RemoteEndpoint *EndpointVxlan - + localEndpoint Endpoint + remoteEndpoint *EndpointVxlan + noLearning bool + noL2Miss bool + noL3Miss bool deploymentState LinkDeploymentState } func (l *LinkVxlan) Deploy(ctx context.Context) error { + err := l.deployVxlanInterface() + if err != nil { + return err + } + + // retrieve the Link by name + mvInterface, err := netlink.LinkByName(l.localEndpoint.GetRandIfaceName()) + if err != nil { + return fmt.Errorf("failed to lookup %q: %v", l.localEndpoint.GetRandIfaceName(), err) + } + // add the link to the Node Namespace + err = l.localEndpoint.GetNode().AddLinkToContainer(ctx, mvInterface, SetNameMACAndUpInterface(mvInterface, l.localEndpoint)) + return err + +} + +// deployVxlanInterface internal function to create the vxlan interface in the host namespace +func (l *LinkVxlan) deployVxlanInterface() error { // retrieve the parent interface netlink handle - parentIface, err := netlink.LinkByName(l.RemoteEndpoint.parentIface) + parentIface, err := netlink.LinkByName(l.remoteEndpoint.parentIface) if err != nil { return err } @@ -110,20 +285,20 @@ func (l *LinkVxlan) Deploy(ctx context.Context) error { // create the Vxlan struct vxlanconf := netlink.Vxlan{ LinkAttrs: netlink.LinkAttrs{ - Name: l.LocalEndpoint.GetRandIfaceName(), + Name: l.localEndpoint.GetRandIfaceName(), TxQLen: 1000, - HardwareAddr: l.RemoteEndpoint.mac, + HardwareAddr: l.remoteEndpoint.mac, }, - VxlanId: l.RemoteEndpoint.vni, + VxlanId: l.remoteEndpoint.vni, VtepDevIndex: parentIface.Attrs().Index, - Group: l.RemoteEndpoint.remote, - Learning: true, - L2miss: true, - L3miss: true, + Group: l.remoteEndpoint.remote, + Learning: !l.noLearning, // invert the value - we make use of the bool default value == false + L2miss: !l.noL2Miss, // invert the value + L3miss: !l.noL3Miss, // invert the value } // set the upd port if defined in the input - if l.RemoteEndpoint.udpPort != 0 { - vxlanconf.Port = l.RemoteEndpoint.udpPort + if l.remoteEndpoint.udpPort != 0 { + vxlanconf.Port = l.remoteEndpoint.udpPort } // define the MTU if defined in the input @@ -134,18 +309,9 @@ func (l *LinkVxlan) Deploy(ctx context.Context) error { // add the link err = netlink.LinkAdd(&vxlanconf) if err != nil { - return nil - } - - // retrieve the Link by name - mvInterface, err := netlink.LinkByName(l.LocalEndpoint.GetRandIfaceName()) - if err != nil { - return fmt.Errorf("failed to lookup %q: %v", l.LocalEndpoint.GetRandIfaceName(), err) + return err } - - // add the link to the Node Namespace - err = l.LocalEndpoint.GetNode().AddLinkToContainer(ctx, mvInterface, SetNameMACAndUpInterface(mvInterface, l.LocalEndpoint)) - return err + return nil } func (l *LinkVxlan) Remove(_ context.Context) error { @@ -154,7 +320,7 @@ func (l *LinkVxlan) Remove(_ context.Context) error { } func (l *LinkVxlan) GetEndpoints() []Endpoint { - return []Endpoint{l.LocalEndpoint, l.RemoteEndpoint} + return []Endpoint{l.localEndpoint, l.remoteEndpoint} } func (l *LinkVxlan) GetType() LinkType { diff --git a/links/link_vxlan_stitched.go b/links/link_vxlan_stitched.go new file mode 100644 index 000000000..35304345f --- /dev/null +++ b/links/link_vxlan_stitched.go @@ -0,0 +1,130 @@ +package links + +import ( + "context" + "fmt" + "os" + "syscall" + + log "github.com/sirupsen/logrus" + "github.com/vishvananda/netlink" +) + +type VxlanStitched struct { + vxlanLink *LinkVxlan + vethLink *LinkVEth + // the veth does not distinguist between endpoints. but we + // need to know which endpoint is the one used for + // stitching therefore we get that endpoint seperately + vethStitchEp Endpoint +} + +func NewVxlanStitched(vxlan *LinkVxlan, veth *LinkVEth, vethStitchEp Endpoint) *VxlanStitched { + // init the VxlanStitched struct + vxlanStitched := &VxlanStitched{ + vxlanLink: vxlan, + vethLink: veth, + vethStitchEp: vethStitchEp, + } + + return vxlanStitched +} + +func (l *VxlanStitched) Deploy(ctx context.Context) error { + err := l.vxlanLink.Deploy(ctx) + if err != nil { + return err + } + + err = l.vethLink.Deploy(ctx) + if err != nil { + return err + } + + err = stitch(l.vxlanLink.localEndpoint, l.vethStitchEp) + if err != nil { + return err + } + err = stitch(l.vethStitchEp, l.vxlanLink.localEndpoint) + if err != nil { + return err + } + + return nil +} + +func (l *VxlanStitched) Remove(_ context.Context) error { + // TODO + return nil +} + +func (l *VxlanStitched) GetEndpoints() []Endpoint { + return []Endpoint{l.vxlanLink.localEndpoint, l.vxlanLink.remoteEndpoint} +} + +func (l *VxlanStitched) GetType() LinkType { + return LinkTypeVxlanStitch +} + +func stitch(ep1, ep2 Endpoint) error { + + var err error + var linkSrc, linkDest netlink.Link + log.Infof("configuring ingress mirroring with tc in the direction of %s -> %s", ep1, ep2) + + if linkSrc, err = netlink.LinkByName(ep1.GetIfaceName()); err != nil { + return fmt.Errorf("failed to lookup %q: %v", + ep1, err) + } + + if linkDest, err = netlink.LinkByName(ep2.GetIfaceName()); err != nil { + return fmt.Errorf("failed to lookup %q: %v", + ep2, err) + } + + // tc qdisc add dev $SRC_IFACE ingress + qdisc := &netlink.Ingress{ + QdiscAttrs: netlink.QdiscAttrs{ + LinkIndex: linkSrc.Attrs().Index, + Handle: netlink.MakeHandle(0xffff, 0), + Parent: netlink.HANDLE_INGRESS, + }, + } + if err = netlink.QdiscAdd(qdisc); err != nil { + if !os.IsExist(err) { + return err + } + } + + // tc filter add dev $SRC_IFACE parent fffff: + // protocol all + // u32 match u32 0 0 + // action mirred egress mirror dev $DST_IFACE + filter := &netlink.U32{ + FilterAttrs: netlink.FilterAttrs{ + LinkIndex: linkSrc.Attrs().Index, + Parent: netlink.MakeHandle(0xffff, 0), + Protocol: syscall.ETH_P_ALL, + }, + Sel: &netlink.TcU32Sel{ + Keys: []netlink.TcU32Key{ + { + Mask: 0x0, + Val: 0, + }, + }, + Flags: netlink.TC_U32_TERMINAL, + }, + Actions: []netlink.Action{ + &netlink.MirredAction{ + ActionAttrs: netlink.ActionAttrs{ + Action: netlink.TC_ACT_PIPE, + }, + MirredAction: netlink.TCA_EGRESS_MIRROR, + Ifindex: linkDest.Attrs().Index, + }, + }, + } + + return netlink.FilterAdd(filter) +}