github.com/cilium/cilium@v1.16.2/pkg/multicast/multicast.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package multicast 5 6 import ( 7 "net" 8 "net/netip" 9 10 "github.com/vishvananda/netlink" 11 "golang.org/x/net/ipv6" 12 13 "github.com/cilium/cilium/pkg/lock" 14 "github.com/cilium/cilium/pkg/logging" 15 "github.com/cilium/cilium/pkg/logging/logfields" 16 ) 17 18 var ( 19 // v6Socket is the udp socket used to join/leave a multicast group 20 v6Socket net.PacketConn 21 22 mutex lock.Mutex 23 24 log = logging.DefaultLogger.WithField(logfields.LogSubsys, "multicast") 25 ) 26 27 // Pre-Defined Multicast Addresses 28 // as defined in https://tools.ietf.org/html/rfc4291#section-2.7.1 29 30 var ( 31 // AllNodesIfcLocalMaddr is the multicast address that identifies the group of 32 // all IPv6 nodes, within scope 1 (interface-local) 33 AllNodesIfcLocalMaddr net.IP = net.ParseIP("ff01::1") 34 35 // AllNodesLinkLocalMaddr is the multicast address that identifies the group of 36 // all IPv6 nodes, within scope 2 (link-local) 37 AllNodesLinkLocalMaddr net.IP = net.ParseIP("ff02::1") 38 39 // AllRoutersIfcLocalMaddr is the multicast address that identifies the group of 40 // all IPv6 routers, within scope 1 (interface-local) 41 AllRoutersIfcLocalMaddr net.IP = net.ParseIP("ff01::2") 42 43 // AllRoutersLinkLocalMaddr is the multicast address that identifies the group of 44 // all IPv6 routers, within scope 2 (link-local) 45 AllRoutersLinkLocalMaddr net.IP = net.ParseIP("ff02::2") 46 47 // AllRoutersSiteLocalMaddr is the multicast address that identifies the group of 48 // all IPv6 routers, within scope 5 (site-local) 49 AllRoutersSiteLocalMaddr net.IP = net.ParseIP("ff05::2") 50 51 // SolicitedNodeMaddrPrefix is the prefix of the multicast address that is used 52 // as part of NDP 53 SolicitedNodeMaddrPrefix net.IP = net.ParseIP("ff02::1:ff00:0") 54 ) 55 56 func initSocket() error { 57 mutex.Lock() 58 defer mutex.Unlock() 59 60 if v6Socket != nil { 61 return nil 62 } 63 64 c, err := net.ListenPacket("udp6", "[::]:0") 65 if err != nil { 66 log.WithError(err).Warn("Failed to listen on socket for multicast") 67 return err 68 } 69 70 v6Socket = c 71 return nil 72 } 73 74 // JoinGroup joins the group address group on the interface ifc 75 func JoinGroup(ifc string, ip netip.Addr) error { 76 if err := initSocket(); err != nil { 77 return err 78 } 79 80 dev, err := interfaceByName(ifc) 81 if err != nil { 82 return err 83 } 84 85 return ipv6.NewPacketConn(v6Socket).JoinGroup(dev, &net.UDPAddr{IP: ip.AsSlice()}) 86 } 87 88 // LeaveGroup leaves the group address group on the interface ifc 89 func LeaveGroup(ifc string, ip netip.Addr) error { 90 if err := initSocket(); err != nil { 91 return err 92 } 93 94 dev, err := interfaceByName(ifc) 95 if err != nil { 96 return err 97 } 98 99 return ipv6.NewPacketConn(v6Socket).LeaveGroup(dev, &net.UDPAddr{IP: ip.AsSlice()}) 100 } 101 102 // ListGroup lists multicast addresses on the interface ifc 103 func ListGroup(ifc string) ([]net.Addr, error) { 104 dev, err := interfaceByName(ifc) 105 if err != nil { 106 return nil, err 107 } 108 109 return dev.MulticastAddrs() 110 } 111 112 // IsInGroup tells if interface ifc belongs to group represented by maddr 113 func IsInGroup(ifc string, maddr netip.Addr) (bool, error) { 114 ips, err := ListGroup(ifc) 115 if err != nil { 116 return false, err 117 } 118 119 maddrStr := maddr.String() 120 for _, gip := range ips { 121 if gip.String() == maddrStr { 122 return true, nil 123 } 124 } 125 126 return false, nil 127 } 128 129 // Address encapsulates the functionality to generate solicated node multicast address 130 type Address netip.Addr 131 132 // Key takes the last 3 bytes of endpoint's IPv6 address and compile them in to 133 // an int32 value as key of the endpoint. It assumes the input is a valid IPv6 address. 134 // Otherwise it returns 0 (https://tools.ietf.org/html/rfc4291#section-2.7.1) 135 func (a Address) Key() int32 { 136 ipv6 := netip.Addr(a) 137 if !ipv6.IsValid() { 138 return 0 139 } 140 141 var key int32 142 for _, v := range ipv6.AsSlice()[13:] { 143 key <<= 8 144 key += int32(v) 145 } 146 147 return key 148 } 149 150 // SolicitedNodeMaddr returns solicited node multicast address 151 func (a Address) SolicitedNodeMaddr() netip.Addr { 152 ipv6 := netip.Addr(a) 153 if !ipv6.IsValid() { 154 return netip.Addr{} 155 } 156 157 maddr := make([]byte, 16) 158 copy(maddr[:13], SolicitedNodeMaddrPrefix[:13]) 159 copy(maddr[13:], ipv6.AsSlice()[13:]) 160 161 // Use slice-to-array-pointer conversion, available since Go 1.17. 162 // TODO: use slice-to-array conversion when switching to Go 1.20 163 return netip.AddrFrom16(*(*[16]byte)(maddr)) 164 } 165 166 // interfaceByName get *net.Interface by name using netlink. 167 // 168 // The reason not to use net.InterfaceByName directly is to avoid potential 169 // deadlocks (#15051). 170 func interfaceByName(name string) (*net.Interface, error) { 171 link, err := netlink.LinkByName(name) 172 if err != nil { 173 return nil, err 174 } 175 176 return &net.Interface{ 177 Index: link.Attrs().Index, 178 MTU: link.Attrs().MTU, 179 Name: link.Attrs().Name, 180 Flags: link.Attrs().Flags, 181 HardwareAddr: link.Attrs().HardwareAddr, 182 }, nil 183 }