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