1 #include <sys/socket.h>
6 #include <net/ethernet.h>
8 #include <net/if_types.h>
9 #include <netinet/in.h>
10 #include <netinet/ip6.h>
11 #include <netinet/icmp6.h>
12 #include <pcap/pcap.h>
25 fprintf(stderr, "ndp6-pcap [-d] -i interface -p network/prefixlen...\n");
30 * list of networks proxied by this programme
33 struct in6_addr prefix;
39 * NA payload, always including the ether address of the proxying interface
42 struct nd_neighbor_advert na;
43 struct nd_opt_hdr lladdr_opt;
44 struct ether_addr lladdr;
45 /* no padding: sizeof(nd_opt_hdr + ether_addr) % 8 == 0 */
48 struct ether_addr mylladdr; /* link-layer address of the outside interface */
49 uint32_t myscope; /* link-local scope of the outside interface */
50 int sender; /* socket for sending NA */
54 void trace(int level, char *message, ...);
55 uint16_t getscope(struct sockaddr_in6 *sin6);
56 int samenet(struct in6_addr *a, struct in6_addr *b, int plen);
57 int proxied(struct in6_addr *a);
58 void reply_ns(u_char *args, const struct pcap_pkthdr *pkt,
61 void reply_ns(u_char *args, const struct pcap_pkthdr *pkt,
64 int minsize = sizeof(struct ether_header) +
65 sizeof(struct ip6_hdr) +
66 sizeof(struct nd_neighbor_solicit);
67 if (pkt->caplen < minsize) {
68 trace(LOG_NOTICE, "received small packet: %d < %d\n",
69 pkt->caplen, minsize);
73 struct ip6_hdr *ns_packet;
74 ns_packet = (struct ip6_hdr *)(frame +
75 sizeof(struct ether_header));
77 if (! IN6_IS_ADDR_LINKLOCAL(&ns_packet->ip6_src)) {
78 trace(LOG_NOTICE, "received packet from non link-local sender");
82 struct nd_neighbor_solicit *ns;
83 ns = (struct nd_neighbor_solicit *)(frame +
84 sizeof(struct ether_header) +
85 sizeof(struct ip6_hdr));
88 char src[INET6_ADDRSTRLEN], tgt[INET6_ADDRSTRLEN];
89 inet_ntop(AF_INET6, &ns_packet->ip6_src, src, INET6_ADDRSTRLEN);
90 inet_ntop(AF_INET6, &ns->nd_ns_target, tgt, INET6_ADDRSTRLEN);
91 trace(LOG_DEBUG, "received packet from %s, target %s", src, tgt);
94 if (! proxied(&ns->nd_ns_target)) {
95 trace(LOG_DEBUG, "target address is not to be proxied");
100 bzero(&adv, sizeof(struct adv));
101 adv.na.nd_na_type = ND_NEIGHBOR_ADVERT;
102 adv.na.nd_na_flags_reserved = ND_NA_FLAG_SOLICITED;
103 adv.na.nd_na_target = ns->nd_ns_target;
104 adv.lladdr_opt.nd_opt_type = ND_OPT_TARGET_LINKADDR;
105 adv.lladdr_opt.nd_opt_len = 1; /* sizeof lladdr in octets */
106 adv.lladdr = mylladdr;
108 struct sockaddr_in6 dstsaddr;
109 bzero(&dstsaddr, sizeof(struct sockaddr_in6));
110 dstsaddr.sin6_family = AF_INET6;
111 dstsaddr.sin6_addr = ns_packet->ip6_src;
112 dstsaddr.sin6_scope_id = myscope;
114 if (sendto(sender, &adv, sizeof(struct adv), 0,
115 (struct sockaddr *)&dstsaddr, sizeof(struct sockaddr_in6)) < 0)
116 trace(LOG_ERR, "error sending reply: %m");
118 trace(LOG_DEBUG, "reply sent");
122 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe
124 int samenet(struct in6_addr *a, struct in6_addr *b, int plen)
126 if (memcmp(a, b, plen / 8))
128 uint8_t m = pl2m[plen % 8];
129 return (a->s6_addr[plen / 8] & m) == (b->s6_addr[plen / 8] & m);
132 /* returns 1 if the address given is one for which we proxy */
133 int proxied(struct in6_addr *a)
135 for (int i = 0; ! IN6_IS_ADDR_UNSPECIFIED(&proxiednets[i].prefix); i++)
136 if (samenet(a, &proxiednets[i].prefix, proxiednets[i].plen))
141 int main(int argc, char **argv)
144 struct bpf_program filter;
145 char errbuf[PCAP_ERRBUF_SIZE];
146 char *intfname = NULL;
149 while ((ch = getopt(argc, argv, "di:p:")) != -1) {
160 char *netname = strdup(optarg);
161 char *plena = strchr(netname, '/');
163 errx(1, "can't parse %s as a network name",
168 proxiednets = realloc(proxiednets,
169 (nbnet+1) * sizeof(struct network));
171 err(1, "realloc(proxiednets, %d)", nbnet);
172 trace(LOG_INFO, "will proxy for %s/%s (%d)",
173 netname, plena, nbnet);
175 struct network *net = &proxiednets[nbnet - 1];
176 if (inet_pton(AF_INET6, netname, &net->prefix) <= 0)
177 err(1, "could not parse %s", netname);
178 net->plen = atoi(plena);
179 if (net->plen < 0 || net->plen > 128)
180 err(1, "bad prefix length: %d", net->plen);
186 warnx("unkown option -- %c", ch);
191 proxiednets[nbnet].prefix = in6addr_any;
192 proxiednets[nbnet].plen = 0;
195 errx(1, "no outside interface given");
197 errx(1, "no network to be proxied given");
200 setlogmask(LOG_UPTO(LOG_NOTICE));
202 /* get scope id for LL address, and the link address */
203 struct ifaddrs *headifa;
206 if (getifaddrs(&headifa) < 0)
207 err(1, "getifaddrs");
208 for (struct ifaddrs *ifa = headifa; ifa; ifa = ifa->ifa_next) {
209 if (strcmp(ifa->ifa_name, intfname))
213 if (ifa->ifa_addr->sa_family == AF_INET6) {
214 struct sockaddr_in6 *sin6 =
215 (struct sockaddr_in6 *)ifa->ifa_addr;
216 if (! IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
218 myscope = getscope(sin6);
220 trace(LOG_DEBUG, "scope for %s is %d", intfname, myscope);
221 } else if (ifa->ifa_addr->sa_family == AF_LINK) {
222 struct sockaddr_dl *sdl =
223 (struct sockaddr_dl *)ifa->ifa_addr;
224 if (sdl->sdl_type != IFT_ETHER)
226 mylladdr = *((struct ether_addr *) LLADDR(sdl));
228 trace(LOG_DEBUG, "ethernet address for %s is %s",
229 intfname, ether_ntoa(&mylladdr));
232 freeifaddrs(headifa);
234 if (! lladdrfound || ! scopefound)
235 errx(1, "could not find scope and link address for %s",
238 /* setup the sending socket for the NA replies */
239 if ((sender = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0)
240 err(1, "creating socket");
241 if (shutdown(sender, SHUT_RD) < 0)
242 err(1, "half-closing the sending socket");
243 /* the NA we send are link-local */
245 if (setsockopt(sender, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
246 (char *) &hoplimit, sizeof(hoplimit)) == -1)
247 err(1, "set hoplimit");
250 if (! (pcap = pcap_open_live(intfname, BUFSIZ, 1, 1, errbuf)))
251 errx(1, "pcap_open_live: %s", errbuf);
253 /* pcap filter. we're looking at an offset of the ip6 header because
254 * pcap doesn't support looking at the icmp6 type */
256 asprintf(&filter_exp, "icmp6 and ip6[%zd] = %d",
257 sizeof(struct ip6_hdr) + offsetof(struct icmp6_hdr, icmp6_type),
258 ND_NEIGHBOR_SOLICIT);
260 err(1, "creating filter");
261 if (pcap_compile(pcap, &filter, filter_exp, 1, 0) < 0)
262 errx(1, "pcap_compile: %s", pcap_geterr(pcap));
263 if (pcap_setfilter(pcap, &filter) < 0)
264 errx(1, "pcap_setfilter: %s", pcap_geterr(pcap));
266 /* fork and start pcap's event loop */
267 if (!debug && daemon(0, 0) < 0)
269 pcap_loop(pcap, -1, reply_ns, NULL);
276 * XXX: taken from ifconfig: FreeBSD doesn't return the scope id in the
277 * scope_id field, but embedded in the address itself
279 * fixed in http://svnweb.freebsd.org/base?view=revision&revision=243187
281 uint16_t getscope(struct sockaddr_in6 *sin6)
283 if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) &&
284 sin6->sin6_scope_id == 0) {
285 sin6->sin6_scope_id =
286 ntohs(*(uint16_t *)&sin6->sin6_addr.s6_addr[2]);
287 sin6->sin6_addr.s6_addr[2] = sin6->sin6_addr.s6_addr[3] = 0;
289 return sin6->sin6_scope_id;
292 void trace(int priority, char *message, ...)
295 va_start(argp, message);
297 vfprintf(stderr, message, argp);
298 fprintf(stderr, "\n");
300 vsyslog(priority, message, argp);