-
Notifications
You must be signed in to change notification settings - Fork 386
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add NDP protocol interface for ipv6 support;
Signed-off-by: wenqiq <wenqiq@vmware.com>
- Loading branch information
Showing
2 changed files
with
434 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
Oops, something went wrong.