From 4838cbc350a7608ebe345a821ef32bb01a8aeca7 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Wed, 2 Sep 2015 14:32:01 -0700 Subject: Unix domain sockets in Phy<> --- osdep/Phy.hpp | 187 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 174 insertions(+), 13 deletions(-) (limited to 'osdep') diff --git a/osdep/Phy.hpp b/osdep/Phy.hpp index 2ea68b9d..eca4109f 100644 --- a/osdep/Phy.hpp +++ b/osdep/Phy.hpp @@ -93,6 +93,10 @@ typedef void PhySocket; * phyOnTcpClose(PhySocket *sock,void **uptr) * phyOnTcpData(PhySocket *sock,void **uptr,void *data,unsigned long len) * phyOnTcpWritable(PhySocket *sock,void **uptr) + * phyOnUnixAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN) + * phyOnUnixClose(PhySocket *sock,void **uptr) + * phyOnUnixData(PhySocket *sock,void **uptr,void *data,unsigned long len) + * phyOnUnixWritable(PhySocket *sock,void **uptr) * * These templates typically refer to function objects. Templates are used to * avoid the call overhead of indirection, which is surprisingly high for high @@ -105,6 +109,9 @@ typedef void PhySocket; * uptr: sockL and uptrL for the listen socket, and sockN and uptrN for * the new TCP connection socket that has just been created. * + * Note that phyOnUnix*() are only required and will only be used on systems + * that support Unix domain sockets. + * * Handlers are always called. On outgoing TCP connection, CONNECT is always * called on either success or failure followed by DATA and/or WRITABLE as * indicated. On socket close, handlers are called unless close() is told @@ -129,7 +136,9 @@ private: ZT_PHY_SOCKET_TCP_IN = 0x03, ZT_PHY_SOCKET_TCP_LISTEN = 0x04, ZT_PHY_SOCKET_RAW = 0x05, - ZT_PHY_SOCKET_UDP = 0x06 + ZT_PHY_SOCKET_UDP = 0x06, + ZT_PHY_SOCKET_UNIX_IN = 0x07, + ZT_PHY_SOCKET_UNIX_LISTEN = 0x08 }; struct PhySocketImpl @@ -358,6 +367,64 @@ public: #endif } +#ifdef __UNIX_LIKE__ + /** + * Listen for connections on a Unix domain socket + * + * @param path Path to Unix domain socket + * @param uptr Arbitrary pointer to associate + * @return PhySocket or NULL if cannot bind + */ + inline PhySocket *unixListen(const char *path,void *uptr = (void *)0) + { + struct sockaddr_un sun; + + if (_socks.size() >= ZT_PHY_MAX_SOCKETS) + return (PhySocket *)0; + + memset(&sun,0,sizeof(sun)); + sun.sun_family = AF_UNIX; + if (strlen(path) >= sizeof(sun.sun_path)) + return (PhySocket *)0; + strcpy(sun.sun_path,path); + + ZT_PHY_SOCKFD_TYPE s = ::socket(PF_UNIX,SOCK_STREAM,0); + if (!ZT_PHY_SOCKFD_VALID(s)) + return (PhySocket *)0; + + ::fcntl(s,F_SETFL,O_NONBLOCK); + + ::unlink(path); + if (::bind(s,(struct sockaddr *)&sun,sizeof(struct sockaddr_un)) != 0) { + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } + if (::listen(s,128) != 0) { + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } + + try { + _socks.push_back(PhySocketImpl()); + } catch ( ... ) { + ZT_PHY_CLOSE_SOCKET(s); + return (PhySocket *)0; + } + PhySocketImpl &sws = _socks.back(); + + if ((long)s > _nfds) + _nfds = (long)s; + FD_SET(s,&_readfds); + sws.type = ZT_PHY_SOCKET_UNIX_LISTEN; + sws.sock = s; + sws.uptr = uptr; + memset(&(sws.saddr),0,sizeof(struct sockaddr_storage)); + memcpy(&(sws.saddr),&sun,sizeof(struct sockaddr_un)); + + return (PhySocket *)&sws; + } +#endif // __UNIX_LIKE__ + /** * Bind a local listen socket to listen for new TCP connections * @@ -573,6 +640,45 @@ public: return n; } +#ifdef __UNIX_LIKE__ + /** + * Attempt to send data to a Unix domain socket connection (non-blocking) + * + * If -1 is returned, the socket should no longer be used as it is now + * destroyed. If callCloseHandler is true, the close handler will be + * called before the function returns. + * + * @param sock An open Unix socket (other socket types will fail) + * @param data Data to send + * @param len Length of data + * @param callCloseHandler If true, call close handler on socket closing failure condition (default: true) + * @return Number of bytes actually sent or -1 on fatal error (socket closure) + */ + inline long unixSend(PhySocket *sock,const void *data,unsigned long len,bool callCloseHandler = true) + { + PhySocketImpl &sws = *(reinterpret_cast(sock)); + long n = (long)::write(sws.sock,data,len); + if (n < 0) { + switch(errno) { +#ifdef EAGAIN + case EAGAIN: +#endif +#if defined(EWOULDBLOCK) && ( !defined(EAGAIN) || (EWOULDBLOCK != EAGAIN) ) + case EWOULDBLOCK: +#endif +#ifdef EINTR + case EINTR: +#endif + return 0; + default: + this->close(sock,callCloseHandler); + return -1; + } + } + return n; + } +#endif // __UNIX_LIKE__ + /** * Set whether we want to be notified via the TCP writability handler when a socket is writable * @@ -727,6 +833,56 @@ public: } break; + case ZT_PHY_SOCKET_UNIX_IN: { +#ifdef __UNIX_LIKE__ + ZT_PHY_SOCKFD_TYPE sock = s->sock; // if closed, s->sock becomes invalid as s is no longer dereferencable + if (FD_ISSET(sock,&rfds)) { + long n = (long)::read(sock,buf,sizeof(buf)); + if (n <= 0) { + this->close((PhySocket *)&(*s),true); + } else { + try { + _handler->phyOnUnixData((PhySocket *)&(*s),&(s->uptr),(void *)buf,(unsigned long)n); + } catch ( ... ) {} + } + } + if ((FD_ISSET(sock,&wfds))&&(FD_ISSET(sock,&_writefds))) { + try { + _handler->phyOnUnixWritable((PhySocket *)&(*s),&(s->uptr)); + } catch ( ... ) {} + } +#endif // __UNIX_LIKE__ + } break; + + case ZT_PHY_SOCKET_UNIX_LISTEN: +#ifdef __UNIX_LIKE__ + if (FD_ISSET(s->sock,&rfds)) { + memset(&ss,0,sizeof(ss)); + socklen_t slen = sizeof(ss); + ZT_PHY_SOCKFD_TYPE newSock = ::accept(s->sock,(struct sockaddr *)&ss,&slen); + if (ZT_PHY_SOCKFD_VALID(newSock)) { + if (_socks.size() >= ZT_PHY_MAX_SOCKETS) { + ZT_PHY_CLOSE_SOCKET(newSock); + } else { + fcntl(newSock,F_SETFL,O_NONBLOCK); + _socks.push_back(PhySocketImpl()); + PhySocketImpl &sws = _socks.back(); + FD_SET(newSock,&_readfds); + if ((long)newSock > _nfds) + _nfds = (long)newSock; + sws.type = ZT_PHY_SOCKET_UNIX_IN; + sws.sock = newSock; + sws.uptr = (void *)0; + memcpy(&(sws.saddr),&ss,sizeof(struct sockaddr_storage)); + try { + _handler->phyOnUnixAccept((PhySocket *)&(*s),(PhySocket *)&(_socks.back()),&(s->uptr),&(sws.uptr)); + } catch ( ... ) {} + } + } + } +#endif // __UNIX_LIKE__ + break; + default: break; @@ -758,24 +914,29 @@ public: ZT_PHY_CLOSE_SOCKET(sws.sock); - switch(sws.type) { - case ZT_PHY_SOCKET_TCP_OUT_PENDING: - if (callHandlers) { + if (callHandlers) { + switch(sws.type) { + case ZT_PHY_SOCKET_TCP_OUT_PENDING: try { _handler->phyOnTcpConnect(sock,&(sws.uptr),false); } catch ( ... ) {} - } - break; - case ZT_PHY_SOCKET_TCP_OUT_CONNECTED: - case ZT_PHY_SOCKET_TCP_IN: - if (callHandlers) { + break; + case ZT_PHY_SOCKET_TCP_OUT_CONNECTED: + case ZT_PHY_SOCKET_TCP_IN: try { _handler->phyOnTcpClose(sock,&(sws.uptr)); } catch ( ... ) {} - } - break; - default: - break; + break; + case ZT_PHY_SOCKET_UNIX_IN: +#ifdef __UNIX_LIKE__ + try { + _handler->phyOnUnixClose(sock,&(sws.uptr)); + } catch ( ... ) {} +#endif // __UNIX_LIKE__ + break; + default: + break; + } } // Causes entry to be deleted from list in poll(), ignored elsewhere -- cgit v1.2.3 From 9a723be263d9307b2bf9d2efca3db9e8c12e6a92 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 10 Sep 2015 13:18:57 -0700 Subject: Add socketpair support to Phy. --- osdep/Phy.hpp | 115 +++++++++++++++++++++++++++++++++++++++++++++++-- selftest.cpp | 3 ++ service/OneService.cpp | 3 ++ 3 files changed, 117 insertions(+), 4 deletions(-) (limited to 'osdep') diff --git a/osdep/Phy.hpp b/osdep/Phy.hpp index eca4109f..1a07fa0a 100644 --- a/osdep/Phy.hpp +++ b/osdep/Phy.hpp @@ -46,6 +46,7 @@ #define ZT_PHY_SOCKFD_VALID(s) ((s) != INVALID_SOCKET) #define ZT_PHY_CLOSE_SOCKET(s) ::closesocket(s) #define ZT_PHY_MAX_SOCKETS (FD_SETSIZE) +#define ZT_PHY_MAX_INTERCEPTS ZT_PHY_MAX_SOCKETS #define ZT_PHY_SOCKADDR_STORAGE_TYPE struct sockaddr_storage #else // not Windows @@ -58,6 +59,7 @@ #include #include #include +#include #include #include #include @@ -67,8 +69,14 @@ #define ZT_PHY_SOCKFD_VALID(s) ((s) > -1) #define ZT_PHY_CLOSE_SOCKET(s) ::close(s) #define ZT_PHY_MAX_SOCKETS (FD_SETSIZE) +#define ZT_PHY_MAX_INTERCEPTS ZT_PHY_MAX_SOCKETS #define ZT_PHY_SOCKADDR_STORAGE_TYPE struct sockaddr_storage +#if defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) +#define ZT_PHY_HAVE_EVENTFD 1 +#include +#endif + #endif // Windows or not namespace ZeroTier { @@ -87,16 +95,24 @@ typedef void PhySocket; * This class is templated on a pointer to a handler class which must * implement the following functions: * + * For all platforms: + * * phyOnDatagram(PhySocket *sock,void **uptr,const struct sockaddr *from,void *data,unsigned long len) * phyOnTcpConnect(PhySocket *sock,void **uptr,bool success) * phyOnTcpAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN,const struct sockaddr *from) * phyOnTcpClose(PhySocket *sock,void **uptr) * phyOnTcpData(PhySocket *sock,void **uptr,void *data,unsigned long len) * phyOnTcpWritable(PhySocket *sock,void **uptr) + * + * On Linux/OSX/Unix only (not required/used on Windows or elsewhere): + * * phyOnUnixAccept(PhySocket *sockL,PhySocket *sockN,void **uptrL,void **uptrN) * phyOnUnixClose(PhySocket *sock,void **uptr) * phyOnUnixData(PhySocket *sock,void **uptr,void *data,unsigned long len) * phyOnUnixWritable(PhySocket *sock,void **uptr) + * phyOnSocketPairEndpointClose(PhySocket *sock,void **uptr) + * phyOnSocketPairEndpointData(PhySocket *sock,void **uptr,void *data,unsigned long len) + * phyOnSocketPairEndpointWritable(PhySocket *sock,void **uptr) * * These templates typically refer to function objects. Templates are used to * avoid the call overhead of indirection, which is surprisingly high for high @@ -109,9 +125,6 @@ typedef void PhySocket; * uptr: sockL and uptrL for the listen socket, and sockN and uptrN for * the new TCP connection socket that has just been created. * - * Note that phyOnUnix*() are only required and will only be used on systems - * that support Unix domain sockets. - * * Handlers are always called. On outgoing TCP connection, CONNECT is always * called on either success or failure followed by DATA and/or WRITABLE as * indicated. On socket close, handlers are called unless close() is told @@ -138,7 +151,8 @@ private: ZT_PHY_SOCKET_RAW = 0x05, ZT_PHY_SOCKET_UDP = 0x06, ZT_PHY_SOCKET_UNIX_IN = 0x07, - ZT_PHY_SOCKET_UNIX_LISTEN = 0x08 + ZT_PHY_SOCKET_UNIX_LISTEN = 0x08, + ZT_PHY_SOCKET_PAIR_ENDPOINT = 0x09 }; struct PhySocketImpl @@ -226,8 +240,17 @@ public: ZT_PHY_CLOSE_SOCKET(_whackSendSocket); } + /** + * @param s Socket object + * @return Underlying OS-type (usually int or long) file descriptor associated with object + */ + static inline ZT_PHY_SOCKFD_TYPE getDescriptor(PhySocket *s) throw() { return reinterpret_cast(s)->sock; } + /** * Cause poll() to stop waiting immediately + * + * This can be used to reset the polling loop after changes that require + * attention, or to shut down a background thread that is waiting, etc. */ inline void whack() { @@ -248,6 +271,58 @@ public: */ inline unsigned long maxCount() const throw() { return ZT_PHY_MAX_SOCKETS; } +#ifdef __UNIX_LIKE__ + /** + * Create a two-way socket pair + * + * This uses socketpair() to create a local domain pair. The returned + * PhySocket holds the local side of the socket pair, while the + * supplied fd variable is set to the descriptor for the remote side. + * + * The local side is set to O_NONBLOCK to work with our poll loop, but + * the remote descriptor is left untouched. It's up to the caller to + * set any required fcntl(), ioctl(), or setsockopt() settings there. + * It's also up to the caller to close the remote descriptor when + * done, if necessary. + * + * @param remoteSocketDescriptor Result parameter set to remote end of socket pair's socket FD + * @param uptr Pointer to associate with local side of socket pair + * @return PhySocket for local side of socket pair + */ + inline PhySocket *createSocketPair(ZT_PHY_SOCKFD_TYPE &remoteSocketDescriptor,void *uptr = (void *)0) + { + if (_socks.size() >= ZT_PHY_MAX_SOCKETS) + return (PhySocket *)0; + + int fd[2]; fd[0] = -1; fd[1] = -1; + if ((::socketpair(PF_LOCAL,SOCK_STREAM,0,fd) != 0)||(fd[0] <= 0)||(fd[1] <= 0)) + return (PhySocket *)0; + fcntl(fd[0],F_SETFL,O_NONBLOCK); + + try { + _socks.push_back(PhySocketImpl()); + } catch ( ... ) { + ZT_PHY_CLOSE_SOCKET(fd[0]); + ZT_PHY_CLOSE_SOCKET(fd[1]); + return (PhySocket *)0; + } + PhySocketImpl &sws = _socks.back(); + + if ((long)fd[0] > _nfds) + _nfds = (long)fd[0]; + FD_SET(fd[0],&_readfds); + sws.type = ZT_PHY_SOCKET_PAIR_ENDPOINT; + sws.sock = fd[0]; + sws.uptr = uptr; + memset(&(sws.saddr),0,sizeof(struct sockaddr_storage)); + // no sockaddr for this socket type, leave saddr null + + remoteSocketDescriptor = fd[1]; + + return (PhySocket *)&sws; + } +#endif // __UNIX_LIKE__ + /** * Bind a UDP socket * @@ -883,6 +958,27 @@ public: #endif // __UNIX_LIKE__ break; + case ZT_PHY_SOCKET_PAIR_ENDPOINT: { +#ifdef __UNIX_LIKE__ + ZT_PHY_SOCKFD_TYPE sock = s->sock; // if closed, s->sock becomes invalid as s is no longer dereferencable + if (FD_ISSET(sock,&rfds)) { + long n = (long)::read(sock,buf,sizeof(buf)); + if (n <= 0) { + this->close((PhySocket *)&(*s),true); + } else { + try { + _handler->phyOnSocketPairEndpointData((PhySocket *)&(*s),&(s->uptr),(void *)buf,(unsigned long)n); + } catch ( ... ) {} + } + } + if ((FD_ISSET(sock,&wfds))&&(FD_ISSET(sock,&_writefds))) { + try { + _handler->phyOnSocketPairEndpointWritable((PhySocket *)&(*s),&(s->uptr)); + } catch ( ... ) {} + } +#endif // __UNIX_LIKE__ + } break; + default: break; @@ -914,6 +1010,11 @@ public: ZT_PHY_CLOSE_SOCKET(sws.sock); +#ifdef __UNIX_LIKE__ + if (sws.type == ZT_PHY_SOCKET_UNIX_LISTEN) + ::unlink(((struct sockaddr_un *)(&(sws.saddr)))->sun_path); +#endif // __UNIX_LIKE__ + if (callHandlers) { switch(sws.type) { case ZT_PHY_SOCKET_TCP_OUT_PENDING: @@ -934,6 +1035,12 @@ public: } catch ( ... ) {} #endif // __UNIX_LIKE__ break; + case ZT_PHY_SOCKET_PAIR_ENDPOINT: +#ifdef __UNIX_LIKE__ + try { + _handler->phyOnSocketPairEndpointClose(sock,&(sws.uptr)); + } catch ( ... ) {} +#endif // __UNIX_LIKE__ default: break; } diff --git a/selftest.cpp b/selftest.cpp index 5e3b620b..a664ca8e 100644 --- a/selftest.cpp +++ b/selftest.cpp @@ -826,6 +826,9 @@ struct TestPhyHandlers inline void phyOnUnixClose(PhySocket *sock,void **uptr) {} inline void phyOnUnixData(PhySocket *sock,void **uptr,void *data,unsigned long len) {} inline void phyOnUnixWritable(PhySocket *sock,void **uptr) {} + inline void phyOnSocketPairEndpointClose(PhySocket *sock,void **uptr) {} + inline void phyOnSocketPairEndpointData(PhySocket *sock,void **uptr,void *data,unsigned long len) {} + inline void phyOnSocketPairEndpointWritable(PhySocket *sock,void **uptr) {} #endif // __UNIX_LIKE__ }; static int testPhy() diff --git a/service/OneService.cpp b/service/OneService.cpp index f7ea2130..8085c9b4 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -928,6 +928,9 @@ public: inline void phyOnUnixClose(PhySocket *sock,void **uptr) {} inline void phyOnUnixData(PhySocket *sock,void **uptr,void *data,unsigned long len) {} inline void phyOnUnixWritable(PhySocket *sock,void **uptr) {} + inline void phyOnSocketPairEndpointClose(PhySocket *sock,void **uptr) {} + inline void phyOnSocketPairEndpointData(PhySocket *sock,void **uptr,void *data,unsigned long len) {} + inline void phyOnSocketPairEndpointWritable(PhySocket *sock,void **uptr) {} inline int nodeVirtualNetworkConfigFunction(uint64_t nwid,enum ZT1_VirtualNetworkConfigOperation op,const ZT1_VirtualNetworkConfig *nwc) { -- cgit v1.2.3 From cd3d77987ccaa77df803332a2ab0ff4805fabf70 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 10 Sep 2015 15:52:24 -0700 Subject: . --- osdep/Http.cpp | 6 +++--- osdep/Phy.hpp | 16 ++++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) (limited to 'osdep') diff --git a/osdep/Http.cpp b/osdep/Http.cpp index d491b062..0eb7c4c6 100644 --- a/osdep/Http.cpp +++ b/osdep/Http.cpp @@ -68,7 +68,7 @@ struct HttpPhyHandler inline void phyOnTcpConnect(PhySocket *sock,void **uptr,bool success) { if (success) { - phy->tcpSetNotifyWritable(sock,true); + phy->setNotifyWritable(sock,true); } else { *responseBody = "connection failed"; error = true; @@ -92,12 +92,12 @@ struct HttpPhyHandler inline void phyOnTcpWritable(PhySocket *sock,void **uptr) { if (writePtr < writeSize) { - long n = phy->tcpSend(sock,writeBuf + writePtr,writeSize - writePtr,true); + long n = phy->streamSend(sock,writeBuf + writePtr,writeSize - writePtr,true); if (n > 0) writePtr += n; } if (writePtr >= writeSize) - phy->tcpSetNotifyWritable(sock,false); + phy->setNotifyWritable(sock,false); } http_parser parser; diff --git a/osdep/Phy.hpp b/osdep/Phy.hpp index 1a07fa0a..7f790e5d 100644 --- a/osdep/Phy.hpp +++ b/osdep/Phy.hpp @@ -665,19 +665,21 @@ public: } /** - * Attempt to send data to a TCP connection (non-blocking) + * Attempt to send data to a stream socket (non-blocking) * * If -1 is returned, the socket should no longer be used as it is now * destroyed. If callCloseHandler is true, the close handler will be * called before the function returns. * - * @param sock An open TCP socket (other socket types will fail) + * This can be used with TCP, Unix, or socket pair sockets. + * + * @param sock An open stream socket (other socket types will fail) * @param data Data to send * @param len Length of data * @param callCloseHandler If true, call close handler on socket closing failure condition (default: true) * @return Number of bytes actually sent or -1 on fatal error (socket closure) */ - inline long tcpSend(PhySocket *sock,const void *data,unsigned long len,bool callCloseHandler = true) + inline long streamSend(PhySocket *sock,const void *data,unsigned long len,bool callCloseHandler = true) { PhySocketImpl &sws = *(reinterpret_cast(sock)); #if defined(_WIN32) || defined(_WIN64) @@ -755,16 +757,18 @@ public: #endif // __UNIX_LIKE__ /** - * Set whether we want to be notified via the TCP writability handler when a socket is writable + * For streams, sets whether we want to be notified that the socket is writable + * + * This can be used with TCP, Unix, or socket pair sockets. * * Call whack() if this is being done from another thread and you want * it to take effect immediately. Otherwise it is only guaranteed to * take effect on the next poll(). * - * @param sock TCP connection socket (other types are not valid) + * @param sock Stream connection socket * @param notifyWritable Want writable notifications? */ - inline const void tcpSetNotifyWritable(PhySocket *sock,bool notifyWritable) + inline const void setNotifyWritable(PhySocket *sock,bool notifyWritable) { PhySocketImpl &sws = *(reinterpret_cast(sock)); if (notifyWritable) { -- cgit v1.2.3 From c1a53a26536d2635118262f5f719795b2e70e5fa Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 11 Sep 2015 11:45:04 -0700 Subject: ARP cache and responder agent code for use in netcon and iOS. --- node/Hashtable.hpp | 5 ++ node/MAC.hpp | 6 +++ osdep/Arp.cpp | 134 +++++++++++++++++++++++++++++++++++++++++++++ osdep/Arp.hpp | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 301 insertions(+) create mode 100644 osdep/Arp.cpp create mode 100644 osdep/Arp.hpp (limited to 'osdep') diff --git a/node/Hashtable.hpp b/node/Hashtable.hpp index bcc111e3..d2b85c15 100644 --- a/node/Hashtable.hpp +++ b/node/Hashtable.hpp @@ -380,6 +380,11 @@ private: * hash an integer key index in a hash table. */ return (unsigned long)i; } + static inline unsigned long _hc(const uint32_t i) + { + // In the uint32_t case we use a simple multiplier for hashing to ensure coverage + return ((unsigned long)i * (unsigned long)2654435761); + } inline void _grow() { diff --git a/node/MAC.hpp b/node/MAC.hpp index 619b7195..a9cd43cf 100644 --- a/node/MAC.hpp +++ b/node/MAC.hpp @@ -250,6 +250,12 @@ public: _m = m._m; return *this; } + inline MAC &operator=(const uint64_t m) + throw() + { + _m = m; + return *this; + } inline bool operator==(const MAC &m) const throw() { return (_m == m._m); } inline bool operator!=(const MAC &m) const throw() { return (_m != m._m); } diff --git a/osdep/Arp.cpp b/osdep/Arp.cpp new file mode 100644 index 00000000..f71dfb54 --- /dev/null +++ b/osdep/Arp.cpp @@ -0,0 +1,134 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include +#include +#include + +#include "Arp.hpp" +#include "OSUtils.hpp" + +namespace ZeroTier { + +static const uint8_t ARP_REQUEST_HEADER[8] = { 0x00,0x01,0x08,0x00,0x06,0x04,0x00,0x01 }; +static const uint8_t ARP_RESPONSE_HEADER[8] = { 0x00,0x01,0x08,0x00,0x06,0x04,0x00,0x02 }; + +Arp::Arp() : + _cache(256), + _lastCleaned(OSUtils::now()) +{ +} + +void Arp::addLocal(uint32_t ip,const MAC &mac) +{ + _ArpEntry &e = _cache[ip]; + e.lastQuerySent = 0; // local IP + e.lastResponseReceived = 0; // local IP + e.mac = mac; + e.local = true; +} + +void Arp::remove(uint32_t ip) +{ + _cache.erase(ip); +} + +uint32_t Arp::processIncomingArp(const void *arp,unsigned int len,void *response,unsigned int &responseLen,MAC &responseDest) +{ + const uint64_t now = OSUtils::now(); + uint32_t ip = 0; + + responseLen = 0; + responseDest.zero(); + + if (len > 28) { + if (!memcmp(arp,ARP_REQUEST_HEADER,8)) { + // Respond to ARP requests for locally-known IPs + _ArpEntry *targetEntry = _cache.get(reinterpret_cast(arp)[6]); + if ((targetEntry)&&(targetEntry->local)) { + memcpy(response,ARP_RESPONSE_HEADER,8); + targetEntry->mac.copyTo(reinterpret_cast(response) + 8,6); + memcpy(reinterpret_cast(response) + 14,reinterpret_cast(arp) + 24,4); + memcpy(reinterpret_cast(response) + 18,reinterpret_cast(arp) + 8,10); + responseLen = 28; + responseDest.setTo(reinterpret_cast(arp) + 8,6); + } + } else if (!memcmp(arp,ARP_RESPONSE_HEADER,8)) { + // Learn cache entries for remote IPs from relevant ARP replies + uint32_t responseIp = 0; + memcpy(&responseIp,reinterpret_cast(arp) + 14,4); + _ArpEntry *queryEntry = _cache.get(responseIp); + if ((queryEntry)&&(!queryEntry->local)&&((now - queryEntry->lastQuerySent) <= ZT_ARP_QUERY_MAX_TTL)) { + queryEntry->lastResponseReceived = now; + queryEntry->mac.setTo(reinterpret_cast(arp) + 8,6); + ip = responseIp; + } + } + } + + if ((now - _lastCleaned) >= ZT_ARP_EXPIRE) { + _lastCleaned = now; + Hashtable< uint32_t,_ArpEntry >::Iterator i(_cache); + uint32_t *k = (uint32_t *)0; + _ArpEntry *v = (_ArpEntry *)0; + while (i.next(k,v)) { + if ((!v->local)&&((now - v->lastResponseReceived) >= ZT_ARP_EXPIRE)) + _cache.erase(*k); + } + } + + return ip; +} + +MAC Arp::query(const MAC &localMac,uint32_t ip,void *query,unsigned int &queryLen,MAC &queryDest) +{ + const uint64_t now = OSUtils::now(); + + _ArpEntry &e = _cache[ip]; + + if ( ((e.mac)&&((now - e.lastResponseReceived) >= (ZT_ARP_EXPIRE / 3))) || + ((!e.mac)&&((now - e.lastQuerySent) >= ZT_ARP_QUERY_INTERVAL)) ) { + e.lastQuerySent = now; + + uint8_t *q = reinterpret_cast(query); + memcpy(q,ARP_REQUEST_HEADER,8); q += 8; // ARP request header information, always the same + localMac.copyTo(q,6); q += 6; // sending host address + memset(q,0,10); q += 10; // sending IP and target media address are ignored in requests + memcpy(q,&ip,4); // target IP address for resolution (IP already in big-endian byte order) + queryLen = 28; + if (e.mac) + queryDest = e.mac; // confirmation query, send directly to address holder + else queryDest = (uint64_t)0xffffffffffffULL; // broadcast query + } else { + queryLen = 0; + queryDest.zero(); + } + + return e.mac; +} + +} // namespace ZeroTier diff --git a/osdep/Arp.hpp b/osdep/Arp.hpp new file mode 100644 index 00000000..b747cf85 --- /dev/null +++ b/osdep/Arp.hpp @@ -0,0 +1,156 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef ZT_ARP_HPP +#define ZT_ARP_HPP + +#include + +#include + +#include "../node/Constants.hpp" +#include "../node/Hashtable.hpp" +#include "../node/MAC.hpp" + +/** + * Maximum possible ARP length + * + * ARPs are 28 bytes in length, but specify a 128 byte buffer since + * some weird extensions we may support in the future can pad them + * out to as long as 72 bytes. + */ +#define ZT_ARP_BUF_LENGTH 128 + +/** + * Minimum permitted interval between sending ARP queries for a given IP + */ +#define ZT_ARP_QUERY_INTERVAL 2000 + +/** + * Maximum time between query and response, otherwise responses are discarded to prevent poisoning + */ +#define ZT_ARP_QUERY_MAX_TTL 5000 + +/** + * ARP expiration time + */ +#define ZT_ARP_EXPIRE 600000 + +namespace ZeroTier { + +/** + * ARP cache and resolver + * + * To implement ARP: + * + * (1) Call processIncomingArp() on all ARP packets received and then always + * check responseLen after calling. If it is non-zero, send the contents + * of response to responseDest. + * + * (2) Call query() to look up IP addresses, and then check queryLen. If it + * is non-zero, send the contents of query to queryDest (usually broadcast). + * + * Note that either of these functions can technically generate a response or + * a query at any time, so their result parameters for sending ARPs should + * always be checked. + * + * This class is not thread-safe and must be guarded if used in multi-threaded + * code. + */ +class Arp +{ +public: + Arp(); + + /** + * Set a local IP entry that we should respond to ARPs for + * + * @param mac Our local MAC address + * @param ip IP in big-endian byte order (sin_addr.s_addr) + */ + void addLocal(uint32_t ip,const MAC &mac); + + /** + * Delete a local IP entry or a cached ARP entry + * + * @param ip IP in big-endian byte order (sin_addr.s_addr) + */ + void remove(uint32_t ip); + + /** + * Process ARP packets + * + * For ARP queries, a response is generated and responseLen is set to its + * frame payload length in bytes. + * + * For ARP responses, the cache is populated and the IP address entry that + * was learned is returned. + * + * @param arp ARP frame data + * @param len Length of ARP frame (usually 28) + * @param response Response buffer -- MUST be a minimum of ZT_ARP_BUF_LENGTH in size + * @param responseLen Response length, or set to 0 if no response + * @param responseDest Destination of response, or set to null if no response + * @return IP address learned or 0 if no new IPs in cache + */ + uint32_t processIncomingArp(const void *arp,unsigned int len,void *response,unsigned int &responseLen,MAC &responseDest); + + /** + * Get the MAC corresponding to an IP, generating a query if needed + * + * This returns a MAC for a remote IP. The local MAC is returned for local + * IPs as well. It may also generate a query if the IP is not known or the + * entry needs to be refreshed. In this case queryLen will be set to a + * non-zero value, so this should always be checked on return even if the + * MAC returned is non-null. + * + * @param localMac Local MAC address of host interface + * @param ip IP to look up + * @param query Buffer for generated query -- MUST be a minimum of ZT_ARP_BUF_LENGTH in size + * @param queryLen Length of generated query, or set to 0 if no query generated + * @param queryDest Destination of query, or set to null if no query generated + * @return MAC or 0 if no cached entry for this IP + */ + MAC query(const MAC &localMac,uint32_t ip,void *query,unsigned int &queryLen,MAC &queryDest); + +private: + struct _ArpEntry + { + _ArpEntry() : lastQuerySent(0),lastResponseReceived(0),mac(),local(false) {} + uint64_t lastQuerySent; // Time last query was sent or 0 for local IP + uint64_t lastResponseReceived; // Time of last ARP response or 0 for local IP + MAC mac; // MAC address of device responsible for IP or null if not known yet + bool local; // True if this is a local ARP entry + }; + + Hashtable< uint32_t,_ArpEntry > _cache; + uint64_t _lastCleaned; +}; + +} // namespace ZeroTier + +#endif -- cgit v1.2.3