1 
2 #include "protoDetour.h"
3 #include "protoSocket.h"
4 #include "protoRouteMgr.h"  // for ifAddr/ifIndex mapping
5 
6 #include <sys/types.h>
7 #include <sys/socket.h>
8 #include <netinet/in.h>
9 #include <string.h>
10 #include <arpa/inet.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <unistd.h>
14 #include <fcntl.h>  // for fcntl(), etc
15 
16 class BsdDetour : public ProtoDetour
17 {
18     public:
19         BsdDetour();
20         ~BsdDetour();
21 
22         bool Open(int hookFlags                     = 0,
23                   const ProtoAddress& srcFilterAddr = PROTO_ADDR_NONE,
24                   unsigned int        srcFilterMask = 0,
25                   const ProtoAddress& dstFilterAddr = PROTO_ADDR_NONE,
26                   unsigned int        dstFilterMask = 0,
27                   int                 dscpValue     = -1);
28         void Close();
29         bool Recv(char*         buffer,
30                   unsigned int& numBytes,
31                   Direction*    direction = NULL,
32                   ProtoAddress* srcMac = NULL,
33                   unsigned int* ifIndex = NULL);
34 
35         bool Allow(const char* buffer, unsigned int numBytes);
36         bool Drop();
37         bool Inject(const char* buffer, unsigned int numBytes);
38 
39         virtual bool SetMulticastInterface(const char* interfaceName);
40 
41         enum {DIVERT_PORT = 2998};
42 
43     private:
44         enum Action
45         {
46             INSTALL,
47             DELETE
48         };
49         bool SetIPFirewall(Action action,
50                            int hookFlags ,
51                            const ProtoAddress& srcFilterAddr,
52                            unsigned int        srcFilterMask,
53                            const ProtoAddress& dstFilterAddr,
54                            unsigned int        dstFilterMask);
55 
56         ProtoAddress            pkt_addr;  // iface IP addr for inbound, unspecified for outbound
57 
58         int                     domain;    // AF_INET or AF_INET6
59         int                     hook_flags;
60         ProtoAddress            src_filter_addr;
61         unsigned int            src_filter_mask;
62         ProtoAddress            dst_filter_addr;
63         unsigned int            dst_filter_mask;
64 
65         unsigned short          rule_number[3];
66         unsigned int            rule_count;
67         bool                    inject_only;
68 
69 
70         class AddressListItem : public ProtoTree::Item
71         {
72             public:
73                 AddressListItem(const ProtoAddress& addr, unsigned int ifIndex);
74 
GetInterfaceIndex() const75                 unsigned int GetInterfaceIndex() const
76                     {return if_index;}
77 
78                 // ProtoTree::Item overrides
GetKey() const79                 const char* GetKey() const
80                     {return if_addr.GetRawHostAddress();}
GetKeysize() const81                 unsigned int GetKeysize() const
82                     {return (if_addr.GetLength() << 3);}
83 
84             private:
85                 ProtoAddress    if_addr;
86                 unsigned int    if_index;
87         };
88 
89         ProtoTree               if_addr_list;  // for if_addr -> ifIndex lookup
90 
91 };  // end class BsdDetour
92 
Create()93 ProtoDetour* ProtoDetour::Create()
94 {
95     return static_cast<ProtoDetour*>(new BsdDetour());
96 }  // end ProtoDetour::Create(), (void*)nl_head
97 
AddressListItem(const ProtoAddress & ifAddr,unsigned int ifIndex)98 BsdDetour::AddressListItem::AddressListItem(const ProtoAddress& ifAddr, unsigned int ifIndex)
99  : if_addr(ifAddr), if_index(ifIndex)
100 {
101 }
102 
BsdDetour()103 BsdDetour::BsdDetour()
104  : hook_flags(0), rule_count(0), inject_only(false)
105 {
106     memset(&pkt_addr, 0, sizeof(struct sockaddr_storage));
107 }
108 
~BsdDetour()109 BsdDetour::~BsdDetour()
110 {
111     Close();
112 }
113 
SetIPFirewall(Action action,int hookFlags,const ProtoAddress & srcFilterAddr,unsigned int srcFilterMask,const ProtoAddress & dstFilterAddr,unsigned int dstFilterMask)114 bool BsdDetour::SetIPFirewall(Action              action,
115                               int                 hookFlags ,
116                               const ProtoAddress& srcFilterAddr,
117                               unsigned int        srcFilterMask,
118                               const ProtoAddress& dstFilterAddr,
119                               unsigned int        dstFilterMask)
120 {
121     // 1) IPv4 or IPv6 address family?
122     const char* cmd;
123     if (srcFilterAddr.GetType() != dstFilterAddr.GetType())
124     {
125         PLOG(PL_ERROR, "BsdDetour::SetIPFirewall() error: inconsistent src/dst filter addr families\n");
126         return false;
127     }
128     else if (ProtoAddress::IPv4 == srcFilterAddr.GetType())
129     {
130         cmd = "/sbin/ipfw";  // IPv4 ipfw
131     }
132     else if (ProtoAddress::IPv6 == srcFilterAddr.GetType())
133     {
134         cmd = "/sbin/ipfw"; // IPv6 ipfw (ipfw2 does both, in theory)
135     }
136     else
137     {
138         PLOG(PL_ERROR, "BsdDetour::SetIPFirewall() error: unspecified filter addr family\n");
139         return false;
140     }
141     // 2) For (DELETE == action) re-interpret hookFlags value as rule number
142     if (DELETE == action) hookFlags++;
143     // 3) For which firewall hooks?
144     while (0 != hookFlags)
145     {
146         // Make and install "iptables" firewall rules
147         const size_t RULE_MAX = 511;
148         char rule[RULE_MAX+1];
149 
150         if (INSTALL == action)
151         {
152             if (rule_count > 2)
153             {
154                 PLOG(PL_ERROR, "BsdDetour::SetIPFirewall() max ruleCount exceeded!\n");
155                 return false;
156             }
157 
158             const char* target;
159             if (0 != (hookFlags & OUTPUT))
160             {
161                 target = "out";
162                 hookFlags &= ~OUTPUT;
163             }
164             else if (0 != (hookFlags & INPUT))
165             {
166                 target = "in";
167                 hookFlags &= ~INPUT;
168             }
169             else if (0 != (hookFlags & FORWARD))
170             {
171                 target = "via any";
172                 hookFlags &= ~FORWARD;
173             }
174             else
175             {
176                 break;  // all flags have been processed
177             }
178 
179             // cmd  = "ipfw" or "ip6fw"
180             const char* f = (ProtoAddress::IPv4 == srcFilterAddr.GetType()) ?
181                     "ip" : "ipv6";
182             sprintf(rule, "%s add divert %hu %s ", cmd, (UINT16)DIVERT_PORT, f);
183             if (0 != srcFilterMask)
184             {
185                 strcat(rule, "from ");
186                 size_t len = strlen(rule);
187                 if (!srcFilterAddr.GetHostString(rule+len, RULE_MAX - len))
188                 {
189                     PLOG(PL_ERROR, "BsdDetour::SetIPFirewall() error: bad source addr filter\n");
190                     return false;
191                 }
192                 len = strlen(rule);
193                 sprintf(rule+len, "/%u ", srcFilterMask);
194             }
195             else
196             {
197                 size_t len = strlen(rule);
198                 sprintf(rule+len, "from any ");
199             }
200             if (0 != dstFilterMask)
201             {
202                 strcat(rule, "to ");
203                 size_t len = strlen(rule);
204                 if (!dstFilterAddr.GetHostString(rule+len, RULE_MAX - len))
205                 {
206                     PLOG(PL_ERROR, "BsdDetour::SetIPFirewall() error: bad destination addr filter\n");
207                     return false;
208                 }
209                 len = strlen(rule);
210                 sprintf(rule+len, "/%u ", dstFilterMask);
211             }
212             else
213             {
214                 size_t len = strlen(rule);
215                 sprintf(rule+len, "to any ");
216             }
217 
218             // target = "in", "out", or "via any"
219             strcat(rule, target);
220         }
221         else  // (DELETE == action)
222         {
223             sprintf(rule, "%s delete %d\n", cmd, hookFlags - 1);
224             hookFlags = 0;
225         }
226 
227         // Add redirection so we can get stderr result
228         strcat(rule, " 2>&1");
229 
230         FILE* p = popen(rule, "r");
231         if (NULL != p)
232         {
233             char feedback[256];
234             fread(feedback, 1, 256, p);
235             char* ptr = strchr(feedback, '\n');
236             if (NULL != ptr) *ptr = '\0';
237             feedback[255] = '\0';
238             if (0 == pclose(p))
239             {
240                 if (INSTALL == action)
241                 {
242                     // Add firewall rule number to list for delete on close
243                     if (1 != sscanf(feedback, "%05hu\n", rule_number+rule_count))
244                     {
245                         PLOG(PL_ERROR, "BsdDetour::SetIPFirewall() warning: couldn't record firewall rule number\n");
246                         return true;
247                     }
248                     rule_count++;
249                 }
250             }
251             else
252             {
253                 PLOG(PL_ERROR, "BsdDetour::SetIPFirewall() \"%s\" error: %s\n",
254                          rule, feedback);
255                 return false;
256             }
257         }
258         else
259         {
260             PLOG(PL_ERROR, "BsdDetour::SetIPFirewall() error: popen(%s): %s\n",
261                     rule, GetErrorString());
262             return false;
263         }
264     }  // end while (0 != hookFlags)
265     return true;
266 }  // end BsdDetour::SetIPFirewall()
267 
Open(int hookFlags,const ProtoAddress & srcFilterAddr,unsigned int srcFilterMask,const ProtoAddress & dstFilterAddr,unsigned int dstFilterMask,int)268 bool BsdDetour::Open(int                 hookFlags,
269                      const ProtoAddress& srcFilterAddr,
270                      unsigned int        srcFilterMask,
271                      const ProtoAddress& dstFilterAddr,
272                      unsigned int        dstFilterMask,
273                      int                 /*dscpValue*/)  // TBD - support DSCP
274 {
275     if (IsOpen()) Close();
276 
277     // Check for "inject-only" mode and "rewire appropriately
278     inject_only = false;
279     if (0 != (hookFlags & INJECT))
280     {
281         // 0) Open raw socket for optional packet injection use
282         //if (0 > (raw_fd = socket(domain, SOCK_RAW, IPPROTO_RAW)))
283         if (0 > (descriptor = socket(PF_INET, SOCK_RAW, IPPROTO_RAW)))  // or can we IPv6 on an AF_INET/HDRINCL socket?
284         {
285             PLOG(PL_ERROR, "BsdDetour::Open() socket(IPPROTO_RAW) error: %s\n",
286                     GetErrorString());
287             return false;
288         }
289         domain = AF_INET;
290         if (AF_INET == domain)
291         {
292             // Note: no IP_HDRINCL for IPv6 raw sockets ?
293             int enable = 1;
294             if (setsockopt(descriptor, IPPROTO_IP, IP_HDRINCL, &enable, sizeof(enable)))
295             {
296                 PLOG(PL_ERROR, "BsdDetour::Open() setsockopt(IP_HDRINCL) error: %s\n",
297                         GetErrorString());
298                 Close();
299                 return false;
300             }
301         }
302         ProtoDetour::Open();
303         StopInputNotification();
304 
305         // Set to non-blocking for our purposes (TBD) Add a SetBlocking() method
306         if(-1 == fcntl(descriptor, F_SETFL, fcntl(descriptor, F_GETFL, 0) | O_NONBLOCK))
307         {
308             PLOG(PL_ERROR, "BsdDetour::Open() fcntl(F_SETFL(O_NONBLOCK)) error: %s\n", GetErrorString());
309             Close();
310             return false;
311         }
312         inject_only = true;
313         return true;
314     }
315 
316 
317     if (srcFilterAddr.GetType() != dstFilterAddr.GetType())
318     {
319         PLOG(PL_ERROR, "BsdDetour::Open() error: inconsistent src/dst filter addr families\n");
320         return false;
321     }
322     else if (ProtoAddress::IPv4 == srcFilterAddr.GetType())
323 
324     {
325         domain = AF_INET;
326     }
327     else if (ProtoAddress::IPv6 == srcFilterAddr.GetType())
328     {
329         domain = AF_INET6;
330     }
331     else
332     {
333         PLOG(PL_ERROR, "BsdDetour::Open() error: unspecified filter addr family\n");
334         return false;
335     }
336 
337     // Setup ipfw rule(s) ...
338     // Save parameters for firewall rule removal
339     hook_flags = hookFlags;
340     src_filter_addr = srcFilterAddr;
341     src_filter_mask = srcFilterMask;
342     dst_filter_addr = dstFilterAddr;
343     dst_filter_mask = dstFilterMask;
344     if (0 != hookFlags)
345     {
346          if (!SetIPFirewall(INSTALL, hookFlags,
347                             srcFilterAddr, srcFilterMask,
348                             dstFilterAddr, dstFilterMask))
349         {
350             PLOG(PL_ERROR, "BsdDetour::Open() error: couldn't install firewall rules\n");
351             Close();
352             return false;
353         }
354     }
355 
356     // Open a divert socket ...
357     if ((descriptor = socket(domain, SOCK_RAW, IPPROTO_DIVERT)) < 0)
358     {
359         PLOG(PL_ERROR, "BsdDetour::Open() socket() error: %s\n", GetErrorString());
360         Close();
361         return false;
362     }
363 
364     // Bind to our ipfw divert "port"
365     UINT16 ipfwPort = DIVERT_PORT;  // (TBD) manage port numbers dynamically?
366     struct sockaddr_storage socketAddr;
367     socklen_t addrSize;
368     if (AF_INET == domain)
369     {
370         addrSize = sizeof(struct sockaddr_in);
371         memset((char*)&socketAddr, 0, sizeof(struct sockaddr_in));
372         ((struct sockaddr_in*)&socketAddr)->sin_family = AF_INET;
373         ((struct sockaddr_in*)&socketAddr)->sin_port = htons(ipfwPort);
374     }
375     else // if (AF_INET6 == domain)
376     {
377         addrSize = sizeof(struct sockaddr_in6);
378         memset((char*)&socketAddr, 0, sizeof(struct sockaddr_in6));
379         ((struct sockaddr_in6*)&socketAddr)->sin6_family = AF_INET6;
380         ((struct sockaddr_in6*)&socketAddr)->sin6_port = htons(ipfwPort);
381         ((struct sockaddr_in6*)&socketAddr)->sin6_flowinfo = 0;
382     }
383     if (bind(descriptor, (struct sockaddr*)&socketAddr, addrSize) < 0)
384     {
385         PLOG(PL_ERROR, "BsdDetour::Open() bind() error: %s\n", GetErrorString());
386         Close();
387         return false;
388     }
389 
390     if (!ProtoDetour::Open())
391     {
392         PLOG(PL_ERROR, "BsdDetour::Open() ProtoChannel::Open() error\n");
393         Close();
394         return false;
395     }
396 
397     // Set up ProtoAddressList for lookup of ifAddr -> ifIndex
398     ProtoRouteMgr* rtMgr = ProtoRouteMgr::Create();
399     if (NULL == rtMgr)
400     {
401         PLOG(PL_ERROR, "BsdDetour::Open(): ProtoRouteMgr::Create() error: %s\n", GetErrorString());
402         Close();
403         return false;
404     }
405     if (!rtMgr->Open())
406     {
407         PLOG(PL_ERROR, "BsdDetour::Open(): error: unable to open ProtoRouteMgr\n");
408         delete rtMgr;
409         Close();
410         return false;
411     }
412     unsigned int ifIndexArray[256];
413     unsigned int ifCount = ProtoSocket::GetInterfaceIndices(ifIndexArray, 256);
414     if (0 == ifCount)
415     {
416         PLOG(PL_ERROR, "BsdDetour::Open(): warning: no network interface indices were found.\n");
417     }
418     else if (ifCount > 256)
419     {
420         PLOG(PL_ERROR, "BsdDetour::Open(): warning: found network interfaces indices exceeding maximum count.\n");
421         ifCount = 256;
422     }
423     // Add any IP addrs assigned to this iface to our if_addr_list
424     for (unsigned int i = 0; i < ifCount; i++)
425     {
426         unsigned int ifIndex = ifIndexArray[i];
427         ProtoAddressList tempList;
428         if (!rtMgr->GetInterfaceAddressList(ifIndex, ProtoAddress::IPv4, tempList))
429         {
430             PLOG(PL_ERROR, "BsdDetour::Open() warning: couldn't retrieve IPv4 address for iface index: %d\n", ifIndex);
431         }
432         if (!rtMgr->GetInterfaceAddressList(ifIndex, ProtoAddress::IPv6, tempList))
433         {
434             PLOG(PL_ERROR, "BsdDetour::Open() warning: couldn't retrieve IPv6 address for iface index: %d\n", ifIndex);
435         }
436         ProtoAddressList::Iterator it(tempList);
437         ProtoAddress addr;
438         while (it.GetNextAddress(addr))
439         {
440             AddressListItem* ifItem = new AddressListItem(addr, ifIndex);
441             if (NULL == ifItem)
442             {
443                 PLOG(PL_ERROR, "BsdDetour::Open() new AddressListItem error: %s\n", GetErrorString());
444                 delete rtMgr;
445                 Close();
446                 return false;
447             }
448             if_addr_list.Insert(*ifItem);
449         }
450     }
451     delete rtMgr;
452     return true;
453 }  // end BsdDetour::Open()
454 
SetMulticastInterface(const char * interfaceName)455 bool BsdDetour::SetMulticastInterface(const char* interfaceName)
456 {
457     ASSERT(IsOpen());
458     if (NULL != interfaceName)
459     {
460         int result;
461 #ifdef HAVE_IPV6
462         if (AF_INET6 == domain)
463         {
464             unsigned int interfaceIndex = ProtoSocket::GetInterfaceIndex(interfaceName);
465             result = setsockopt(descriptor, IPPROTO_IPV6, IPV6_MULTICAST_IF,
466                                 (char*)&interfaceIndex, sizeof(interfaceIndex));
467         }
468         else
469 #endif // HAVE_IPV6
470         {
471             struct in_addr localAddr;
472             ProtoAddress interfaceAddress;
473             if (ProtoSocket::GetInterfaceAddress(interfaceName, ProtoAddress::IPv4, interfaceAddress))
474 			{
475                 localAddr.s_addr = htonl(interfaceAddress.IPv4GetAddress());
476             }
477             else
478             {
479                 PLOG(PL_ERROR, "BsdDetour::SetMulticastInterface() invalid interface name\n");
480                 return false;
481             }
482             result = setsockopt(descriptor, IPPROTO_IP, IP_MULTICAST_IF, (char*)&localAddr,
483                                 sizeof(localAddr));
484         }
485         if (result < 0)
486         {
487             PLOG(PL_ERROR, "BsdDetour: setsockopt(IP_MULTICAST_IF) error: %s\n",
488                      GetErrorString());
489             return false;
490         }
491     }
492     return true;
493 }  // end BsdDetour::SetMulticastInterface()
494 
Close()495 void BsdDetour::Close()
496 {
497     if (0 != hook_flags)
498     {
499         for (unsigned int i =0; i < rule_count; i++)
500             SetIPFirewall(DELETE, (int)rule_number[i],
501                           src_filter_addr, src_filter_mask,
502                           dst_filter_addr, dst_filter_mask);
503         rule_count = 0;
504         hook_flags = 0;
505     }
506     if (descriptor >= 0)
507     {
508         ProtoDetour::Close();
509         close(descriptor);
510         descriptor = INVALID_HANDLE;
511     }
512 }  // end BsdDetour::Close()
513 
Recv(char * buffer,unsigned int & numBytes,Direction * direction,ProtoAddress * srcMac,unsigned int * ifIndex)514 bool BsdDetour::Recv(char*          buffer,
515                      unsigned int&  numBytes,
516                      Direction*     direction,
517                      ProtoAddress*  srcMac,
518                      unsigned int*  ifIndex)
519 {
520     // (TBD) can BSD divert socket get src MAC addr?
521     // (It looks like we _could_ get a full MAC header if we
522     //  use a "layer 2" hook to capture packets in our
523     //  ipfw rule)
524     if (NULL != srcMac) srcMac->Invalidate();
525 
526     struct sockaddr_storage sockAddr;
527     socklen_t addrLen = sizeof(sockAddr);
528     ssize_t result = recvfrom(descriptor, buffer, numBytes, 0,
529                              (struct sockaddr*)&sockAddr, &addrLen);
530     if (result < 0)
531     {
532         pkt_addr.Invalidate();
533         switch (errno)
534         {
535             case EINTR:
536             case EAGAIN:
537                 numBytes = 0;
538                 if (NULL != direction)
539                     *direction = UNSPECIFIED;
540                 return true;
541             default:
542                 numBytes = 0;
543                 PLOG(PL_ERROR, "BsdDetour::Recv() recvfrom() error: %s\n", GetErrorString());
544                 return false;
545         }
546     }
547     else
548     {
549         pkt_addr.SetSockAddr(*((struct sockaddr*)&sockAddr));
550         numBytes = result;
551         if (NULL != direction)
552         {
553             if (pkt_addr.IsUnspecified())
554                 *direction = OUTBOUND;
555             else
556                 *direction = INBOUND;
557         }
558         if (NULL != ifIndex)
559         {
560             if (pkt_addr.IsUnspecified())
561             {
562                 *ifIndex = 0;
563             }
564             else
565             {
566                 // Lookup "ifIndex" for given interface IP address
567                 AddressListItem* ifItem =
568                     static_cast<AddressListItem*>(if_addr_list.Find(pkt_addr.GetRawHostAddress(), pkt_addr.GetLength() << 3));
569                 if (NULL != ifItem)
570                     *ifIndex = ifItem->GetInterfaceIndex();
571                 else
572                     *ifIndex = 0;
573             }
574         }
575         return true;
576     }
577 }  // end BsdDetour::Recv()
578 
Allow(const char * buffer,unsigned int numBytes)579 bool BsdDetour::Allow(const char* buffer, unsigned int numBytes)
580 {
581     if (pkt_addr.IsValid())
582     {
583         socklen_t addrSize = (ProtoAddress::IPv6 == pkt_addr.GetType()) ?
584                                 sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in);
585         ssize_t result = sendto(descriptor, buffer, (size_t)numBytes, 0,
586                                &pkt_addr.GetSockAddr(), addrSize);
587         if (result < 0)
588         {
589             PLOG(PL_ERROR, "BsdDetour::Allow() sendto() error: %s\n", GetErrorString());
590             return false;
591         }
592         pkt_addr.Invalidate();
593         return true;
594     }
595     else
596     {
597         PLOG(PL_ERROR, "BsdDetour::Allow() error: no packet pending\n");
598         return false;
599     }
600 }  // end BsdDetour::Allow()
601 
Drop()602 bool BsdDetour::Drop()
603 {
604     if (pkt_addr.IsValid())
605     {
606         pkt_addr.Invalidate();
607         return true;
608     }
609     else
610     {
611         PLOG(PL_ERROR, "BsdDetour::Drop() error: no packet pending\n");
612         return false;
613     }
614 }  // end BsdDetour::Drop()
615 
Inject(const char * buffer,unsigned int numBytes)616 bool BsdDetour::Inject(const char* buffer, unsigned int numBytes)
617 {
618     if (inject_only)
619     {
620         unsigned char version = buffer[0];
621         version >>= 4;
622         ProtoAddress dstAddr;
623         socklen_t addrLen;
624         if (4 == version)
625         {
626             dstAddr.SetRawHostAddress(ProtoAddress::IPv4, buffer+16, 4);
627             addrLen = sizeof(struct sockaddr);
628         }
629         else if (6 == version)
630         {
631             //PLOG(PL_ERROR, "BsdDetour::Inject() IPv6 injection not yet supported!\n");
632             dstAddr.SetRawHostAddress(ProtoAddress::IPv6, buffer+24, 16);
633             addrLen = sizeof(struct sockaddr_in6);
634             //return false;
635         }
636         else
637         {
638             PLOG(PL_ERROR, "BsdDetour::Inject() unknown IP version!\n");
639             return false;
640         }
641         size_t result = sendto(descriptor, buffer, numBytes, 0,
642                                &dstAddr.GetSockAddr(), addrLen);
643         if (result != numBytes)
644         {
645             PLOG(PL_ERROR, "BsdDetour::Inject() sendto() error: %s\n", GetErrorString());
646             return false;
647         }
648         return true;
649     }
650     else
651     {
652         size_t result = write(descriptor, buffer, (size_t)numBytes);
653         if (result != numBytes)
654         {
655             PLOG(PL_ERROR, "BsdDetour::Inject() write() error: %s\n", GetErrorString());
656             return false;
657         }
658     }
659     return true;
660 }  // end BsdDetour::Inject()
661 
662