Skip to content

Commit

Permalink
Merge pull request loxilb-io#897 from TrekkieCoder/main
Browse files Browse the repository at this point in the history
PR - Support for unsolicited NDP advertisement for VIPs
  • Loading branch information
UltraInstinct14 authored Dec 3, 2024
2 parents a0010f7 + bbb0272 commit 5a430c8
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 18 deletions.
42 changes: 30 additions & 12 deletions pkg/loxinet/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -3134,7 +3134,7 @@ func (R *RuleH) AdvRuleVIPIfL2(IP net.IP, eIP net.IP, inst string) error {
inst = cmn.CIDefault
}

if IP.String() == "0.0.0.0" {
if IP.String() == "0.0.0.0" || IP.String() == "::" {
return nil
}

Expand All @@ -3143,10 +3143,14 @@ func (R *RuleH) AdvRuleVIPIfL2(IP net.IP, eIP net.IP, inst string) error {
dev := fmt.Sprintf("llb-rule-%s", IP.String())
ret, _ := R.zone.L3.IfaFindAddr(dev, IP)
if ret == 0 {
R.zone.L3.IfaDelete(dev, IP.String()+"/32")
R.zone.L3.IfaDelete(dev, utils.IPHostCIDRString(IP))
}
ev, _, iface := R.zone.L3.IfaSelectAny(IP, false)
if ev == 0 {
ifname := "lo"
if tk.IsNetIPv6(IP.String()) {
ifname = iface
}
if !utils.IsIPHostAddr(IP.String()) {
if mh.cloudHook != nil {
err := mh.cloudHook.CloudUpdatePrivateIP(IP, eIP, true)
Expand All @@ -3156,17 +3160,17 @@ func (R *RuleH) AdvRuleVIPIfL2(IP net.IP, eIP net.IP, inst string) error {
}
}

if loxinlp.AddAddrNoHook(IP.String()+"/32", "lo") != 0 {
tk.LogIt(tk.LogError, "lb-rule vip %s:%s add failed\n", IP.String(), "lo")
if loxinlp.AddAddrNoHook(utils.IPHostCIDRString(IP), ifname) != 0 {
tk.LogIt(tk.LogError, "lb-rule vip %s:%s add failed\n", IP.String(), ifname)
} else {
tk.LogIt(tk.LogInfo, "lb-rule vip %s:%s added\n", IP.String(), "lo")
tk.LogIt(tk.LogInfo, "lb-rule vip %s:%s added\n", IP.String(), ifname)
}
loxinlp.DelNeighNoHook(IP.String(), "")
}
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Millisecond)
defer cancel()
rCh := make(chan int)
go utils.GratArpReqWithCtx(ctx, rCh, IP, iface)
go utils.NetAdvertiseVIPReqWithCtx(ctx, rCh, IP, iface)
select {
case <-rCh:
break
Expand All @@ -3177,18 +3181,25 @@ func (R *RuleH) AdvRuleVIPIfL2(IP net.IP, eIP net.IP, inst string) error {

} else if ciState != "NOT_DEFINED" {
if utils.IsIPHostAddr(IP.String()) {
if loxinlp.DelAddrNoHook(IP.String()+"/32", "lo") != 0 {
tk.LogIt(tk.LogError, "lb-rule vip %s:%s delete failed\n", IP.String(), "lo")
ifname := "lo"
ev, _, iface := R.zone.L3.IfaSelectAny(IP, false)
if ev == 0 {
if tk.IsNetIPv6(IP.String()) {
ifname = iface
}
}
if loxinlp.DelAddrNoHook(utils.IPHostCIDRString(IP), ifname) != 0 {
tk.LogIt(tk.LogError, "lb-rule vip %s:%s delete failed\n", IP.String(), ifname)
} else {
tk.LogIt(tk.LogInfo, "lb-rule vip %s:%s deleted\n", IP.String(), "lo")
tk.LogIt(tk.LogInfo, "lb-rule vip %s:%s deleted\n", IP.String(), ifname)
}
}
} else {
if _, foundIP := R.zone.L3.IfaAddrLocal(IP); foundIP == nil {
dev := fmt.Sprintf("llb-rule-%s", IP.String())
ret, _ := R.zone.L3.IfaFindAddr(dev, IP)
if ret != 0 {
_, err := R.zone.L3.IfaAdd(dev, IP.String()+"/32")
_, err := R.zone.L3.IfaAdd(dev, utils.IPHostCIDRString(IP))
if err != nil {
fmt.Printf("Failed to add IP : %s:%s\n", dev, err)
}
Expand Down Expand Up @@ -3264,7 +3275,14 @@ func (R *RuleH) DeleteRuleVIP(VIP net.IP) {
xVIP = vipEnt.pVIP
}
if utils.IsIPHostAddr(xVIP.String()) {
loxinlp.DelAddrNoHook(xVIP.String()+"/32", "lo")
ifname := "lo"
ev, _, iface := R.zone.L3.IfaSelectAny(xVIP, false)
if ev == 0 {
if tk.IsNetIPv6(xVIP.String()) {
ifname = iface
}
}
loxinlp.DelAddrNoHook(utils.IPHostCIDRString(xVIP), ifname)
if mh.cloudHook != nil {
err := mh.cloudHook.CloudUpdatePrivateIP(xVIP, VIP, false)
if err != nil {
Expand All @@ -3275,7 +3293,7 @@ func (R *RuleH) DeleteRuleVIP(VIP net.IP) {
dev := fmt.Sprintf("llb-rule-%s", xVIP.String())
ret, _ := mh.zr.L3.IfaFindAddr(dev, xVIP)
if ret == 0 {
mh.zr.L3.IfaDelete(dev, xVIP.String()+"/32")
mh.zr.L3.IfaDelete(dev, utils.IPHostCIDRString(xVIP))
}
delete(R.vipMap, VIP.String())
}
Expand Down
174 changes: 168 additions & 6 deletions pkg/utils/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"crypto/x509"
"encoding/binary"
"errors"
"golang.org/x/sys/unix"
"io"
"net"
"net/http"
Expand All @@ -32,10 +31,156 @@ import (
"time"
"unsafe"

"golang.org/x/sys/unix"

tk "github.com/loxilb-io/loxilib"
nlp "github.com/vishvananda/netlink"
)

const (
ICMPv6NeighborAdvertisement = 136 // ICMPv6 Type for Neighbor Advertisement
)

type icmpv6Header struct {
Type uint8
Code uint8
Checksum uint16
Reserved uint32
}

func NetAdvertiseVI64Req(targetIP net.IP, ifName string) (int, error) {
if targetIP == nil || ifName == "" || ifName == "lo" {
return -1, errors.New("invalid parameters")
}

if !tk.IsNetIPv6(targetIP.String()) {
return -1, errors.New("invalid parameters")
}

ifi, err := net.InterfaceByName(ifName)
if err != nil {
return -1, errors.New("intfv6-err")
}

srcIP := net.IPv6linklocalallnodes
dstIP := net.IPv6linklocalallnodes

// Create an ICMPv6 header for Neighbor Advertisement
icmpHeader := &icmpv6Header{
Type: ICMPv6NeighborAdvertisement,
Code: 0,
Checksum: 0, // To be calculated later
Reserved: 0,
}

payload := newNeighborAdvertisementPayload(targetIP, ifi.HardwareAddr)
icmpData := append(icmpHeader.Marshal(), payload...)

// Calculate checksum
icmpHeader.Checksum = calculateChecksum(icmpData, srcIP, dstIP)
icmpData = append(icmpHeader.Marshal(), payload...)

fd, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_RAW, syscall.IPPROTO_ICMPV6)
if err != nil {
return -1, err
}
defer syscall.Close(fd)

if err := syscall.BindToDevice(fd, ifName); err != nil {
return -1, errors.New("bindv6-err")
}

dstAddr := &syscall.SockaddrInet6{
Port: 0,
Addr: [16]byte{},
}
copy(dstAddr.Addr[:], dstIP)

cmsgBuf := make([]byte, syscall.CmsgSpace(4)) // 4 bytes for hop limit
cmsg := (*syscall.Cmsghdr)(unsafe.Pointer(&cmsgBuf[0]))
cmsg.Level = syscall.IPPROTO_IPV6
cmsg.Type = syscall.IPV6_HOPLIMIT
cmsg.SetLen(syscall.CmsgLen(4))
*(*int32)(unsafe.Pointer(&cmsgBuf[syscall.CmsgLen(0)])) = 255

_, err = syscall.SendmsgN(fd, icmpData, cmsgBuf, dstAddr, 0)
if err != nil {
return -1, err
}

return 0, nil
}

func (h *icmpv6Header) Marshal() []byte {
buf := make([]byte, 8)
buf[0] = h.Type
buf[1] = h.Code
binary.BigEndian.PutUint16(buf[2:], h.Checksum)
binary.BigEndian.PutUint32(buf[4:], 0x20000000)
return buf
}

func newNeighborAdvertisementPayload(targetIP net.IP, macAddr net.HardwareAddr) []byte {
buf := make([]byte, 24)

targetIP = targetIP.To16()
if targetIP == nil {
panic("Invalid IPv6 address")
}

// Target Address
copy(buf[0:16], targetIP)

// Option: Target Link-Layer Address
buf[16] = 2 // Option Type
buf[17] = 1 // Length in units of 8 bytes
copy(buf[18:], macAddr)

return buf
}

// ConvertToSolicitedNodeMulticast converts an IPv6 address to its solicited-node multicast address
func ConvertToSolicitedNodeMulticast(ip net.IP) net.IP {

last24 := ip[len(ip)-3:] // Last 3 bytes of the IPv6 address

solicitedNode := net.IPv6unspecified
copy(solicitedNode[:], net.ParseIP("ff02::1:ff00:0")[:])
copy(solicitedNode[13:], last24)

return solicitedNode
}

func calculateChecksum(data []byte, srcIP, dstIP net.IP) uint16 {
pseudoHeader := createPseudoHeader(srcIP, dstIP, len(data))
fullPacket := append(pseudoHeader, data...)
return checksum(fullPacket)
}

func createPseudoHeader(srcIP, dstIP net.IP, length int) []byte {
buf := bytes.Buffer{}
buf.Write(srcIP.To16())
buf.Write(dstIP.To16())
buf.WriteByte(0)
buf.WriteByte(58) // Next Header (ICMPv6)
binary.Write(&buf, binary.BigEndian, uint32(length))
return buf.Bytes()
}

func checksum(data []byte) uint16 {
var sum uint32
for i := 0; i < len(data)-1; i += 2 {
sum += uint32(binary.BigEndian.Uint16(data[i:]))
}
if len(data)%2 != 0 {
sum += uint32(data[len(data)-1]) << 8
}
for sum > 0xFFFF {
sum = (sum >> 16) + (sum & 0xFFFF)
}
return ^uint16(sum)
}

// HTTPSProber - Do a https probe for given url
// returns true/false depending on whether probing was successful
func HTTPSProber(urls string, cert tls.Certificate, certPool *x509.CertPool, resp string) bool {
Expand Down Expand Up @@ -116,8 +261,8 @@ func IsIPHostNetAddr(ip net.IP) bool {
return false
}

// GratArpReq - sends a gratuitous arp reply given the DIP, SIP and interface name
func GratArpReq(AdvIP net.IP, ifName string) (int, error) {
// NetAdvertiseVIP4Req - sends a gratuitous arp reply given the DIP, SIP and interface name
func NetAdvertiseVIP4Req(AdvIP net.IP, ifName string) (int, error) {
bcAddr := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_DGRAM, int(tk.Htons(syscall.ETH_P_ARP)))
if err != nil {
Expand Down Expand Up @@ -177,14 +322,19 @@ func GratArpReq(AdvIP net.IP, ifName string) (int, error) {
return 0, nil
}

// GratArpReq - sends a gratuitous arp reply given the DIP, SIP and interface name
func GratArpReqWithCtx(ctx context.Context, rCh chan<- int, AdvIP net.IP, ifName string) (int, error) {
// NetAdvertiseVIPReqWithCtx - sends a gratuitous arp reply given the DIP interface name
func NetAdvertiseVIPReqWithCtx(ctx context.Context, rCh chan<- int, AdvIP net.IP, ifName string) (int, error) {
for {
select {
case <-ctx.Done():
return -1, ctx.Err()
default:
ret, _ := GratArpReq(AdvIP, ifName)
var ret int
if tk.IsNetIPv4(AdvIP.String()) {
ret, _ = NetAdvertiseVIP4Req(AdvIP, ifName)
} else {
ret, _ = NetAdvertiseVI64Req(AdvIP, ifName)
}
rCh <- ret
return 0, nil
}
Expand Down Expand Up @@ -356,3 +506,15 @@ func MkTunFsIfNotExist() error {
}
return nil
}

// sIPHostNetAddr - Check if provided address is a local subnet
func IPHostCIDRString(ip net.IP) string {
if ip == nil {
return "0.0.0.0/0"
}
if tk.IsNetIPv4(ip.String()) {
return ip.String() + "/32"
} else {
return ip.String() + "/128"
}
}

0 comments on commit 5a430c8

Please sign in to comment.