/* * ZeroTier One - Global Peer to Peer Ethernet * Copyright (C) 2012-2013 ZeroTier Networks LLC * * 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 #include "RuntimeEnvironment.hpp" #include "Logger.hpp" #include "Filter.hpp" #include "Utils.hpp" namespace ZeroTier { const char *const Filter::UNKNOWN_NAME = "(unknown)"; const Range Filter::ANY; bool Filter::Rule::operator()(unsigned int etype,const void *data,unsigned int len) const throw(std::invalid_argument) { if ((!_etherType)||(_etherType(etype))) { // ethertype is ANY, or matches // Ethertype determines meaning of protocol and port switch(etype) { case ZT_ETHERTYPE_IPV4: if (len > 20) { if ((!_protocol)||(_protocol(((const uint8_t *)data)[9]))) { // protocol is ANY or match if (!_port) // port is ANY return true; // Don't match on fragments beyond fragment 0. If we've blocked // fragment 0, further fragments will fall on deaf ears anyway. if ((Utils::ntoh(((const uint16_t *)data)[3]) & 0x1fff)) return false; // Internet header length determines where data begins, in multiples of 32 bits unsigned int ihl = 4 * (((const uint8_t *)data)[0] & 0x0f); switch(((const uint8_t *)data)[9]) { // port's meaning depends on IP protocol case ZT_IPPROTO_ICMP: // For ICMP, port is ICMP type return _port(((const uint8_t *)data)[ihl]); case ZT_IPPROTO_TCP: case ZT_IPPROTO_UDP: case ZT_IPPROTO_SCTP: case ZT_IPPROTO_UDPLITE: // For these, port is destination port. Protocol designers were // nice enough to put the field in the same place. return _port(((const uint16_t *)data)[(ihl / 2) + 1]); default: // port has no meaning for other IP types, so ignore it return true; } return false; // no match on port } } else throw std::invalid_argument("undersized IPv4 packet"); break; case ZT_ETHERTYPE_IPV6: if (len > 40) { int nextHeader = ((const uint8_t *)data)[6]; unsigned int pos = 40; while ((pos < len)&&(nextHeader >= 0)&&(nextHeader != 59)) { // 59 == no next header fprintf(stderr,"[rule] V6: start header parse, header %.2x pos %d\n",nextHeader,pos); switch(nextHeader) { case 0: // hop-by-hop options case 60: // destination options case 43: // routing case 135: // mobility (mobile IPv6 options) if (_protocol((unsigned int)nextHeader)) return true; // match if our goal was to match any of these nextHeader = ((const uint8_t *)data)[pos]; pos += 8 + (8 * ((const uint8_t *)data)[pos + 1]); break; case 44: // fragment if (_protocol(44)) return true; // match if our goal was to match fragments nextHeader = ((const uint8_t *)data)[pos]; pos += 8; break; case ZT_IPPROTO_AH: // AH return _protocol(ZT_IPPROTO_AH); // true if AH is matched protocol, otherwise false since packet will be IPsec case ZT_IPPROTO_ESP: // ESP return _protocol(ZT_IPPROTO_ESP); // true if ESP is matched protocol, otherwise false since packet will be IPsec case ZT_IPPROTO_ICMPV6: // Only match ICMPv6 if we've selected it specifically if (_protocol(ZT_IPPROTO_ICMPV6)) { // Port is interpreted as ICMPv6 type if ((!_port)||(_port(((const uint8_t *)data)[pos]))) return true; } break; case ZT_IPPROTO_TCP: case ZT_IPPROTO_UDP: case ZT_IPPROTO_SCTP: case ZT_IPPROTO_UDPLITE: // If we encounter any of these, match if protocol matches or is wildcard as // we'll consider these the "real payload" if present. if ((!_protocol)||(_protocol(nextHeader))) { if ((!_port)||(_port(((const uint16_t *)data)[(pos / 2) + 1]))) return true; // protocol matches or is ANY, port is ANY or matches } break; default: { char foo[128]; sprintf(foo,"unrecognized IPv6 header type %d",(int)nextHeader); throw std::invalid_argument(foo); } } fprintf(stderr,"[rule] V6: end header parse, next header %.2x, new pos %d\n",nextHeader,pos); } } else throw std::invalid_argument("undersized IPv6 packet"); break; default: // For other ethertypes, protocol and port are ignored. What would they mean? return true; } } return false; } std::string Filter::Rule::toString() const { char buf[128]; std::string s; switch(_etherType.magnitude()) { case 0: s.push_back('*'); break; case 1: sprintf(buf,"%u",_etherType.start); s.append(buf); break; default: sprintf(buf,"%u-%u",_etherType.start,_etherType.end); s.append(buf); break; } s.push_back('/'); switch(_protocol.magnitude()) { case 0: s.push_back('*'); break; case 1: sprintf(buf,"%u",_protocol.start); s.append(buf); break; default: sprintf(buf,"%u-%u",_protocol.start,_protocol.end); s.append(buf); break; } s.push_back('/'); switch(_port.magnitude()) { case 0: s.push_back('*'); break; case 1: sprintf(buf,"%u",_port.start); s.append(buf); break; default: sprintf(buf,"%u-%u",_port.start,_port.end); s.append(buf); break; } return s; } void Filter::add(const Rule &r,const Action &a) { Mutex::Lock _l(_chain_m); for(std::vector::iterator i(_chain.begin());i!=_chain.end();++i) { if (i->rule == r) { _chain.erase(i); break; } } _chain.push_back(Entry(r,a)); } std::string Filter::toString(const char *sep) const { if (!sep) sep = ","; std::string s; bool first = true; Mutex::Lock _l(_chain_m); for(std::vector::const_iterator i(_chain.begin());i!=_chain.end();++i) { s.append(i->rule.toString()); if (first) first = false; else s.append(sep); } return s; } const char *Filter::etherTypeName(const unsigned int etherType) throw() { switch(etherType) { case ZT_ETHERTYPE_IPV4: return "ETHERTYPE_IPV4"; case ZT_ETHERTYPE_ARP: return "ETHERTYPE_ARP"; case ZT_ETHERTYPE_RARP: return "ETHERTYPE_RARP"; case ZT_ETHERTYPE_ATALK: return "ETHERTYPE_ATALK"; case ZT_ETHERTYPE_AARP: return "ETHERTYPE_AARP"; case ZT_ETHERTYPE_IPX_A: return "ETHERTYPE_IPX_A"; case ZT_ETHERTYPE_IPX_B: return "ETHERTYPE_IPX_B"; case ZT_ETHERTYPE_IPV6: return "ETHERTYPE_IPV6"; } return UNKNOWN_NAME; } const char *Filter::ipProtocolName(const unsigned int ipp) throw() { switch(ipp) { case ZT_IPPROTO_ICMP: return "IPPROTO_ICMP"; case ZT_IPPROTO_IGMP: return "IPPROTO_IGMP"; case ZT_IPPROTO_TCP: return "IPPROTO_TCP"; case ZT_IPPROTO_UDP: return "IPPROTO_UDP"; case ZT_IPPROTO_GRE: return "IPPROTO_GRE"; case ZT_IPPROTO_ESP: return "IPPROTO_ESP"; case ZT_IPPROTO_AH: return "IPPROTO_AH"; case ZT_IPPROTO_ICMPV6: return "IPPROTO_ICMPV6"; case ZT_IPPROTO_OSPF: return "IPPROTO_OSPF"; case ZT_IPPROTO_IPIP: return "IPPROTO_IPIP"; case ZT_IPPROTO_IPCOMP: return "IPPROTO_IPCOMP"; case ZT_IPPROTO_L2TP: return "IPPROTO_L2TP"; case ZT_IPPROTO_SCTP: return "IPPROTO_SCTP"; case ZT_IPPROTO_FC: return "IPPROTO_FC"; case ZT_IPPROTO_UDPLITE: return "IPPROTO_UDPLITE"; case ZT_IPPROTO_HIP: return "IPPROTO_HIP"; } return UNKNOWN_NAME; } const char *Filter::icmpTypeName(const unsigned int icmpType) throw() { switch(icmpType) { case ZT_ICMP_ECHO_REPLY: return "ICMP_ECHO_REPLY"; case ZT_ICMP_DESTINATION_UNREACHABLE: return "ICMP_DESTINATION_UNREACHABLE"; case ZT_ICMP_SOURCE_QUENCH: return "ICMP_SOURCE_QUENCH"; case ZT_ICMP_REDIRECT: return "ICMP_REDIRECT"; case ZT_ICMP_ALTERNATE_HOST_ADDRESS: return "ICMP_ALTERNATE_HOST_ADDRESS"; case ZT_ICMP_ECHO_REQUEST: return "ICMP_ECHO_REQUEST"; case ZT_ICMP_ROUTER_ADVERTISEMENT: return "ICMP_ROUTER_ADVERTISEMENT"; case ZT_ICMP_ROUTER_SOLICITATION: return "ICMP_ROUTER_SOLICITATION"; case ZT_ICMP_TIME_EXCEEDED: return "ICMP_TIME_EXCEEDED"; case ZT_ICMP_BAD_IP_HEADER: return "ICMP_BAD_IP_HEADER"; case ZT_ICMP_TIMESTAMP: return "ICMP_TIMESTAMP"; case ZT_ICMP_TIMESTAMP_REPLY: return "ICMP_TIMESTAMP_REPLY"; case ZT_ICMP_INFORMATION_REQUEST: return "ICMP_INFORMATION_REQUEST"; case ZT_ICMP_INFORMATION_REPLY: return "ICMP_INFORMATION_REPLY"; case ZT_ICMP_ADDRESS_MASK_REQUEST: return "ICMP_ADDRESS_MASK_REQUEST"; case ZT_ICMP_ADDRESS_MASK_REPLY: return "ICMP_ADDRESS_MASK_REPLY"; case ZT_ICMP_TRACEROUTE: return "ICMP_TRACEROUTE"; case ZT_ICMP_MOBILE_HOST_REDIRECT: return "ICMP_MOBILE_HOST_REDIRECT"; case ZT_ICMP_MOBILE_REGISTRATION_REQUEST: return "ICMP_MOBILE_REGISTRATION_REQUEST"; case ZT_ICMP_MOBILE_REGISTRATION_REPLY: return "ICMP_MOBILE_REGISTRATION_REPLY"; } return UNKNOWN_NAME; } const char *Filter::icmp6TypeName(const unsigned int icmp6Type) throw() { switch(icmp6Type) { case ZT_ICMP6_DESTINATION_UNREACHABLE: return "ICMP6_DESTINATION_UNREACHABLE"; case ZT_ICMP6_PACKET_TOO_BIG: return "ICMP6_PACKET_TOO_BIG"; case ZT_ICMP6_TIME_EXCEEDED: return "ICMP6_TIME_EXCEEDED"; case ZT_ICMP6_PARAMETER_PROBLEM: return "ICMP6_PARAMETER_PROBLEM"; case ZT_ICMP6_ECHO_REQUEST: return "ICMP6_ECHO_REQUEST"; case ZT_ICMP6_ECHO_REPLY: return "ICMP6_ECHO_REPLY"; case ZT_ICMP6_MULTICAST_LISTENER_QUERY: return "ICMP6_MULTICAST_LISTENER_QUERY"; case ZT_ICMP6_MULTICAST_LISTENER_REPORT: return "ICMP6_MULTICAST_LISTENER_REPORT"; case ZT_ICMP6_MULTICAST_LISTENER_DONE: return "ICMP6_MULTICAST_LISTENER_DONE"; case ZT_ICMP6_ROUTER_SOLICITATION: return "ICMP6_ROUTER_SOLICITATION"; case ZT_ICMP6_ROUTER_ADVERTISEMENT: return "ICMP6_ROUTER_ADVERTISEMENT"; case ZT_ICMP6_NEIGHBOR_SOLICITATION: return "ICMP6_NEIGHBOR_SOLICITATION"; case ZT_ICMP6_NEIGHBOR_ADVERTISEMENT: return "ICMP6_NEIGHBOR_ADVERTISEMENT"; case ZT_ICMP6_REDIRECT_MESSAGE: return "ICMP6_REDIRECT_MESSAGE"; case ZT_ICMP6_ROUTER_RENUMBERING: return "ICMP6_ROUTER_RENUMBERING"; case ZT_ICMP6_NODE_INFORMATION_QUERY: return "ICMP6_NODE_INFORMATION_QUERY"; case ZT_ICMP6_NODE_INFORMATION_RESPONSE: return "ICMP6_NODE_INFORMATION_RESPONSE"; case ZT_ICMP6_INV_NEIGHBOR_SOLICITATION: return "ICMP6_INV_NEIGHBOR_SOLICITATION"; case ZT_ICMP6_INV_NEIGHBOR_ADVERTISEMENT: return "ICMP6_INV_NEIGHBOR_ADVERTISEMENT"; case ZT_ICMP6_MLDV2: return "ICMP6_MLDV2"; case ZT_ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REQUEST: return "ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REQUEST"; case ZT_ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REPLY: return "ICMP6_HOME_AGENT_ADDRESS_DISCOVERY_REPLY"; case ZT_ICMP6_MOBILE_PREFIX_SOLICITATION: return "ICMP6_MOBILE_PREFIX_SOLICITATION"; case ZT_ICMP6_MOBILE_PREFIX_ADVERTISEMENT: return "ICMP6_MOBILE_PREFIX_ADVERTISEMENT"; case ZT_ICMP6_CERTIFICATION_PATH_SOLICITATION: return "ICMP6_CERTIFICATION_PATH_SOLICITATION"; case ZT_ICMP6_CERTIFICATION_PATH_ADVERTISEMENT: return "ICMP6_CERTIFICATION_PATH_ADVERTISEMENT"; case ZT_ICMP6_MULTICAST_ROUTER_ADVERTISEMENT: return "ICMP6_MULTICAST_ROUTER_ADVERTISEMENT"; case ZT_ICMP6_MULTICAST_ROUTER_SOLICITATION: return "ICMP6_MULTICAST_ROUTER_SOLICITATION"; case ZT_ICMP6_MULTICAST_ROUTER_TERMINATION: return "ICMP6_MULTICAST_ROUTER_TERMINATION"; case ZT_ICMP6_RPL_CONTROL_MESSAGE: return "ICMP6_RPL_CONTROL_MESSAGE"; } return UNKNOWN_NAME; } Filter::Action Filter::operator()(const RuntimeEnvironment *_r,unsigned int etherType,const void *frame,unsigned int len) const { Mutex::Lock _l(_chain_m); TRACE("starting match against %d rules",(int)_chain.size()); int ruleNo = 0; for(std::vector::const_iterator r(_chain.begin());r!=_chain.end();++r,++ruleNo) { try { if (r->rule(etherType,frame,len)) { TRACE("match: %s",r->rule.toString().c_str()); switch(r->action) { case ACTION_ALLOW: case ACTION_DENY: return r->action; default: break; } } else { TRACE("no match: %s",r->rule.toString().c_str()); } } catch (std::invalid_argument &exc) { LOG("filter: unable to parse packet on rule %s (%d): %s",r->rule.toString().c_str(),ruleNo,exc.what()); return ACTION_UNPARSEABLE; } catch ( ... ) { LOG("filter: unable to parse packet on rule %s (%d): unknown exception",r->rule.toString().c_str(),ruleNo); return ACTION_UNPARSEABLE; } } return ACTION_ALLOW; } } // namespace ZeroTier