]>
Commit | Line | Data |
---|---|---|
acddc0ed | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
4f52e9a6 QY |
2 | /* |
3 | * VRRP Neighbor Discovery. | |
4 | * Copyright (C) 2019 Cumulus Networks, Inc. | |
63d4bd12 QY |
5 | * Quentin Young |
6 | * | |
4f52e9a6 | 7 | * Portions: |
2fff50ec | 8 | * Copyright (C) 2001-2017 Alexandre Cassen |
4f52e9a6 QY |
9 | */ |
10 | #include <zebra.h> | |
11 | ||
12 | #include <linux/if_packet.h> | |
13 | #include <net/ethernet.h> | |
14 | #include <netinet/icmp6.h> | |
15 | #include <netinet/in.h> | |
16 | ||
17 | #include "lib/checksum.h" | |
18 | #include "lib/if.h" | |
19 | #include "lib/ipaddr.h" | |
20 | #include "lib/log.h" | |
21 | ||
b637bcd4 | 22 | #include "vrrp_debug.h" |
4f52e9a6 QY |
23 | #include "vrrp_ndisc.h" |
24 | ||
25 | #define VRRP_LOGPFX "[NDISC] " | |
26 | ||
27 | #define VRRP_NDISC_HOPLIMIT 255 | |
28 | #define VRRP_NDISC_SIZE \ | |
29 | ETHER_HDR_LEN + sizeof(struct ip6_hdr) \ | |
30 | + sizeof(struct nd_neighbor_advert) \ | |
31 | + sizeof(struct nd_opt_hdr) + ETH_ALEN | |
32 | ||
33 | /* static vars */ | |
34 | static int ndisc_fd = -1; | |
35 | ||
36 | /* | |
37 | * Build an unsolicited Neighbour Advertisement. | |
38 | * | |
39 | * ifp | |
40 | * Interface to send Neighbor Advertisement on | |
41 | * | |
42 | * ip | |
43 | * IP address to send Neighbor Advertisement for | |
44 | * | |
45 | * buf | |
46 | * Buffer to fill with IPv6 Neighbor Advertisement message. Includes | |
47 | * Ethernet header. | |
48 | * | |
49 | * bufsiz | |
50 | * Size of buf. | |
51 | * | |
52 | * Returns; | |
53 | * -1 if bufsiz is too small | |
54 | * 0 otherwise | |
55 | */ | |
56 | static int vrrp_ndisc_una_build(struct interface *ifp, struct ipaddr *ip, | |
354b49d6 | 57 | uint8_t *buf, size_t bufsiz) |
4f52e9a6 QY |
58 | { |
59 | if (bufsiz < VRRP_NDISC_SIZE) | |
60 | return -1; | |
61 | ||
62 | memset(buf, 0x00, bufsiz); | |
63 | ||
64 | struct ether_header *eth = (struct ether_header *)buf; | |
65 | struct ip6_hdr *ip6h = (struct ip6_hdr *)((char *)eth + ETHER_HDR_LEN); | |
66 | struct nd_neighbor_advert *ndh = | |
67 | (struct nd_neighbor_advert *)((char *)ip6h | |
68 | + sizeof(struct ip6_hdr)); | |
69 | struct icmp6_hdr *icmp6h = &ndh->nd_na_hdr; | |
70 | struct nd_opt_hdr *nd_opt_h = | |
71 | (struct nd_opt_hdr *)((char *)ndh | |
72 | + sizeof(struct nd_neighbor_advert)); | |
c4efd0f4 | 73 | char *nd_opt_lladdr = ((char *)nd_opt_h + sizeof(struct nd_opt_hdr)); |
4f52e9a6 QY |
74 | char *lladdr = (char *)ifp->hw_addr; |
75 | ||
76 | /* | |
77 | * An IPv6 packet with a multicast destination address DST, consisting | |
78 | * of the sixteen octets DST[1] through DST[16], is transmitted to the | |
79 | * Ethernet multicast address whose first two octets are the value 3333 | |
80 | * hexadecimal and whose last four octets are the last four octets of | |
81 | * DST. | |
82 | * - RFC2464.7 | |
83 | * | |
84 | * In this case we are sending to the all nodes multicast address, so | |
85 | * the last four octets are 0x00 0x00 0x00 0x01. | |
86 | */ | |
87 | memset(eth->ether_dhost, 0, ETH_ALEN); | |
88 | eth->ether_dhost[0] = 0x33; | |
89 | eth->ether_dhost[1] = 0x33; | |
90 | eth->ether_dhost[5] = 1; | |
91 | ||
92 | /* Set source Ethernet address to interface link layer address */ | |
93 | memcpy(eth->ether_shost, lladdr, ETH_ALEN); | |
94 | eth->ether_type = htons(ETHERTYPE_IPV6); | |
95 | ||
96 | /* IPv6 Header */ | |
97 | ip6h->ip6_vfc = 6 << 4; | |
98 | ip6h->ip6_plen = htons(sizeof(struct nd_neighbor_advert) | |
99 | + sizeof(struct nd_opt_hdr) + ETH_ALEN); | |
100 | ip6h->ip6_nxt = IPPROTO_ICMPV6; | |
101 | ip6h->ip6_hlim = VRRP_NDISC_HOPLIMIT; | |
102 | memcpy(&ip6h->ip6_src, &ip->ipaddr_v6, sizeof(struct in6_addr)); | |
103 | /* All nodes multicast address */ | |
104 | ip6h->ip6_dst.s6_addr[0] = 0xFF; | |
105 | ip6h->ip6_dst.s6_addr[1] = 0x02; | |
106 | ip6h->ip6_dst.s6_addr[15] = 0x01; | |
107 | ||
108 | /* ICMPv6 Header */ | |
109 | ndh->nd_na_type = ND_NEIGHBOR_ADVERT; | |
110 | ndh->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER; | |
111 | ndh->nd_na_flags_reserved |= ND_NA_FLAG_OVERRIDE; | |
112 | memcpy(&ndh->nd_na_target, &ip->ipaddr_v6, sizeof(struct in6_addr)); | |
113 | ||
114 | /* NDISC Option header */ | |
115 | nd_opt_h->nd_opt_type = ND_OPT_TARGET_LINKADDR; | |
116 | nd_opt_h->nd_opt_len = 1; | |
117 | memcpy(nd_opt_lladdr, lladdr, ETH_ALEN); | |
118 | ||
119 | /* Compute checksum */ | |
120 | uint32_t len = sizeof(struct nd_neighbor_advert) | |
121 | + sizeof(struct nd_opt_hdr) + ETH_ALEN; | |
122 | struct ipv6_ph ph = {}; | |
2fff50ec | 123 | |
4f52e9a6 QY |
124 | ph.src = ip6h->ip6_src; |
125 | ph.dst = ip6h->ip6_dst; | |
126 | ph.ulpl = htonl(len); | |
127 | ph.next_hdr = IPPROTO_ICMPV6; | |
ab059def QY |
128 | |
129 | /* Suppress static analysis warnings about accessing icmp6 oob */ | |
130 | void *offset = icmp6h; | |
131 | icmp6h->icmp6_cksum = in_cksum_with_ph6(&ph, offset, len); | |
4f52e9a6 QY |
132 | |
133 | return 0; | |
134 | } | |
135 | ||
136 | int vrrp_ndisc_una_send(struct vrrp_router *r, struct ipaddr *ip) | |
137 | { | |
138 | assert(r->family == AF_INET6); | |
139 | ||
140 | int ret = 0; | |
141 | struct interface *ifp = r->mvl_ifp; | |
4f52e9a6 | 142 | uint8_t buf[VRRP_NDISC_SIZE]; |
2fff50ec | 143 | |
4f52e9a6 QY |
144 | ret = vrrp_ndisc_una_build(ifp, ip, buf, sizeof(buf)); |
145 | ||
146 | if (ret == -1) | |
147 | return ret; | |
148 | ||
149 | struct sockaddr_ll sll; | |
150 | ssize_t len; | |
151 | ||
152 | /* Build the dst device */ | |
153 | memset(&sll, 0, sizeof(sll)); | |
154 | sll.sll_family = AF_PACKET; | |
155 | memcpy(sll.sll_addr, ifp->hw_addr, ETH_ALEN); | |
156 | sll.sll_halen = ETH_ALEN; | |
157 | sll.sll_ifindex = (int)ifp->ifindex; | |
158 | ||
159 | char ipbuf[INET6_ADDRSTRLEN]; | |
2fff50ec | 160 | |
4f52e9a6 QY |
161 | ipaddr2str(ip, ipbuf, sizeof(ipbuf)); |
162 | ||
b637bcd4 | 163 | DEBUGD(&vrrp_dbg_ndisc, |
613b45b0 | 164 | VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM |
b637bcd4 | 165 | "Sending unsolicited Neighbor Advertisement on %s for %s", |
613b45b0 | 166 | r->vr->vrid, family2str(r->family), ifp->name, ipbuf); |
b637bcd4 QY |
167 | |
168 | if (DEBUG_MODE_CHECK(&vrrp_dbg_ndisc, DEBUG_MODE_ALL) | |
169 | && DEBUG_MODE_CHECK(&vrrp_dbg_pkt, DEBUG_MODE_ALL)) | |
170 | zlog_hexdump(buf, VRRP_NDISC_SIZE); | |
4f52e9a6 QY |
171 | |
172 | len = sendto(ndisc_fd, buf, VRRP_NDISC_SIZE, 0, (struct sockaddr *)&sll, | |
173 | sizeof(sll)); | |
174 | ||
175 | if (len < 0) { | |
176 | zlog_err( | |
613b45b0 | 177 | VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM |
4f52e9a6 | 178 | "Error sending unsolicited Neighbor Advertisement on %s for %s", |
613b45b0 | 179 | r->vr->vrid, family2str(r->family), ifp->name, ipbuf); |
4f52e9a6 | 180 | ret = -1; |
6332c77f QY |
181 | } else { |
182 | ++r->stats.una_tx_cnt; | |
4f52e9a6 QY |
183 | } |
184 | ||
185 | return ret; | |
186 | } | |
187 | ||
188 | int vrrp_ndisc_una_send_all(struct vrrp_router *r) | |
189 | { | |
190 | assert(r->family == AF_INET6); | |
191 | ||
192 | struct listnode *ln; | |
193 | struct ipaddr *ip; | |
194 | ||
195 | for (ALL_LIST_ELEMENTS_RO(r->addrs, ln, ip)) | |
196 | vrrp_ndisc_una_send(r, ip); | |
197 | ||
198 | return 0; | |
199 | } | |
200 | ||
201 | void vrrp_ndisc_init(void) | |
202 | { | |
0cf6db21 | 203 | frr_with_privs(&vrrp_privs) { |
4f52e9a6 QY |
204 | ndisc_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IPV6)); |
205 | } | |
4f52e9a6 | 206 | |
b637bcd4 QY |
207 | if (ndisc_fd > 0) { |
208 | DEBUGD(&vrrp_dbg_sock, | |
209 | VRRP_LOGPFX "Initialized Neighbor Discovery socket"); | |
210 | DEBUGD(&vrrp_dbg_ndisc, | |
211 | VRRP_LOGPFX "Initialized Neighbor Discovery subsystem"); | |
212 | } else { | |
213 | zlog_err(VRRP_LOGPFX | |
214 | "Error initializing Neighbor Discovery socket"); | |
215 | } | |
4f52e9a6 QY |
216 | } |
217 | ||
218 | void vrrp_ndisc_fini(void) | |
219 | { | |
220 | close(ndisc_fd); | |
221 | ndisc_fd = -1; | |
b637bcd4 QY |
222 | |
223 | DEBUGD(&vrrp_dbg_ndisc, | |
224 | VRRP_LOGPFX "Deinitialized Neighbor Discovery subsystem"); | |
4f52e9a6 QY |
225 | } |
226 | ||
227 | bool vrrp_ndisc_is_init(void) | |
228 | { | |
229 | return ndisc_fd > 0; | |
230 | } |