Files
2025-07-12 12:10:18 +08:00

528 lines
14 KiB
Go

// Copyright 2019 Yunion
//
// 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 icmp6
import (
"fmt"
"net"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"yunion.io/x/pkg/errors"
)
type TPreference int
const (
PreferenceLow TPreference = 0
PreferenceMedium TPreference = 1
PreferenceHigh TPreference = 2
)
func (pref TPreference) String() string {
switch pref {
case PreferenceLow:
return "low"
case PreferenceMedium:
return "medium"
case PreferenceHigh:
return "high"
}
return "unknown"
}
type SBaseICMP6Message struct {
SrcMac net.HardwareAddr
SrcIP net.IP
DstMac net.HardwareAddr
DstIP net.IP
Vlan uint16
}
func (msg SBaseICMP6Message) String() string {
var vlanStr string
if msg.Vlan > 1 {
vlanStr += fmt.Sprintf("(vlan %d)", msg.Vlan)
}
return fmt.Sprintf("%s[%s]->%s[%s]%s", msg.SrcIP.String(), msg.SrcMac.String(), msg.DstIP.String(), msg.DstMac.String(), vlanStr)
}
type IICMP6Message interface {
fmt.Stringer
Payload() gopacket.SerializableLayer
ICMP6TypeCode() layers.ICMPv6TypeCode
SourceMac() net.HardwareAddr
SourceIP() net.IP
DestinationMac() net.HardwareAddr
DestinationIP() net.IP
VlanId() uint16
}
func (msg SBaseICMP6Message) SourceIP() net.IP {
return msg.SrcIP
}
func (msg SBaseICMP6Message) SourceMac() net.HardwareAddr {
return msg.SrcMac
}
func (msg SBaseICMP6Message) DestinationIP() net.IP {
return msg.DstIP
}
func (msg SBaseICMP6Message) DestinationMac() net.HardwareAddr {
return msg.DstMac
}
func (msg SBaseICMP6Message) VlanId() uint16 {
return msg.Vlan
}
type SRouterSolicitation struct {
SBaseICMP6Message
}
type SPrefixInfoOption struct {
IsOnlink bool
IsAutoconf bool
Prefix net.IP
PrefixLen uint8
ValidLifetime uint32
PreferredLifetime uint32
}
func (opt SPrefixInfoOption) String() string {
return fmt.Sprintf("%s/%d (IsOnlink: %t, IsAutoconf: %t) ValidLifetime: %d, PreferredLifetime: %d", opt.Prefix.String(), opt.PrefixLen, opt.IsOnlink, opt.IsAutoconf, opt.ValidLifetime, opt.PreferredLifetime)
}
type SRouteInfoOption struct {
RouteLifetime uint32
Prefix net.IP
PrefixLen uint8
Preference TPreference
}
func (opt SRouteInfoOption) String() string {
return fmt.Sprintf("%s/%d (Pref: %s) RouteLifetime: %d", opt.Prefix.String(), opt.PrefixLen, opt.Preference.String(), opt.RouteLifetime)
}
type SRouterAdvertisement struct {
SBaseICMP6Message
CurHopLimit uint8
IsManaged bool
IsOther bool
IsHomeAgent bool
Preference TPreference
RouterLifetime uint16
ReachableTime uint32
RetransTimer uint32
MTU uint32
PrefixInfo []SPrefixInfoOption
RouteInfo []SRouteInfoOption
}
type SNeighborSolicitation struct {
SBaseICMP6Message
/*
The IP address of the target of the solicitation.
It MUST NOT be a multicast address.
*/
TargetAddr net.IP
}
type SNeighborAdvertisement struct {
SBaseICMP6Message
/*
For solicited advertisements, the Target Address
field in the Neighbor Solicitation message that
prompted this advertisement. For an unsolicited
advertisement, the address whose link-layer address
has changed. The Target Address MUST NOT be a
multicast address.
*/
TargetAddr net.IP
IsRouter bool
IsSolicited bool
}
func (msg SNeighborSolicitation) ICMP6TypeCode() layers.ICMPv6TypeCode {
return layers.CreateICMPv6TypeCode(layers.ICMPv6TypeNeighborSolicitation, 0)
}
func (msg SNeighborSolicitation) Payload() gopacket.SerializableLayer {
pkt := layers.ICMPv6NeighborSolicitation{}
pkt.TargetAddress = msg.TargetAddr
pkt.Options = layers.ICMPv6Options{
layers.ICMPv6Option{
Type: layers.ICMPv6OptSourceAddress,
Data: NewIcmpv6OptSourceTargetAddress(msg.SrcMac).Bytes(),
},
}
return &pkt
}
func (msg *SNeighborSolicitation) Unmarshal(data *layers.ICMPv6NeighborSolicitation) error {
msg.TargetAddr = data.TargetAddress
return nil
}
func (msg SNeighborSolicitation) String() string {
return fmt.Sprintf("NeighborSolicitation %s target %s", msg.SBaseICMP6Message.String(), msg.TargetAddr.String())
}
func (msg SNeighborAdvertisement) ICMP6TypeCode() layers.ICMPv6TypeCode {
return layers.CreateICMPv6TypeCode(layers.ICMPv6TypeNeighborAdvertisement, 0)
}
func (msg SNeighborAdvertisement) Payload() gopacket.SerializableLayer {
pkt := layers.ICMPv6NeighborAdvertisement{}
pkt.TargetAddress = msg.TargetAddr
pkt.Flags = 0x00
if msg.IsRouter {
pkt.Flags |= 0x80
}
if msg.IsSolicited {
pkt.Flags |= 0x40
}
pkt.Options = layers.ICMPv6Options{
layers.ICMPv6Option{
Type: layers.ICMPv6OptSourceAddress,
Data: NewIcmpv6OptSourceTargetAddress(msg.SrcMac).Bytes(),
},
}
return &pkt
}
func (msg *SNeighborAdvertisement) Unmarshal(data *layers.ICMPv6NeighborAdvertisement) error {
msg.TargetAddr = data.TargetAddress
msg.IsRouter = data.Flags&0x80 != 0
msg.IsSolicited = data.Flags&0x40 != 0
return nil
}
func (msg SNeighborAdvertisement) String() string {
return fmt.Sprintf("NeighborAdvertisement %s target %s", msg.SBaseICMP6Message.String(), msg.TargetAddr.String())
}
func (msg SRouterSolicitation) ICMP6TypeCode() layers.ICMPv6TypeCode {
return layers.CreateICMPv6TypeCode(layers.ICMPv6TypeRouterSolicitation, 0)
}
func (msg SRouterSolicitation) Payload() gopacket.SerializableLayer {
pkt := layers.ICMPv6RouterSolicitation{}
pkt.Options = layers.ICMPv6Options{
layers.ICMPv6Option{
Type: layers.ICMPv6OptSourceAddress,
Data: NewIcmpv6OptSourceTargetAddress(msg.SrcMac).Bytes(),
},
}
return &pkt
}
func (msg *SRouterSolicitation) Unmarshal(data *layers.ICMPv6RouterSolicitation) error {
return nil
}
func (msg SRouterSolicitation) String() string {
return fmt.Sprintf("RouterSolicitation %s", msg.SBaseICMP6Message.String())
}
func (msg SRouterAdvertisement) ICMP6TypeCode() layers.ICMPv6TypeCode {
return layers.CreateICMPv6TypeCode(layers.ICMPv6TypeRouterAdvertisement, 0)
}
func (msg SRouterAdvertisement) Payload() gopacket.SerializableLayer {
pkt := layers.ICMPv6RouterAdvertisement{}
// https://datatracker.ietf.org/doc/html/rfc4861#section-4.2
// https://datatracker.ietf.org/doc/html/rfc4191
pkt.Flags = 0x00 // M=1, O=1, stateful DHCPv6, pref=00
if msg.IsManaged {
pkt.Flags |= 0x80
}
if msg.IsOther {
pkt.Flags |= 0x40
}
if msg.IsHomeAgent {
pkt.Flags |= 0x20
}
switch msg.Preference {
case PreferenceLow:
pkt.Flags |= 0x18
case PreferenceMedium:
// do nothing
case PreferenceHigh:
pkt.Flags |= 0x08
}
pkt.HopLimit = msg.CurHopLimit
pkt.RouterLifetime = msg.RouterLifetime
pkt.ReachableTime = msg.ReachableTime
pkt.RetransTimer = msg.RetransTimer
pkt.Options = layers.ICMPv6Options{}
for i := range msg.PrefixInfo {
pref := msg.PrefixInfo[i]
pkt.Options = append(pkt.Options, layers.ICMPv6Option{
Type: layers.ICMPv6OptPrefixInfo,
Data: NewIcmpv6OptPrefixInfo(pref).Bytes(),
})
}
for i := range msg.RouteInfo {
rt := msg.RouteInfo[i]
pkt.Options = append(pkt.Options, layers.ICMPv6Option{
Type: 24,
Data: NewIcmpv6OptRouteInfo(rt).Bytes(),
})
}
if msg.MTU > 0 {
pkt.Options = append(pkt.Options, layers.ICMPv6Option{
Type: layers.ICMPv6OptMTU,
Data: NewIcmpV6OptMtu(msg.MTU).Bytes(),
})
}
pkt.Options = append(pkt.Options, layers.ICMPv6Option{
Type: layers.ICMPv6OptSourceAddress,
Data: NewIcmpv6OptSourceTargetAddress(msg.SrcMac).Bytes(),
})
return &pkt
}
func (msg *SRouterAdvertisement) Unmarshal(data *layers.ICMPv6RouterAdvertisement) error {
msg.CurHopLimit = data.HopLimit
msg.IsManaged = data.Flags&0x80 != 0
msg.IsOther = data.Flags&0x40 != 0
msg.IsHomeAgent = data.Flags&0x20 != 0
switch data.Flags & 0x18 {
case 0x18:
msg.Preference = PreferenceLow
case 0x08:
msg.Preference = PreferenceHigh
default:
msg.Preference = PreferenceMedium
}
msg.RouterLifetime = data.RouterLifetime
for i := range data.Options {
opt := data.Options[i]
switch opt.Type {
case layers.ICMPv6OptMTU:
msg.MTU = DecodeIcmpV6OptMtu(opt.Data)
case layers.ICMPv6OptPrefixInfo:
prefix := DecodeIcmpv6OptPrefixInfo(opt.Data)
msg.PrefixInfo = append(msg.PrefixInfo, prefix)
case 24:
route := DecodeIcmpv6OptRouteInfo(opt.Data)
msg.RouteInfo = append(msg.RouteInfo, route)
}
}
return nil
}
func (msg SRouterAdvertisement) String() string {
return fmt.Sprintf(`RouterAdvertisement %s
CurHopLimit: %d, IsManaged: %t, IsOther: %t, IsHomeAgent: %t, Preference: %s, RouterLifetime: %d, ReachableTime: %d, RetransTimer: %d
MTU: %d
PrefixInfo: %v
RouteInfo: %v)`,
msg.SBaseICMP6Message.String(),
msg.CurHopLimit, msg.IsManaged, msg.IsOther, msg.IsHomeAgent, msg.Preference.String(), msg.RouterLifetime, msg.ReachableTime, msg.RetransTimer,
msg.MTU, msg.PrefixInfo, msg.RouteInfo)
}
func EncodePacket(msg IICMP6Message) ([]byte, error) {
var pktLayers []gopacket.SerializableLayer
var eth = &layers.Ethernet{
EthernetType: layers.EthernetTypeIPv6,
SrcMAC: msg.SourceMac(),
DstMAC: msg.DestinationMac(),
}
pktLayers = append(pktLayers, eth)
if msg.VlanId() > 1 {
eth.EthernetType = layers.EthernetTypeDot1Q
dot1Q := &layers.Dot1Q{
VLANIdentifier: msg.VlanId(),
Priority: 6,
Type: layers.EthernetTypeIPv6,
}
pktLayers = append(pktLayers, dot1Q)
}
var ip = &layers.IPv6{
Version: 6,
HopLimit: 0xff,
TrafficClass: 0xc0,
SrcIP: msg.SourceIP(),
DstIP: msg.DestinationIP(),
NextHeader: layers.IPProtocolICMPv6,
}
pktLayers = append(pktLayers, ip)
var icmp6 = &layers.ICMPv6{
TypeCode: msg.ICMP6TypeCode(),
}
icmp6.SetNetworkLayerForChecksum(ip)
pktLayers = append(pktLayers, icmp6)
pktLayers = append(pktLayers, msg.Payload())
var (
buf = gopacket.NewSerializeBuffer()
opts = gopacket.SerializeOptions{ComputeChecksums: true, FixLengths: true}
)
if err := gopacket.SerializeLayers(buf, opts, pktLayers...); err != nil {
return nil, errors.Wrap(err, "SerializeLayers error")
}
return buf.Bytes(), nil
}
func DecodePacket(data []byte) (IICMP6Message, error) {
packet := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.Default)
if packet.ErrorLayer() != nil {
return nil, errors.Wrap(packet.ErrorLayer().Error(), "DecodePacket error")
}
var baseMsg SBaseICMP6Message
{
ethLayer := packet.Layer(layers.LayerTypeEthernet)
if ethLayer != nil {
eth := ethLayer.(*layers.Ethernet)
baseMsg.SrcMac = eth.SrcMAC
baseMsg.DstMac = eth.DstMAC
} else {
return nil, errors.Wrap(packet.ErrorLayer().Error(), "Expect Ethernet layer")
}
}
{
// optional vlan layer
dot1qLayer := packet.Layer(layers.LayerTypeDot1Q)
if dot1qLayer != nil {
dot1q := dot1qLayer.(*layers.Dot1Q)
baseMsg.Vlan = dot1q.VLANIdentifier
}
}
{
ipLayer := packet.Layer(layers.LayerTypeIPv6)
if ipLayer != nil {
// ipv6
ip6 := ipLayer.(*layers.IPv6)
baseMsg.SrcIP = ip6.SrcIP
baseMsg.DstIP = ip6.DstIP
} else {
return nil, errors.Wrap(packet.ErrorLayer().Error(), "Expect IPv6 packet")
}
}
{
icmpLayer := packet.Layer(layers.LayerTypeICMPv6)
if icmpLayer != nil {
icmp6 := icmpLayer.(*layers.ICMPv6)
if icmp6 == nil {
return nil, errors.Wrap(packet.ErrorLayer().Error(), "Expect IPv6 packet")
}
switch icmp6.TypeCode.Type() {
case 133:
payload := packet.Layer(layers.LayerTypeICMPv6RouterSolicitation)
if payload != nil {
msg := &SRouterSolicitation{
SBaseICMP6Message: baseMsg,
}
err := msg.Unmarshal(payload.(*layers.ICMPv6RouterSolicitation))
if err != nil {
return nil, errors.Wrap(err, "Unmarshal Router Solicitation packet")
}
return msg, nil
} else {
return nil, errors.Wrap(packet.ErrorLayer().Error(), "Expect ICMPv6 Router Solicitation packet")
}
case 134:
payload := packet.Layer(layers.LayerTypeICMPv6RouterAdvertisement)
if payload != nil {
msg := &SRouterAdvertisement{
SBaseICMP6Message: baseMsg,
}
err := msg.Unmarshal(payload.(*layers.ICMPv6RouterAdvertisement))
if err != nil {
return nil, errors.Wrap(err, "Unmarshal Router Advertisement packet")
}
return msg, nil
} else {
return nil, errors.Wrap(packet.ErrorLayer().Error(), "Expect ICMPv6 Router Advertisement packet")
}
case 135:
payload := packet.Layer(layers.LayerTypeICMPv6NeighborSolicitation)
if payload != nil {
msg := &SNeighborSolicitation{
SBaseICMP6Message: baseMsg,
}
err := msg.Unmarshal(payload.(*layers.ICMPv6NeighborSolicitation))
if err != nil {
return nil, errors.Wrap(err, "Unmarshal Neighbor Solicitation packet")
}
return msg, nil
} else {
return nil, errors.Wrap(packet.ErrorLayer().Error(), "Expect ICMPv6 Neighbor Solicitation packet")
}
case 136:
payload := packet.Layer(layers.LayerTypeICMPv6NeighborAdvertisement)
if payload != nil {
msg := &SNeighborAdvertisement{
SBaseICMP6Message: baseMsg,
}
err := msg.Unmarshal(payload.(*layers.ICMPv6NeighborAdvertisement))
if err != nil {
return nil, errors.Wrap(err, "Unmarshal Neighbor Advertisement packet")
}
return msg, nil
} else {
return nil, errors.Wrap(packet.ErrorLayer().Error(), "Expect ICMPv6 Neighbor Advertisement packet")
}
default:
return nil, errors.Wrap(packet.ErrorLayer().Error(), "Expect ICMPv6 packet")
}
} else {
return nil, errors.Wrap(packet.ErrorLayer().Error(), "Expect IPv6 packet")
}
}
}