]>
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)); | |
c4efd0f4 | 86 | char *nd_opt_lladdr = ((char *)nd_opt_h + sizeof(struct nd_opt_hdr)); |
4f52e9a6 QY |
87 | char *lladdr = (char *)ifp->hw_addr; |
88 | ||
89 | /* | |
90 | * An IPv6 packet with a multicast destination address DST, consisting | |
91 | * of the sixteen octets DST[1] through DST[16], is transmitted to the | |
92 | * Ethernet multicast address whose first two octets are the value 3333 | |
93 | * hexadecimal and whose last four octets are the last four octets of | |
94 | * DST. | |
95 | * - RFC2464.7 | |
96 | * | |
97 | * In this case we are sending to the all nodes multicast address, so | |
98 | * the last four octets are 0x00 0x00 0x00 0x01. | |
99 | */ | |
100 | memset(eth->ether_dhost, 0, ETH_ALEN); | |
101 | eth->ether_dhost[0] = 0x33; | |
102 | eth->ether_dhost[1] = 0x33; | |
103 | eth->ether_dhost[5] = 1; | |
104 | ||
105 | /* Set source Ethernet address to interface link layer address */ | |
106 | memcpy(eth->ether_shost, lladdr, ETH_ALEN); | |
107 | eth->ether_type = htons(ETHERTYPE_IPV6); | |
108 | ||
109 | /* IPv6 Header */ | |
110 | ip6h->ip6_vfc = 6 << 4; | |
111 | ip6h->ip6_plen = htons(sizeof(struct nd_neighbor_advert) | |
112 | + sizeof(struct nd_opt_hdr) + ETH_ALEN); | |
113 | ip6h->ip6_nxt = IPPROTO_ICMPV6; | |
114 | ip6h->ip6_hlim = VRRP_NDISC_HOPLIMIT; | |
115 | memcpy(&ip6h->ip6_src, &ip->ipaddr_v6, sizeof(struct in6_addr)); | |
116 | /* All nodes multicast address */ | |
117 | ip6h->ip6_dst.s6_addr[0] = 0xFF; | |
118 | ip6h->ip6_dst.s6_addr[1] = 0x02; | |
119 | ip6h->ip6_dst.s6_addr[15] = 0x01; | |
120 | ||
121 | /* ICMPv6 Header */ | |
122 | ndh->nd_na_type = ND_NEIGHBOR_ADVERT; | |
123 | ndh->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER; | |
124 | ndh->nd_na_flags_reserved |= ND_NA_FLAG_OVERRIDE; | |
125 | memcpy(&ndh->nd_na_target, &ip->ipaddr_v6, sizeof(struct in6_addr)); | |
126 | ||
127 | /* NDISC Option header */ | |
128 | nd_opt_h->nd_opt_type = ND_OPT_TARGET_LINKADDR; | |
129 | nd_opt_h->nd_opt_len = 1; | |
130 | memcpy(nd_opt_lladdr, lladdr, ETH_ALEN); | |
131 | ||
132 | /* Compute checksum */ | |
133 | uint32_t len = sizeof(struct nd_neighbor_advert) | |
134 | + sizeof(struct nd_opt_hdr) + ETH_ALEN; | |
135 | struct ipv6_ph ph = {}; | |
2fff50ec | 136 | |
4f52e9a6 QY |
137 | ph.src = ip6h->ip6_src; |
138 | ph.dst = ip6h->ip6_dst; | |
139 | ph.ulpl = htonl(len); | |
140 | ph.next_hdr = IPPROTO_ICMPV6; | |
ab059def QY |
141 | |
142 | /* Suppress static analysis warnings about accessing icmp6 oob */ | |
143 | void *offset = icmp6h; | |
144 | icmp6h->icmp6_cksum = in_cksum_with_ph6(&ph, offset, len); | |
4f52e9a6 QY |
145 | |
146 | return 0; | |
147 | } | |
148 | ||
149 | int vrrp_ndisc_una_send(struct vrrp_router *r, struct ipaddr *ip) | |
150 | { | |
151 | assert(r->family == AF_INET6); | |
152 | ||
153 | int ret = 0; | |
154 | struct interface *ifp = r->mvl_ifp; | |
4f52e9a6 | 155 | uint8_t buf[VRRP_NDISC_SIZE]; |
2fff50ec | 156 | |
4f52e9a6 QY |
157 | ret = vrrp_ndisc_una_build(ifp, ip, buf, sizeof(buf)); |
158 | ||
159 | if (ret == -1) | |
160 | return ret; | |
161 | ||
162 | struct sockaddr_ll sll; | |
163 | ssize_t len; | |
164 | ||
165 | /* Build the dst device */ | |
166 | memset(&sll, 0, sizeof(sll)); | |
167 | sll.sll_family = AF_PACKET; | |
168 | memcpy(sll.sll_addr, ifp->hw_addr, ETH_ALEN); | |
169 | sll.sll_halen = ETH_ALEN; | |
170 | sll.sll_ifindex = (int)ifp->ifindex; | |
171 | ||
172 | char ipbuf[INET6_ADDRSTRLEN]; | |
2fff50ec | 173 | |
4f52e9a6 QY |
174 | ipaddr2str(ip, ipbuf, sizeof(ipbuf)); |
175 | ||
b637bcd4 | 176 | DEBUGD(&vrrp_dbg_ndisc, |
613b45b0 | 177 | VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM |
b637bcd4 | 178 | "Sending unsolicited Neighbor Advertisement on %s for %s", |
613b45b0 | 179 | r->vr->vrid, family2str(r->family), ifp->name, ipbuf); |
b637bcd4 QY |
180 | |
181 | if (DEBUG_MODE_CHECK(&vrrp_dbg_ndisc, DEBUG_MODE_ALL) | |
182 | && DEBUG_MODE_CHECK(&vrrp_dbg_pkt, DEBUG_MODE_ALL)) | |
183 | zlog_hexdump(buf, VRRP_NDISC_SIZE); | |
4f52e9a6 QY |
184 | |
185 | len = sendto(ndisc_fd, buf, VRRP_NDISC_SIZE, 0, (struct sockaddr *)&sll, | |
186 | sizeof(sll)); | |
187 | ||
188 | if (len < 0) { | |
189 | zlog_err( | |
613b45b0 | 190 | VRRP_LOGPFX VRRP_LOGPFX_VRID VRRP_LOGPFX_FAM |
4f52e9a6 | 191 | "Error sending unsolicited Neighbor Advertisement on %s for %s", |
613b45b0 | 192 | r->vr->vrid, family2str(r->family), ifp->name, ipbuf); |
4f52e9a6 | 193 | ret = -1; |
6332c77f QY |
194 | } else { |
195 | ++r->stats.una_tx_cnt; | |
4f52e9a6 QY |
196 | } |
197 | ||
198 | return ret; | |
199 | } | |
200 | ||
201 | int vrrp_ndisc_una_send_all(struct vrrp_router *r) | |
202 | { | |
203 | assert(r->family == AF_INET6); | |
204 | ||
205 | struct listnode *ln; | |
206 | struct ipaddr *ip; | |
207 | ||
208 | for (ALL_LIST_ELEMENTS_RO(r->addrs, ln, ip)) | |
209 | vrrp_ndisc_una_send(r, ip); | |
210 | ||
211 | return 0; | |
212 | } | |
213 | ||
214 | void vrrp_ndisc_init(void) | |
215 | { | |
0cf6db21 | 216 | frr_with_privs(&vrrp_privs) { |
4f52e9a6 QY |
217 | ndisc_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IPV6)); |
218 | } | |
4f52e9a6 | 219 | |
b637bcd4 QY |
220 | if (ndisc_fd > 0) { |
221 | DEBUGD(&vrrp_dbg_sock, | |
222 | VRRP_LOGPFX "Initialized Neighbor Discovery socket"); | |
223 | DEBUGD(&vrrp_dbg_ndisc, | |
224 | VRRP_LOGPFX "Initialized Neighbor Discovery subsystem"); | |
225 | } else { | |
226 | zlog_err(VRRP_LOGPFX | |
227 | "Error initializing Neighbor Discovery socket"); | |
228 | } | |
4f52e9a6 QY |
229 | } |
230 | ||
231 | void vrrp_ndisc_fini(void) | |
232 | { | |
233 | close(ndisc_fd); | |
234 | ndisc_fd = -1; | |
b637bcd4 QY |
235 | |
236 | DEBUGD(&vrrp_dbg_ndisc, | |
237 | VRRP_LOGPFX "Deinitialized Neighbor Discovery subsystem"); | |
4f52e9a6 QY |
238 | } |
239 | ||
240 | bool vrrp_ndisc_is_init(void) | |
241 | { | |
242 | return ndisc_fd > 0; | |
243 | } |