Skip to content

Commit

Permalink
Add NDP protocol interface for ipv6 support;
Browse files Browse the repository at this point in the history
Signed-off-by: wenqiq <wenqiq@vmware.com>
  • Loading branch information
wenqiq committed Jun 14, 2021
1 parent 8424822 commit 9090de5
Show file tree
Hide file tree
Showing 2 changed files with 434 additions and 0 deletions.
299 changes: 299 additions & 0 deletions pkg/agent/util/ndp/ndp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
// Copyright 2021 Antrea Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ndp

import (
"fmt"
"io"
"net"

"golang.org/x/net/icmp"
"golang.org/x/net/ipv6"
"k8s.io/klog/v2"
)

const (
// The assumed NDP option length (in units of 8 bytes) for fixed length options.
llaOptLen = 1

// Length of a link-layer address for Ethernet networks.
ethAddrLen = 6

// Type values for each type of valid Option.
optSourceLLA = 1
optTargetLLA = 2

// Minimum byte length values for each type of valid Message.
naLen = 20

// HopLimit is the expected IPv6 hop limit for all NDP messages.
HopLimit = 255
)

type INDPAdvertiser interface {
NDPNeighborAdvertise(ip net.IP) error
WriteTo(msg NeighborAdvertisementMessage, cm *ipv6.ControlMessage, dst net.IP) error
Close() error
}

type Advertiser struct {
hardwareAdd net.HardwareAddr
iface *net.Interface
conn *ipv6.PacketConn
ipAddr *net.IPAddr
}

func NewNDPAdvertiser(iface *net.Interface) (INDPAdvertiser, error) {
addrs, err := iface.Addrs()
if err != nil {
return nil, fmt.Errorf("interface address error: %q", err.Error())
}

ipAddr := &net.IPAddr{}

for _, a := range addrs {
ipn, ok := a.(*net.IPNet)
if !ok {
continue
}

if err := checkIPv6(ipn.IP); err != nil {
continue
}

if ipn.IP.IsLinkLocalUnicast() {
ipAddr = &net.IPAddr{
IP: ipn.IP,
Zone: iface.Name,
}
}
}

ic, err := icmp.ListenPacket("ip6:ipv6-icmp", ipAddr.String())
if err != nil {
return nil, fmt.Errorf("listen icmp error: %q", err.Error())
}

pc := ic.IPv6PacketConn()

// Hop limit is always 255, per RFC 4861.
if err := pc.SetHopLimit(HopLimit); err != nil {
return nil, fmt.Errorf("ipv6 conn set hop limit error: %q", err.Error())
}
if err := pc.SetMulticastHopLimit(HopLimit); err != nil {
return nil, fmt.Errorf("ipv6 conn set multicast hoplimit error: %q", err.Error())
}

// Calculate and place ICMPv6 checksum at correct offset in all messages.
const chkOff = 2
if err := pc.SetChecksum(true, chkOff); err != nil {
return nil, fmt.Errorf("ipv6 conn set checksum error: %q", err.Error())
}

s := &Advertiser{
iface.HardwareAddr,
iface,
pc,
ipAddr,
}

return s, nil
}

// A linkLayerAddress is a Source or Target Link-Layer Address option, as
// described in RFC 4861, Section 4.6.1.
type linkLayerAddress struct {
Direction int
Addr net.HardwareAddr
}

func toOption(lla linkLayerAddress) (raw *Option, err error) {
if d := lla.Direction; d != optSourceLLA && d != optTargetLLA {
return nil, fmt.Errorf("invalid link-layer address direction: %d", d)
}

if len(lla.Addr) != ethAddrLen {
return nil, fmt.Errorf("invalid link-layer address: %q", lla.Addr.String())
}

raw = &Option{
Type: byte(lla.Direction),
Length: llaOptLen,
Value: lla.Addr,
}
return raw, nil
}

