Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

garp: Introduce Gratuitous ARP Cell #25254

Merged
merged 1 commit into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ require (
github.com/kevinburke/ssh_config v1.2.0
github.com/kr/pretty v0.3.1
github.com/mattn/go-shellwords v1.0.12
github.com/mdlayher/arp v0.0.0-20220221190821-c37aaafac7f9
github.com/miekg/dns v1.1.43
github.com/mitchellh/mapstructure v1.5.0
github.com/onsi/ginkgo v1.16.5
Expand Down Expand Up @@ -200,12 +201,11 @@ require (
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mdlayher/arp v0.0.0-20191213142603-f72070a231fc // indirect
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 // indirect
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 // indirect
github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/ndp v0.0.0-20200602162440-17ab9e3e5567 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/raw v0.0.0-20210412142147-51b895745faf // indirect
github.com/mdlayher/packet v1.1.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
Expand Down
23 changes: 10 additions & 13 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

124 changes: 124 additions & 0 deletions pkg/datapath/garp/garp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

package garp

import (
"errors"
"fmt"
"net"
"net/netip"

"github.com/mdlayher/arp"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
"github.com/vishvananda/netlink"

"github.com/cilium/cilium/pkg/hive/cell"
"github.com/cilium/cilium/pkg/logging/logfields"
)

const (
// GARPInterface is the interface used to send Gratuitous ARP messages.
GARPInterface = "garp-interface"
)

var Cell = cell.Module(
"garp",
"GARP",

cell.Provide(newGARPSender),

// This cell can't have a default config, it's entirely env dependent.
cell.Config(Config{}),
)

// Config contains the configuration for the GARP cell.
type Config struct {
GARPInterface string
}

func (def Config) Flags(flags *pflag.FlagSet) {
flags.String(GARPInterface, def.GARPInterface, "Interface used for sending gratuitous arp messages")
}

type Sender interface {
Send(netip.Addr) error
markpash marked this conversation as resolved.
Show resolved Hide resolved
}

func newGARPSender(log logrus.FieldLogger, cfg Config) (Sender, error) {
if cfg.GARPInterface == "" {
return nil, errors.New("gratuitous arp sender interface undefined")
}

iface, err := interfaceByName(cfg.GARPInterface)
if err != nil {
return nil, fmt.Errorf("gratuitous arp sender interface %q not found: %w", cfg.GARPInterface, err)
}

l := log.WithField(logfields.Interface, iface.Name)
l.Info("initialised gratuitous arp sender")

return &sender{
logger: l,
iface: iface,
}, nil
}

type sender struct {
logger logrus.FieldLogger

iface *net.Interface
}

// Send implements Sender
func (s *sender) Send(ip netip.Addr) error {
err := send(s.iface, ip)
if err == nil {
s.logger.WithField(logfields.IPAddr, ip).Debug("sent gratuitous arp message")
}

return err
}

func SendOnInterface(ifaceName string, ip netip.Addr) error {
iface, err := interfaceByName(ifaceName)
if err != nil {
return fmt.Errorf("gratuitous arp sender interface %q not found: %w", ifaceName, err)
}

return send(iface, ip)
}

func send(iface *net.Interface, ip netip.Addr) error {
arpClient, err := arp.Dial(iface)
if err != nil {
return fmt.Errorf("failed to send gARP message: %w", err)
}
defer arpClient.Close()

if err := arpClient.Request(ip.AsSlice()); err != nil {
return fmt.Errorf("failed to send gARP message: %w", err)
}

return nil
}

// interfaceByName get *net.Interface by name using netlink.
//
// The reason not to use net.InterfaceByName directly is to avoid potential
// deadlocks (#15051).
func interfaceByName(name string) (*net.Interface, error) {
link, err := netlink.LinkByName(name)
if err != nil {
return nil, err
}

return &net.Interface{
Index: link.Attrs().Index,
MTU: link.Attrs().MTU,
Name: link.Attrs().Name,
Flags: link.Attrs().Flags,
HardwareAddr: link.Attrs().HardwareAddr,
}, nil
}
67 changes: 67 additions & 0 deletions pkg/datapath/garp/garp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

//go:build linux

package garp

import (
"errors"
"net/netip"
"testing"

"github.com/mdlayher/arp"
. "gopkg.in/check.v1"

"github.com/cilium/cilium/pkg/hive"
"github.com/cilium/cilium/pkg/hive/cell"
"github.com/cilium/cilium/pkg/testutils"
)

// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) {
TestingT(t)
}

type garpSuite struct{}

var _ = Suite(&garpSuite{})

func (s *garpSuite) TestGARPCell(c *C) {
testutils.PrivilegedCheck(c)

testIfaceName := "lo"
testGARPCell := func(garpSender Sender) error {
s, _ := garpSender.(*sender)
c.Assert(s, NotNil)
c.Assert(s.iface.Name, Equals, testIfaceName)

c.Logf("iface: %+v", s.iface)

// Here we just want to make sure that the Send method works,
// not that the gratuitous arp actually appears as expected. To
// do this, we can try to Send on loopback, and just check to
// see if we get the correct error from the underlying arp
// package.
err := garpSender.Send(netip.MustParseAddr("1.2.3.4"))
if err != nil && errors.Is(err, arp.ErrInvalidHardwareAddr) {
// We got the error we expected.
return nil
}

c.Fatal(err)
return nil
}

h := hive.New(cell.Module(
"test-garp-cell",
"TestGARPCell",
Cell,
cell.Invoke(testGARPCell),
))
hive.AddConfigOverride(h, func(cfg *Config) { cfg.GARPInterface = testIfaceName })

if err := h.Populate(); err != nil {
c.Fatalf("Failed to populate: %s", err)
}
}
14 changes: 6 additions & 8 deletions vendor/github.com/mdlayher/arp/client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions vendor/github.com/mdlayher/arp/fuzz.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 6 additions & 10 deletions vendor/github.com/mdlayher/ethernet/ethernet.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions vendor/github.com/mdlayher/ethernet/fuzz.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 6 additions & 8 deletions vendor/github.com/mdlayher/ethernet/vlan.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions vendor/github.com/mdlayher/packet/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.