]> gitweb.fperrin.net Git - ndp6.git/blob - ndp6.c
Initial commit
[ndp6.git] / ndp6.c
1 #include <sys/socket.h>
2 #include <sys/types.h>
3
4 #include <arpa/inet.h>
5 #include <ifaddrs.h>
6 #include <net/ethernet.h>
7 #include <net/if_dl.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>
13
14 #include <err.h>
15 #include <stdarg.h>
16 #include <stddef.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <syslog.h>
21 #include <unistd.h>
22
23 void usage(void)
24 {
25         fprintf(stderr, "ndp6-pcap [-d] -i interface -p network/prefixlen...\n");
26         exit(1);
27 }
28
29 /*
30  * list of networks proxied by this programme
31  */
32 struct network {
33         struct in6_addr prefix;
34         int             plen;
35 } *proxiednets;
36 int nbnet = 0;
37
38 /*
39  * NA payload, always including the ether address of the proxying interface
40  */
41 struct adv {
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 */
46 } __packed adv;
47
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 */
51
52 int debug = 0;
53
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,
59               const u_char *frame);
60
61 void reply_ns(u_char *args,  const struct pcap_pkthdr *pkt,
62               const u_char *frame)
63 {
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);
70                 return;
71         }
72
73         struct ip6_hdr *ns_packet;
74         ns_packet = (struct ip6_hdr *)(frame +
75                                        sizeof(struct ether_header));
76
77         if (! IN6_IS_ADDR_LINKLOCAL(&ns_packet->ip6_src)) {
78                 trace(LOG_NOTICE, "received packet from non link-local sender");
79                 return;
80         }
81
82         struct nd_neighbor_solicit *ns;
83         ns = (struct nd_neighbor_solicit *)(frame + 
84                                             sizeof(struct ether_header) +
85                                             sizeof(struct ip6_hdr));
86
87         if (debug) {
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);
92         }
93
94         if (! proxied(&ns->nd_ns_target)) {
95                 trace(LOG_DEBUG, "target address is not to be proxied");
96                 return;
97         }
98
99         /* our reply */
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;
107
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;
113
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");
117         else
118                 trace(LOG_DEBUG, "reply sent");
119 }
120
121 uint8_t pl2m[8] = {
122         0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe
123 };
124 int samenet(struct in6_addr *a, struct in6_addr *b, int plen)
125 {
126         if (memcmp(a, b, plen / 8))
127                 return 0;
128         uint8_t m = pl2m[plen % 8];
129         return (a->s6_addr[plen / 8] & m) == (b->s6_addr[plen / 8] & m);
130 }
131
132 /* returns 1 if the address given is one for which we proxy */
133 int proxied(struct in6_addr *a)
134 {
135         for (int i = 0; ! IN6_IS_ADDR_UNSPECIFIED(&proxiednets[i].prefix); i++)
136                 if (samenet(a, &proxiednets[i].prefix, proxiednets[i].plen))
137                         return 1;
138         return 0;
139 }
140
141 int main(int argc, char **argv)
142 {
143         pcap_t *pcap;
144         struct bpf_program filter;
145         char errbuf[PCAP_ERRBUF_SIZE];
146         char *intfname = NULL;
147
148         int ch;
149         while ((ch = getopt(argc, argv, "di:p:")) != -1) {
150                 switch (ch) {
151                 case 'd': {
152                         debug = 1;
153                         break;
154                 }
155                 case 'i': {
156                         intfname = optarg;
157                         break;
158                 }
159                 case 'p': {
160                         char *netname = strdup(optarg);
161                         char *plena = strchr(netname, '/');
162                         if (! plena)
163                                 errx(1, "can't parse %s as a network name",
164                                      netname);
165                         *plena = '\0';
166                         plena++;
167                         nbnet++;
168                         proxiednets = realloc(proxiednets,
169                                               (nbnet+1) * sizeof(struct network));
170                         if (! proxiednets)
171                                 err(1, "realloc(proxiednets, %d)", nbnet);
172                         trace(LOG_INFO, "will proxy for %s/%s (%d)",
173                             netname, plena, nbnet);
174
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);
181
182                         free(netname);
183                         break;
184                 }
185                 default:
186                         warnx("unkown option -- %c", ch);
187                         usage();
188                         break;
189                 }
190         }
191         proxiednets[nbnet].prefix = in6addr_any;
192         proxiednets[nbnet].plen   = 0;
193
194         if (! intfname)
195                 errx(1, "no outside interface given");
196         if (nbnet == 0)
197                 errx(1, "no network to be proxied given");
198
199         if (! debug)
200                 setlogmask(LOG_UPTO(LOG_NOTICE));
201
202         /* get scope id for LL address, and the link address */
203         struct ifaddrs *headifa;
204         int scopefound = 0;
205         int lladdrfound = 0;
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))
210                         continue;
211                 if (! ifa->ifa_addr)
212                         continue;
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))
217                                 continue;
218                         myscope = getscope(sin6);
219                         scopefound = 1;
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)
225                                 continue;
226                         mylladdr = *((struct ether_addr *) LLADDR(sdl));
227                         lladdrfound = 1;
228                         trace(LOG_DEBUG, "ethernet address for %s is %s",
229                             intfname, ether_ntoa(&mylladdr));
230                 }
231         }
232         freeifaddrs(headifa);
233
234         if (! lladdrfound || ! scopefound)
235                 errx(1, "could not find scope and link address for %s",
236                         intfname);
237
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 */
244         int  hoplimit = 255;
245         if (setsockopt(sender, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
246                        (char *) &hoplimit, sizeof(hoplimit)) == -1)
247                 err(1, "set hoplimit");
248
249         /* setup pcap */
250         if (! (pcap = pcap_open_live(intfname, BUFSIZ, 1, 1, errbuf)))
251                 errx(1, "pcap_open_live: %s", errbuf);
252
253         /* pcap filter. we're looking at an offset of the ip6 header because
254          * pcap doesn't support looking at the icmp6 type */
255         char *filter_exp;
256         asprintf(&filter_exp, "icmp6 and ip6[%zd] = %d",
257                  sizeof(struct ip6_hdr) + offsetof(struct icmp6_hdr, icmp6_type),
258                  ND_NEIGHBOR_SOLICIT);
259         if (! filter_exp)
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));
265
266         /* fork and start pcap's event loop */
267         if (!debug && daemon(0, 0) < 0)
268                         err(1, "daemon");
269         pcap_loop(pcap, -1, reply_ns, NULL);
270
271         /* not reached */
272         return 0;
273 }
274
275 /*
276  * XXX: taken from ifconfig: FreeBSD doesn't return the scope id in the
277  * scope_id field, but embedded in the address itself 
278  *
279  * fixed in http://svnweb.freebsd.org/base?view=revision&revision=243187
280  */
281 uint16_t getscope(struct sockaddr_in6 *sin6)
282 {
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;
288         }
289         return sin6->sin6_scope_id;
290 }
291
292 void trace(int priority, char *message, ...)
293 {
294         va_list argp;
295         va_start(argp, message);
296         if (debug) {
297                 vfprintf(stderr, message, argp);
298                 fprintf(stderr, "\n");
299         } else {
300                 vsyslog(priority, message, argp);
301         }
302         va_end(argp);
303 }