func (n *Advertiser) NDPNeighborAdvertise(ip net.IP) error {
dst := net.IPv6linklocalallnodes

lla := linkLayerAddress{
Direction: optTargetLLA,
Addr: n.hardwareAdd,
}
option, _ := toOption(lla)
msg := NeighborAdvertisementMessage{
Override: true,
TargetAddress: ip,
Options: []Option{
*option,
},
}
return n.WriteTo(msg, nil, dst)
}

type NeighborAdvertisementMessage struct {
Override bool
TargetAddress net.IP
Options []Option
}

// A Option is an Option in its raw and unprocessed format. Options which
// are not recognized by this package can be represented using a Option.
type Option struct {
Type uint8
Length uint8
Value []byte
}

func (r *Option) marshal() ([]byte, error) {
// Length specified in units of 8 bytes, and the caller must provide
// an accurate length.
l := int(r.Length * 8)
if 1+1+len(r.Value) != l {
return nil, io.ErrUnexpectedEOF
}

b := make([]byte, r.Length*8)
b[0] = r.Type
b[1] = r.Length

copy(b[2:], r.Value)

return b, nil
}

// marshalOptions marshals a slice of Options into a single byte slice.
func marshalOptions(options []Option) ([]byte, error) {
var b []byte
for _, o := range options {
ob, err := o.marshal()
if err != nil {
return nil, err
}

b = append(b, ob...)
}

return b, nil
}

func (msg *NeighborAdvertisementMessage) marshal() ([]byte, error) {
if err := checkIPv6(msg.TargetAddress); err != nil {
return nil, err
}

b := make([]byte, naLen)

if msg.Override {
b[0] |= 1 << 5
}

copy(b[4:], msg.TargetAddress)

ob, err := marshalOptions(msg.Options)
if err != nil {
return nil, err
}

b = append(b, ob...)

return b, nil
}

func marshalMessage(msg NeighborAdvertisementMessage) ([]byte, error) {
mb, err := msg.marshal()
if err != nil {
return nil, err
}

im := icmp.Message{
Type: ipv6.ICMPTypeNeighborAdvertisement,
// Always zero.
Code: 0,
// Calculated by caller or OS.
Checksum: 0,
Body: &icmp.RawBody{
Data: mb,
},
}

return im.Marshal(nil)
}

func (n *Advertiser) WriteTo(msg NeighborAdvertisementMessage, cm *ipv6.ControlMessage, dst net.IP) error {
b, err := marshalMessage(msg)
if err != nil {
return err
}

if cm == nil {
cm = &ipv6.ControlMessage{
HopLimit: HopLimit,
Src: n.ipAddr.IP,
IfIndex: n.iface.Index,
}
}

dstAddr := &net.IPAddr{
IP: dst,
Zone: n.iface.Name,
}

_, err = n.ipv6WriteTo(b, cm, dstAddr)
return err
}

func (a *Advertiser) ipv6WriteTo(b []byte, cm *ipv6.ControlMessage, dst net.Addr) (n int, err error) {
n, err = a.conn.WriteTo(b, cm, dst)
return
}

func (n *Advertiser) Close() error {
return n.conn.Close()
}

func checkIPv6(ip net.IP) error {
if ip.To16() == nil || ip.To4() != nil {
return fmt.Errorf("invalid IPv6 address: %s", ip.String())
}
return nil
}

func NDPNeighborAdvertiseOverIface(srcIP net.IP, iface *net.Interface) error {
if err := checkIPv6(srcIP); err != nil {
return err
}
na, err := NewNDPAdvertiser(iface)
if err != nil {
return fmt.Errorf("init ndp advertise interface error: %s", err.Error())
}
defer func() {
if err := na.Close(); err != nil {
klog.Errorf("Close icmp ipv6 connect error: %q", err.Error())
}
}()
return na.NDPNeighborAdvertise(srcIP)
}
Loading

0 comments on commit 9090de5

Please sign in to comment.