From 8b82f1c6095bc9761a5961154c5c3bd1b57b9510 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 14 Feb 2017 16:43:22 -0800 Subject: Add rules compiler script. --- rule-compiler/README.md | 5 + rule-compiler/cli.js | 29 + .../examples/capabilities-and-tags.ztrules | 40 + rule-compiler/package.json | 18 + rule-compiler/rule-compiler.js | 854 +++++++++++++++++++++ 5 files changed, 946 insertions(+) create mode 100644 rule-compiler/README.md create mode 100644 rule-compiler/cli.js create mode 100644 rule-compiler/examples/capabilities-and-tags.ztrules create mode 100644 rule-compiler/package.json create mode 100644 rule-compiler/rule-compiler.js (limited to 'rule-compiler') diff --git a/rule-compiler/README.md b/rule-compiler/README.md new file mode 100644 index 00000000..e3aa2615 --- /dev/null +++ b/rule-compiler/README.md @@ -0,0 +1,5 @@ +ZeroTier Rules Compiler +====== + +This script converts ZeroTier rules in human-readable format into rules suitable for import into a ZeroTier network controller. It's the script that is used in the rules editor on [ZeroTier Central](https://my.zerotier.com/). + diff --git a/rule-compiler/cli.js b/rule-compiler/cli.js new file mode 100644 index 00000000..c4a3b291 --- /dev/null +++ b/rule-compiler/cli.js @@ -0,0 +1,29 @@ +'use strict'; + +var fs = require('fs'); + +var RuleCompiler = require('./rule-compiler.js'); + +if (process.argv.length < 3) { + console.log('Usage: node cli.js '); + process.exit(1); +} + +var src = fs.readFileSync(process.argv[2]).toString(); + +var rules = []; +var caps = {}; +var tags = {}; +var err = RuleCompiler.compile(src,rules,caps,tags); + +if (err) { + console.log('ERROR parsing '+process.argv[2]+' line '+err[0]+' column '+err[1]+': '+err[2]); + process.exit(1); +} else { + console.log(JSON.stringify({ + rules: rules, + caps: caps, + tags: tags + },null,2)); + process.exit(0); +} diff --git a/rule-compiler/examples/capabilities-and-tags.ztrules b/rule-compiler/examples/capabilities-and-tags.ztrules new file mode 100644 index 00000000..9b35f28d --- /dev/null +++ b/rule-compiler/examples/capabilities-and-tags.ztrules @@ -0,0 +1,40 @@ +# This is a default rule set that allows IPv4 and IPv6 traffic. +# You can edit as needed. If your rule set gets large we recommend +# cutting and pasting it somewhere to keep a backup. + +# Drop all Ethernet frame types that are not IPv4 or IPv6 +drop + not ethertype 0x0800 # IPv4 + not ethertype 0x0806 # IPv4 ARP + not ethertype 0x86dd # IPv6 +; + +# Capability: outgoing SSH +cap ssh + id 1000 + accept + ipprotocol tcp + dport 22 + ; +; + +# A tag indicating which department people belong to +tag department + id 1000 + enum 100 sales + enum 200 marketing + enum 300 accounting + enum 400 engineering +; + +# Accept all traffic between members of the same department +accept + tdiff department 0 +; + +# You can insert other drop, tee, etc. rules here. This rule +# set ends with a blanket accept, making it permissive by +# default. + +accept; + diff --git a/rule-compiler/package.json b/rule-compiler/package.json new file mode 100644 index 00000000..9bdc63ff --- /dev/null +++ b/rule-compiler/package.json @@ -0,0 +1,18 @@ +{ + "name": "zerotier-rule-compiler", + "version": "1.1.17", + "description": "ZeroTier Rule Script Compiler", + "main": "cli.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/zerotier/ZeroTierOne/rule-compiler" + }, + "keywords": [ + "ZeroTier" + ], + "author": "ZeroTier, Inc. ", + "license": "GPL-2.0" +} diff --git a/rule-compiler/rule-compiler.js b/rule-compiler/rule-compiler.js new file mode 100644 index 00000000..76358606 --- /dev/null +++ b/rule-compiler/rule-compiler.js @@ -0,0 +1,854 @@ +'use strict'; + +// Names for bits in characteristics -- 0==LSB, 63==MSB +const CHARACTERISTIC_BITS = { + 'inbound': 63, + 'multicast': 62, + 'broadcast': 61, + 'tcp_fin': 0, + 'tcp_syn': 1, + 'tcp_rst': 2, + 'tcp_psh': 3, + 'tcp_ack': 4, + 'tcp_urg': 5, + 'tcp_ece': 6, + 'tcp_cwr': 7, + 'tcp_ns': 8, + 'tcp_rs2': 9, + 'tcp_rs1': 10, + 'tcp_rs0': 11 +}; + +// Shorthand names for ethernet types +const ETHERTYPES = { + 'ipv4': 0x0800, + 'arp': 0x0806, + 'rarp': 0x8035, + 'ipv6': 0x86dd, + 'atalk': 0x809b, + 'aarp': 0x80f3, + 'ipx_a': 0x8137, + 'ipx_b': 0x8138 +}; + +// Shorthand names for IP protocols +const IP_PROTOCOLS = { + 'icmp': 0x01, + 'igmp': 0x02, + 'ipip': 0x04, + 'tcp': 0x06, + 'egp': 0x08, + 'igp': 0x09, + 'udp': 0x11, + 'rdp': 0x1b, + 'esp': 0x32, + 'ah': 0x33, + 'icmp6': 0x3a, + 'icmpv6': 0x3a, + 'l2tp': 0x73, + 'sctp': 0x84, + 'udplite': 0x88 +}; + +// Keywords that open new blocks that must be terminated by a semicolon +const OPEN_BLOCK_KEYWORDS = { + 'macro': true, + 'tag': true, + 'cap': true, + 'drop': true, + 'accept': true, + 'tee': true, + 'watch': true, + 'redirect': true, + 'break': true +}; + +// Reserved words that can't be used as tag, capability, or rule set names +const RESERVED_WORDS = { + 'macro': true, + 'tag': true, + 'cap': true, + + 'drop': true, + 'accept': true, + 'tee': true, + 'watch': true, + 'redirect': true, + 'break': true, + + 'ztsrc': true, + 'ztdest': true, + 'vlan': true, + 'vlanpcp': true, + 'vlandei': true, + 'ethertype': true, + 'macsrc': true, + 'macdest': true, + 'ipsrc': true, + 'ipdest': true, + 'iptos': true, + 'ipprotocol': true, + 'icmp': true, + 'sport': true, + 'dport': true, + 'chr': true, + 'framesize': true, + 'random': true, + 'tand': true, + 'tor': true, + 'txor': true, + 'tdiff': true, + 'teq': true, + + 'type': true, + 'enum': true, + 'class': true, + 'define': true, + 'import': true, + 'include': true, + 'log': true, + 'not': true, + 'xor': true, + 'or': true, + 'and': true, + 'set': true, + 'var': true, + 'let': true +}; + +const KEYWORD_TO_API_MAP = { + 'drop': 'ACTION_DROP', + 'accept': 'ACTION_ACCEPT', + 'tee': 'ACTION_TEE', + 'watch': 'ACTION_WATCH', + 'redirect': 'ACTION_REDIRECT', + 'break': 'ACTION_BREAK', + + 'ztsrc': 'MATCH_SOURCE_ZEROTIER_ADDRESS', + 'ztdest': 'MATCH_DEST_ZEROTIER_ADDRESS', + 'vlan': 'MATCH_VLAN_ID', + 'vlanpcp': 'MATCH_VLAN_PCP', + 'vlandei': 'MATCH_VLAN_DEI', + 'ethertype': 'MATCH_ETHERTYPE', + 'macsrc': 'MATCH_MAC_SOURCE', + 'macdest': 'MATCH_MAC_DEST', + //'ipsrc': '', // special handling since we programmatically differentiate between V4 and V6 + //'ipdest': '', // special handling + 'iptos': 'MATCH_IP_TOS', + 'ipprotocol': 'MATCH_IP_PROTOCOL', + 'icmp': 'MATCH_ICMP', + 'sport': 'MATCH_IP_SOURCE_PORT_RANGE', + 'dport': 'MATCH_IP_DEST_PORT_RANGE', + 'chr': 'MATCH_CHARACTERISTICS', + 'framesize': 'MATCH_FRAME_SIZE_RANGE', + 'random': 'MATCH_RANDOM', + 'tand': 'MATCH_TAGS_BITWISE_AND', + 'tor': 'MATCH_TAGS_BITWISE_OR', + 'txor': 'MATCH_TAGS_BITWISE_XOR', + 'tdiff': 'MATCH_TAGS_DIFFERENCE', + 'teq': 'MATCH_TAGS_EQUAL' +}; + +// Number of args for each match +const MATCH_ARG_COUNTS = { + 'ztsrc': 1, + 'ztdest': 1, + 'vlan': 1, + 'vlanpcp': 1, + 'vlandei': 1, + 'ethertype': 1, + 'macsrc': 1, + 'macdest': 1, + 'ipsrc': 1, + 'ipdest': 1, + 'iptos': 2, + 'ipprotocol': 1, + 'icmp': 2, + 'sport': 1, + 'dport': 1, + 'chr': 1, + 'framesize': 1, + 'random': 1, + 'tand': 2, + 'tor': 2, + 'txor': 2, + 'tdiff': 2, + 'teq': 2 +}; + +const INTL_ALPHANUM_REGEX = new RegExp('[0-9A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]'); +function _isValidName(n) +{ + if ((typeof n !== 'string')||(n.length === 0)) + return false; + if ("0123456789".indexOf(n.charAt(0)) >= 0) + return false; + for(let i=0;i 2)&&(n.substr(0,2) === '0x')) + n = parseInt(n.substr(2),16); + else n = parseInt(n,10); + return (((typeof n === 'number')&&(n !== null)&&(!isNaN(n))) ? n : -1); + } catch (e) { + return -1; + } +} + +function _cleanMac(m) +{ + m = m.toLowerCase(); + var m2 = ''; + for(let i=0;((i= 0) { + m2 += c; + if ((m2.length > 0)&&(m2.length !== 17)&&((m2.length & 1) === 0)) + m2 += ':'; + } + } + return m2; +} + +function _cleanHex(m) +{ + m = m.toLowerCase(); + var m2 = ''; + for(let i=0;i= 0) + m2 += c; + } + return m2; +} + +function _renderMatches(mtree,rules,macros,caps,tags,params) +{ + let not = false; + let or = false; + for(let k=0;k= mtree.length) + return [ mtree[k - 1][1],mtree[k - 1][2],'Missing argument(s) to match.' ]; + let arg = mtree[k][0]; + if ((typeof arg !== 'string')||(arg in RESERVED_WORDS)||(arg.length === 0)) + return [ mtree[k - 1][1],mtree[k - 1][2],'Missing argument(s) to match (invalid argument or argument is reserved word).' ]; + if (arg.charAt(0) === '$') { + let tmp = params[arg]; + if (typeof tmp === 'undefined') + return [ mtree[k][1],mtree[k][2],'Undefined variable name.' ]; + args.push([ tmp,mtree[k][1],mtree[k][2] ]); + } else { + args.push(mtree[k]); + } + } + + switch(match) { + case 'ztsrc': + case 'ztdest': { + let zt = _cleanHex(args[0][0]); + if (zt.length !== 10) + return [ args[0][1],args[0][2],'Invalid ZeroTier address.' ]; + rules.push({ + 'type': KEYWORD_TO_API_MAP[match], + 'not': not, + 'or': or, + 'zt': zt + }); + } break; + + case 'vlan': + case 'vlanpcp': + case 'vlandei': + case 'ethertype': + case 'ipprotocol': { + let num = null; + switch (match) { + case 'ethertype': num = ETHERTYPES[args[0][0]]; break; + case 'ipprotocol': num = IP_PROTOCOLS[args[0][0]]; break; + } + if (typeof num !== 'number') + num = _parseNum(args[0][0]); + if ((typeof num !== 'number')||(num < 0)||(num > 0xffffffff)||(num === null)) + return [ args[0][1],args[0][2],'Invalid numeric value.' ]; + let r = { + 'type': KEYWORD_TO_API_MAP[match], + 'not': not, + 'or': or + }; + switch(match) { + case 'vlan': r['vlanId'] = num; break; + case 'vlanpcp': r['vlanPcp'] = num; break; + case 'vlandei': r['vlanDei'] = num; break; + case 'ethertype': r['etherType'] = num; break; + case 'ipprotocol': r['ipProtocol'] = num; break; + } + rules.push(r); + } break; + + case 'random': { + let num = parseFloat(args[0][0])||0.0; + if (num < 0.0) num = 0.0; + if (num > 1.0) num = 1.0; + rules.push({ + 'type': KEYWORD_TO_API_MAP[match], + 'not': not, + 'or': or, + 'probability': Math.floor(4294967295 * num) + }); + } break; + + case 'macsrc': + case 'macdest': { + let mac = _cleanMac(args[0][0]); + if (mac.length !== 17) + return [ args[0][1],args[0][2],'Invalid MAC address.' ]; + rules.push({ + 'type': KEYWORD_TO_API_MAP[match], + 'not': not, + 'or': or, + 'mac': mac + }); + } break; + + case 'ipsrc': + case 'ipdest': { + let ip = args[0][0]; + let slashIdx = ip.indexOf('/'); + if (slashIdx <= 0) + return [ args[0][1],args[0][2],'Missing /bits netmask length designation in IP.' ]; + let ipOnly = ip.substr(0,slashIdx); + if (IPV6_REGEX.test(ipOnly)) { + rules.push({ + 'type': ((match === 'ipsrc') ? 'MATCH_IPV6_SOURCE' : 'MATCH_IPV6_DEST'), + 'not': not, + 'or': or, + 'ip': ip + }); + } else if (IPV4_REGEX.test(ipOnly)) { + rules.push({ + 'type': ((match === 'ipsrc') ? 'MATCH_IPV4_SOURCE' : 'MATCH_IPV4_DEST'), + 'not': not, + 'or': or, + 'ip': ip + }); + } else { + return [ args[0][1],args[0][2],'Invalid IP address (not valid IPv4 or IPv6).' ]; + } + } break; + + case 'icmp': { + let icmpType = _parseNum(args[0][0]); + if ((icmpType < 0)||(icmpType > 0xff)) + return [ args[0][1],args[0][2],'Missing or invalid ICMP type.' ]; + let icmpCode = _parseNum(args[1][0]); // -1 okay, indicates don't match code + if (icmpCode > 0xff) + return [ args[1][1],args[1][2],'Invalid ICMP code (use -1 for none).' ]; + rules.push({ + 'type': 'MATCH_ICMP', + 'not': not, + 'or': or, + 'icmpType': icmpType, + 'icmpCode': ((icmpCode < 0) ? null : icmpCode) + }); + } break; + + case 'sport': + case 'dport': + case 'framesize': { + let arg = args[0][0]; + let fn = null; + let tn = null; + if (arg.indexOf('-') > 0) { + let asplit = arg.split('-'); + if (asplit.length !== 2) { + return [ args[0][1],args[0][2],'Invalid numeric range.' ]; + } else { + fn = _parseNum(asplit[0]); + tn = _parseNum(asplit[1]); + } + } else { + fn = _parseNum(arg); + tn = fn; + } + if ((fn < 0)||(fn > 0xffff)||(tn < 0)||(tn > 0xffff)||(tn < fn)) + return [ args[0][1],args[0][2],'Invalid numeric range.' ]; + rules.push({ + 'type': KEYWORD_TO_API_MAP[match], + 'not': not, + 'or': or, + 'start': fn, + 'end': tn + }); + } break; + + case 'iptos': { + let mask = _parseNum(args[0][0]); + if ((typeof mask !== 'number')||(mask < 0)||(mask > 0xff)||(mask === null)) + return [ args[0][1],args[0][2],'Invalid mask.' ]; + let arg = args[1][0]; + let fn = null; + let tn = null; + if (arg.indexOf('-') > 0) { + let asplit = arg.split('-'); + if (asplit.length !== 2) { + return [ args[1][1],args[1][2],'Invalid value range.' ]; + } else { + fn = _parseNum(asplit[0]); + tn = _parseNum(asplit[1]); + } + } else { + fn = _parseNum(arg); + tn = fn; + } + if ((fn < 0)||(fn > 0xff)||(tn < 0)||(tn > 0xff)||(tn < fn)) + return [ args[1][1],args[1][2],'Invalid value range.' ]; + rules.push({ + 'type': 'MATCH_IP_TOS', + 'not': not, + 'or': or, + 'mask': mask, + 'start': fn, + 'end': tn + }); + } break; + + case 'chr': { + let chrb = args[0][0].split(/[,]+/); + let maskhi = 0; + let masklo = 0; + for(let i=0;i 0) { + let tmp = CHARACTERISTIC_BITS[chrb[i]]; + let bit = (typeof tmp === 'number') ? tmp : _parseNum(chrb[i]); + if ((bit < 0)||(bit > 63)) + return [ args[0][1],args[0][2],'Invalid bit index (range 0-63) or unrecognized name.' ]; + if (bit >= 32) + maskhi |= Math.abs(1 << (bit - 32)); + else masklo |= Math.abs(1 << bit); + } + } + maskhi = Math.abs(maskhi).toString(16); + while (maskhi.length < 8) maskhi = '0' + maskhi; + masklo = Math.abs(masklo).toString(16); + while (masklo.length < 8) masklo = '0' + masklo; + rules.push({ + 'type': 'MATCH_CHARACTERISTICS', + 'not': not, + 'or': or, + 'mask': (maskhi + masklo) + }); + } break; + + case 'tand': + case 'tor': + case 'txor': + case 'tdiff': + case 'teq': { + let tag = tags[args[0][0]]; + let tagId = -1; + let tagValue = -1; + if (tag) { + tagId = tag.id; + tagValue = args[1][0]; + if (tagValue in tag.flags) + tagValue = tag.flags[tagValue]; + else if (tagValue in tag.enums) + tagValue = tag.enums[tagValue]; + else tagValue = _parseNum(tagValue); + } else { + tagId = _parseNum(args[0][0]); + tagValue = _parseNum(args[1][0]); + } + if ((tagId < 0)||(tagId > 0xffffffff)) + return [ args[0][1],args[0][2],'Undefined tag name and invalid tag value.' ]; + if ((tagValue < 0)||(tagValue > 0xffffffff)) + return [ args[1][1],args[1][2],'Invalid tag value or unrecognized flag/enum name.' ]; + rules.push({ + 'type': KEYWORD_TO_API_MAP[match], + 'not': not, + 'or': or, + 'id': tagId, + 'value': tagValue + }); + } break; + } + + not = false; + or = false; + } + } + return null; +} + +function _renderActions(rtree,rules,macros,caps,tags,params) +{ + for(let k=0;k= rtree.length) + return [ rtree[k][1],rtree[k][2],'Include directive is missing a macro name.' ]; + let macroName = rtree[k + 1][0]; + ++k; + + let macroParamArray = []; + let parenIdx = macroName.indexOf('('); + if (parenIdx > 0) { + let pns = macroName.substr(parenIdx + 1).split(/[,)]+/); + for(let k=0;k 0) + macroParamArray.push(pns[k]); + } + macroName = macroName.substr(0,parenIdx); + } + + let macro = macros[macroName]; + if (!macro) + return [ rtree[k][1],rtree[k][2],'Macro name not found.' ]; + let macroParams = {}; + for(let param in macro.params) { + let pidx = macro.params[param]; + if (pidx >= macroParamArray.length) + return [ rtree[k][1],rtree[k][2],'Missing one or more required macro parameter.' ]; + macroParams[param] = macroParamArray[pidx]; + } + + let err = _renderActions(macro.rules,rules,macros,caps,tags,macroParams); + if (err !== null) + return err; + } else if ((action === 'drop')||(action === 'accept')||(action === 'break')) { // actions without arguments + if (((k + 1) < rtree.length)&&(Array.isArray(rtree[k + 1][0]))) { + let mtree = rtree[k + 1]; ++k; + let err = _renderMatches(mtree,rules,macros,caps,tags,params); + if (err !== null) + return err; + } + rules.push({ + 'type': KEYWORD_TO_API_MAP[action] + }); + } else if ((action === 'tee')||(action === 'watch')) { // actions with arguments (ZeroTier address) + if (((k + 1) < rtree.length)&&(Array.isArray(rtree[k + 1][0]))&&(rtree[k + 1][0].length >= 2)) { + let mtree = rtree[k + 1]; ++k; + let maxLength = _parseNum(mtree[0][0]); + if ((maxLength < -1)||(maxLength > 0xffff)) + return [ mtree[0][1],mtree[1][2],'Tee/watch max packet length to forward invalid or out of range.' ]; + let target = mtree[1][0]; + if ((typeof target !== 'string')||(target.length !== 10)) + return [ mtree[1][1],mtree[1][2],'Missing or invalid ZeroTier address target for tee/watch.' ]; + let err = _renderMatches(mtree.slice(2),rules,macros,caps,tags,params); + if (err !== null) + return err; + rules.push({ + 'type': KEYWORD_TO_API_MAP[action], + 'address': target, + 'length': maxLength + }); + } else { + return [ rtree[k][1],rtree[k][2],'The tee and watch actions require two paremters (max length or 0 for all, target).' ]; + } + } else if (action === 'redirect') { + if (((k + 1) < rtree.length)&&(Array.isArray(rtree[k + 1][0]))&&(rtree[k + 1][0].length >= 1)) { + let mtree = rtree[k + 1]; ++k; + let target = mtree[0][0]; + if ((typeof target !== 'string')||(target.length !== 10)) + return [ mtree[0][1],mtree[0][2],'Missing or invalid ZeroTier address target for redirect.' ]; + let err = _renderMatches(mtree.slice(1),rules,macros,caps,tags,params); + if (err !== null) + return err; + rules.push({ + 'type': KEYWORD_TO_API_MAP[action], + 'address': target + }); + } else { + return [ rtree[k][1],rtree[k][2],'The redirect action requires a target parameter.' ]; + } + } else { + return [ rtree[k][1],rtree[k][2],'Unrecognized action or directive in rule set.' ]; + } + } + + return null; +} + +function compile(src,rules,caps,tags) +{ + try { + if (typeof src !== 'string') + return [ 0,0,'"src" parameter must be a string.' ]; + + // Pass 1: parse source into a tree of arrays of elements. Each element is a 3-item + // tuple consisting of string, line number, and character index in line to enable + // informative error messages to be returned. + + var blockStack = [ [] ]; + var curr = [ '',-1,-1 ]; + var skipRestOfLine = false; + for(let idx=0,lineNo=1,lineIdx=0;idx 0) { + let endOfBlock = false; + if (curr[0].charAt(curr[0].length - 1) === ';') { + endOfBlock = true; + curr[0] = curr[0].substr(0,curr[0].length - 1); + } + + if (curr[0].length > 0) { + blockStack[blockStack.length - 1].push(curr); + } + if ((endOfBlock)&&(blockStack.length > 1)&&(blockStack[blockStack.length - 1].length > 0)) { + blockStack[blockStack.length - 2].push(blockStack[blockStack.length - 1]); + blockStack.pop(); + } else if (curr[0] in OPEN_BLOCK_KEYWORDS) { + blockStack.push([]); + } + + curr = [ '',-1,-1 ]; + } + break; + default: + if (curr[0].length === 0) { + if (ch === '#') { + skipRestOfLine = true; + continue; + } else { + curr[1] = lineNo; + curr[2] = lineIdx; + } + } + curr[0] += ch; + break; + } + } + } + + if (curr[0].length > 0) { + if (curr[0].charAt(curr[0].length - 1) === ';') + curr[0] = curr[0].substr(0,curr[0].length - 1); + if (curr[0].length > 0) + blockStack[blockStack.length - 1].push(curr); + } + while ((blockStack.length > 1)&&(blockStack[blockStack.length - 1].length > 0)) { + blockStack[blockStack.length - 2].push(blockStack[blockStack.length - 1]); + blockStack.pop(); + } + var parsed = blockStack[0]; + + // Pass 2: parse tree into capabilities, tags, rule sets, and document-level rules. + + let baseRuleTree = []; + let macros = {}; + for(let i=0;i= parsed.length) || (!Array.isArray(parsed[i + 1])) || (parsed[i + 1].length < 1) || (!Array.isArray(parsed[i + 1][0])) ) + return [ parsed[i][1],parsed[i][2],'Macro definition is missing name.' ]; + let macro = parsed[++i]; + let macroName = macro[0][0].toLowerCase(); + + let params = {}; + let parenIdx = macroName.indexOf('('); + if (parenIdx > 0) { + let pns = macroName.substr(parenIdx + 1).split(/[,)]+/); + for(let k=0;k 0) + params[pns[k]] = k; + } + macroName = macroName.substr(0,parenIdx); + } + + if (!_isValidName(macroName)) + return [ macro[0][1],macro[0][2],'Invalid macro name.' ]; + if (macroName in RESERVED_WORDS) + return [ macro[0][1],macro[0][2],'Macro name is a reserved word.' ]; + + if (macroName in macros) + return [ macro[0][1],macro[0][2],'Multiple definition of macro name.' ]; + + macros[macroName] = { + params: params, + rules: macro.slice(1) + }; + } else if (keyword === 'tag') { + if ( ((i + 1) >= parsed.length) || (!Array.isArray(parsed[i + 1])) || (parsed[i + 1].length < 1) || (!Array.isArray(parsed[i + 1][0])) ) + return [ parsed[i][1],parsed[i][2],'Tag definition is missing name.' ]; + let tag = parsed[++i]; + let tagName = tag[0][0].toLowerCase(); + + if (!_isValidName(tagName)) + return [ tag[0][1],tag[0][2],'Invalid tag name.' ]; + if (tagName in RESERVED_WORDS) + return [ tag[0][1],tag[0][2],'Tag name is a reserved word.' ]; + + if (tagName in tags) + return [ tag[0][1],tag[0][2],'Multiple definition of tag name.' ]; + + let flags = {}; + let enums = {}; + let id = -1; + for(let k=1;k= 0) + return [ tag[k][1],tag[k][2],'Duplicate tag id definition.' ]; + if ((k + 1) >= tag.length) + return [ tag[k][1],tag[k][2],'Missing numeric value for ID.' ]; + id = _parseNum(tag[++k][0]); + if ((id < 0)||(id > 0xffffffff)) + return [ tag[k][1],tag[k][2],'Invalid or out of range tag ID.' ]; + } else if (tkeyword === 'flag') { + if ((k + 2) >= tag.length) + return [ tag[k][1],tag[k][2],'Missing tag flag name or bit index.' ]; + ++k; + let bits = tag[k][0].split(/[,]+/); + let mask = 0; + for(let j=0;j 31)) + return [ tag[k][1],tag[k][2],'Bit index invalid, out of range, or references an undefined flag name.' ]; + mask |= (1 << b); + } + } + let flagName = tag[++k][0].toLowerCase(); + if (!_isValidName(flagName)) + return [ tag[k][1],tag[k][2],'Invalid or reserved flag name.' ]; + if (flagName in flags) + return [ tag[k][1],tag[k][2],'Duplicate flag name in tag definition.' ]; + flags[flagName] = mask; + } else if (tkeyword === 'enum') { + if ((k + 2) >= tag.length) + return [ tag[k][1],tag[k][2],'Missing tag enum name or value.' ]; + ++k; + let value = _parseNum(tag[k][0]); + if ((value < 0)||(value > 0xffffffff)) + return [ tag[k][1],tag[k][2],'Tag enum value invalid or out of range.' ]; + let enumName = tag[++k][0].toLowerCase(); + if (!_isValidName(enumName)) + return [ tag[k][1],tag[k][2],'Invalid or reserved tag enum name.' ]; + if (enumName in enums) + return [ tag[k][1],tag[k][2],'Duplicate enum name in tag definition.' ]; + enums[enumName] = value; + } else { + return [ tag[k][1],tag[k][2],'Unrecognized keyword in tag definition.' ]; + } + } + if (id < 0) + return [ tag[0][1],tag[0][2],'Tag definition is missing a numeric ID.' ]; + + tags[tagName] = { + id: id, + enums: enums, + flags: flags + }; + } else if (keyword === 'cap') { + if ( ((i + 1) >= parsed.length) || (!Array.isArray(parsed[i + 1])) || (parsed[i + 1].length < 1) || (!Array.isArray(parsed[i + 1][0])) ) + return [ parsed[i][1],parsed[i][2],'Capability definition is missing name.' ]; + let cap = parsed[++i]; + let capName = cap[0][0].toLowerCase(); + + if (!_isValidName(capName)) + return [ cap[0][1],cap[0][2],'Invalid capability name.' ]; + if (capName in RESERVED_WORDS) + return [ cap[0][1],cap[0][2],'Capability name is a reserved word.' ]; + + if (capName in caps) + return [ cap[0][1],cap[0][2],'Multiple definition of capability name.' ]; + + let capRules = []; + let id = -1; + for(let k=1;k= 0) + return [ cap[k][1],cap[k][2],'Duplicate id directive in capability definition.' ]; + if ((k + 1) >= cap.length) + return [ cap[k][1],cap[k][2],'Missing value for ID.' ]; + id = _parseNum(cap[++k][0]); + if ((id < 0)||(id > 0xffffffff)) + return [ cap[k - 1][1],cap[k - 1][2],'Invalid or out of range capability ID.' ]; + for(let cn in caps) { + if (caps[cn].id === id) + return [ cap[k - 1][1],cap[k - 1][2],'Duplicate capability ID.' ]; + } + } else { + capRules.push(cap[k]); + } + } + if (id < 0) + return [ cap[0][1],cap[0][2],'Capability definition is missing a numeric ID.' ]; + + caps[capName] = { + id: id, + rules: capRules + }; + } else { + baseRuleTree.push(parsed[i]); + } + } + + // Pass 3: render low-level ZeroTier rules arrays for capabilities and base. + + for(let capName in caps) { + let r = []; + let err = _renderActions(caps[capName].rules,r,macros,caps,tags,{}); + if (err !== null) + return err; + caps[capName].rules = r; + } + + let err = _renderActions(baseRuleTree,rules,macros,caps,tags,{}); + if (err !== null) + return err; + + return null; + } catch (e) { + console.log(e.stack); + return [ 0,0,'Unexpected exception: '+e.toString() ]; + } +} + +exports.compile = compile; -- cgit v1.2.3 From bdadd50251bbe069dd16cadb77b1c52f30bc6517 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 14 Feb 2017 16:49:10 -0800 Subject: . --- rule-compiler/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rule-compiler') diff --git a/rule-compiler/package.json b/rule-compiler/package.json index 9bdc63ff..6fe70696 100644 --- a/rule-compiler/package.json +++ b/rule-compiler/package.json @@ -1,6 +1,6 @@ { "name": "zerotier-rule-compiler", - "version": "1.1.17", + "version": "1.1.17-0", "description": "ZeroTier Rule Script Compiler", "main": "cli.js", "scripts": { -- cgit v1.2.3 From 32f5a0ab1856485fb021fbf98920e8a9d63f0e23 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 21 Feb 2017 13:27:20 -0800 Subject: Add default tag values and default set capabilities for new members. --- controller/EmbeddedNetworkController.cpp | 121 +++++++++++++++++++++++-------- controller/EmbeddedNetworkController.hpp | 1 + rule-compiler/rule-compiler.js | 67 +++++++++++++---- 3 files changed, 144 insertions(+), 45 deletions(-) (limited to 'rule-compiler') diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index e798a80c..615cbb23 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -969,6 +969,7 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( json ncap = json::object(); const uint64_t capId = OSUtils::jsonInt(cap["id"],0ULL); ncap["id"] = capId; + ncap["default"] = OSUtils::jsonBool(cap["default"],false); json &rules = cap["rules"]; json nrules = json::array(); @@ -994,6 +995,31 @@ unsigned int EmbeddedNetworkController::handleControlPlaneHttpPOST( network["capabilities"] = ncapsa; } } + + if (b.count("tags")) { + json &tags = b["tags"]; + if (tags.is_array()) { + std::map< uint64_t,json > ntags; + for(unsigned long i=0;i::iterator t(ntags.begin());t!=ntags.end();++t) + ntagsa.push_back(t->second); + network["tags"] = ntagsa; + } + } + } catch ( ... ) { responseBody = "{ \"message\": \"exception occurred while parsing body variables\" }"; responseContentType = "application/json"; @@ -1207,6 +1233,8 @@ void EmbeddedNetworkController::_request( return; } + const bool newMember = (member.size() == 0); + json origMember(member); // for detecting modification later _initMember(member); @@ -1392,6 +1420,7 @@ void EmbeddedNetworkController::_request( json &routes = network["routes"]; json &rules = network["rules"]; json &capabilities = network["capabilities"]; + json &tags = network["tags"]; json &memberCapabilities = member["capabilities"]; json &memberTags = member["tags"]; @@ -1411,53 +1440,83 @@ void EmbeddedNetworkController::_request( } } - if ((memberCapabilities.is_array())&&(memberCapabilities.size() > 0)&&(capabilities.is_array())) { - std::map< uint64_t,json * > capsById; + std::map< uint64_t,json * > capsById; + if (!memberCapabilities.is_array()) + memberCapabilities = json::array(); + if (capabilities.is_array()) { for(unsigned long i=0;iis_object())&&(cap->size() > 0)) { - ZT_VirtualNetworkRule capr[ZT_MAX_CAPABILITY_RULES]; - unsigned int caprc = 0; - json &caprj = (*cap)["rules"]; - if ((caprj.is_array())&&(caprj.size() > 0)) { - for(unsigned long j=0;j= ZT_MAX_CAPABILITY_RULES) + if (cap.is_object()) { + const uint64_t id = OSUtils::jsonInt(cap["id"],0ULL) & 0xffffffffULL; + capsById[id] = ∩ + if ((newMember)&&(OSUtils::jsonBool(cap["default"],false))) { + bool have = false; + for(unsigned long i=0;i= ZT_MAX_NETWORK_CAPABILITIES) - break; } } } + for(unsigned long i=0;iis_object())&&(cap->size() > 0)) { + ZT_VirtualNetworkRule capr[ZT_MAX_CAPABILITY_RULES]; + unsigned int caprc = 0; + json &caprj = (*cap)["rules"]; + if ((caprj.is_array())&&(caprj.size() > 0)) { + for(unsigned long j=0;j= ZT_MAX_CAPABILITY_RULES) + break; + if (_parseRule(caprj[j],capr[caprc])) + ++caprc; + } + } + nc.capabilities[nc.capabilityCount] = Capability((uint32_t)capId,nwid,now,1,capr,caprc); + if (nc.capabilities[nc.capabilityCount].sign(_signingId,identity.address())) + ++nc.capabilityCount; + if (nc.capabilityCount >= ZT_MAX_NETWORK_CAPABILITIES) + break; + } + } + std::map< uint32_t,uint32_t > memberTagsById; if (memberTags.is_array()) { - std::map< uint32_t,uint32_t > tagsById; for(unsigned long i=0;i::const_iterator t(tagsById.begin());t!=tagsById.end();++t) { - if (nc.tagCount >= ZT_MAX_NETWORK_TAGS) - break; - nc.tags[nc.tagCount] = Tag(nwid,now,identity.address(),t->first,t->second); - if (nc.tags[nc.tagCount].sign(_signingId)) - ++nc.tagCount; + } + if (tags.is_array()) { // check network tags array for defaults that are not present in member tags + for(unsigned long i=0;i::const_iterator t(memberTagsById.begin());t!=memberTagsById.end();++t) { + if (nc.tagCount >= ZT_MAX_NETWORK_TAGS) + break; + nc.tags[nc.tagCount] = Tag(nwid,now,identity.address(),t->first,t->second); + if (nc.tags[nc.tagCount].sign(_signingId)) + ++nc.tagCount; + } } if (routes.is_array()) { diff --git a/controller/EmbeddedNetworkController.hpp b/controller/EmbeddedNetworkController.hpp index bd3a6666..3e39eaf5 100644 --- a/controller/EmbeddedNetworkController.hpp +++ b/controller/EmbeddedNetworkController.hpp @@ -165,6 +165,7 @@ private: if (!network.count("v6AssignMode")) network["v6AssignMode"] = {{"rfc4193",false},{"zt",false},{"6plane",false}}; if (!network.count("authTokens")) network["authTokens"] = nlohmann::json::array(); if (!network.count("capabilities")) network["capabilities"] = nlohmann::json::array(); + if (!network.count("tags")) network["tags"] = nlohmann::json::array(); if (!network.count("routes")) network["routes"] = nlohmann::json::array(); if (!network.count("ipAssignmentPools")) network["ipAssignmentPools"] = nlohmann::json::array(); if (!network.count("rules")) { diff --git a/rule-compiler/rule-compiler.js b/rule-compiler/rule-compiler.js index 76358606..5e8b56c8 100644 --- a/rule-compiler/rule-compiler.js +++ b/rule-compiler/rule-compiler.js @@ -19,7 +19,7 @@ const CHARACTERISTIC_BITS = { 'tcp_rs0': 11 }; -// Shorthand names for ethernet types +// Shorthand names for common ethernet types const ETHERTYPES = { 'ipv4': 0x0800, 'arp': 0x0806, @@ -31,9 +31,11 @@ const ETHERTYPES = { 'ipx_b': 0x8138 }; -// Shorthand names for IP protocols +// Shorthand names for common IP protocols const IP_PROTOCOLS = { 'icmp': 0x01, + 'icmp4': 0x01, + 'icmpv4': 0x01, 'igmp': 0x02, 'ipip': 0x04, 'tcp': 0x06, @@ -68,6 +70,7 @@ const RESERVED_WORDS = { 'macro': true, 'tag': true, 'cap': true, + 'default': true, 'drop': true, 'accept': true, @@ -176,21 +179,22 @@ const MATCH_ARG_COUNTS = { 'teq': 2 }; +// Regex of all alphanumeric characters in Unicode const INTL_ALPHANUM_REGEX = new RegExp('[0-9A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]'); + +// Checks whether something is a valid capability, tag, or macro name function _isValidName(n) { - if ((typeof n !== 'string')||(n.length === 0)) - return false; - if ("0123456789".indexOf(n.charAt(0)) >= 0) - return false; + if ((typeof n !== 'string')||(n.length === 0)) return false; + if ("0123456789".indexOf(n.charAt(0)) >= 0) return false; for(let i=0;i= parsed.length) || (!Array.isArray(parsed[i + 1])) || (parsed[i + 1].length < 1) || (!Array.isArray(parsed[i + 1][0])) ) return [ parsed[i][1],parsed[i][2],'Macro definition is missing name.' ]; let macro = parsed[++i]; @@ -711,6 +717,8 @@ function compile(src,rules,caps,tags) rules: macro.slice(1) }; } else if (keyword === 'tag') { + // Define tags + if ( ((i + 1) >= parsed.length) || (!Array.isArray(parsed[i + 1])) || (parsed[i + 1].length < 1) || (!Array.isArray(parsed[i + 1][0])) ) return [ parsed[i][1],parsed[i][2],'Tag definition is missing name.' ]; let tag = parsed[++i]; @@ -727,6 +735,7 @@ function compile(src,rules,caps,tags) let flags = {}; let enums = {}; let id = -1; + let dfl = null; for(let k=1;k 0xffffffff)) return [ tag[k][1],tag[k][2],'Invalid or out of range tag ID.' ]; + } else if (tkeyword === 'default') { + if (id >= 0) + return [ tag[k][1],tag[k][2],'Duplicate tag id definition.' ]; + if ((k + 1) >= tag.length) + return [ tag[k][1],tag[k][2],'Missing value for default.' ]; + dfl = tag[++k][0]||null; } else if (tkeyword === 'flag') { if ((k + 2) >= tag.length) return [ tag[k][1],tag[k][2],'Missing tag flag name or bit index.' ]; @@ -780,12 +795,32 @@ function compile(src,rules,caps,tags) if (id < 0) return [ tag[0][1],tag[0][2],'Tag definition is missing a numeric ID.' ]; + if (dfl) { + let dfl2 = enums[dfl]; + if (typeof dfl2 === 'number') { + dfl = dfl2; + } else { + dfl2 = flags[dfl]; + if (typeof dfl2 === 'number') { + dfl = dfl2; + } else { + dfl = _parseNum(dfl)||0; + if (dfl < 0) + dfl = 0; + else dfl &= 0xffffffff; + } + } + } + tags[tagName] = { - id: id, - enums: enums, - flags: flags + 'id': id, + 'default': dfl, + 'enums': enums, + 'flags': flags }; } else if (keyword === 'cap') { + // Define capabilities + if ( ((i + 1) >= parsed.length) || (!Array.isArray(parsed[i + 1])) || (parsed[i + 1].length < 1) || (!Array.isArray(parsed[i + 1][0])) ) return [ parsed[i][1],parsed[i][2],'Capability definition is missing name.' ]; let cap = parsed[++i]; @@ -801,8 +836,9 @@ function compile(src,rules,caps,tags) let capRules = []; let id = -1; + let dfl = false; for(let k=1;k= 0) return [ cap[k][1],cap[k][2],'Duplicate id directive in capability definition.' ]; if ((k + 1) >= cap.length) @@ -814,6 +850,8 @@ function compile(src,rules,caps,tags) if (caps[cn].id === id) return [ cap[k - 1][1],cap[k - 1][2],'Duplicate capability ID.' ]; } + } else if (cap[k][0].toLowerCase() === 'default') { + dfl = true; } else { capRules.push(cap[k]); } @@ -822,8 +860,9 @@ function compile(src,rules,caps,tags) return [ cap[0][1],cap[0][2],'Capability definition is missing a numeric ID.' ]; caps[capName] = { - id: id, - rules: capRules + 'id': id, + 'default': dfl, + 'rules': capRules }; } else { baseRuleTree.push(parsed[i]); -- cgit v1.2.3 From b475bf4a2129239a1143efb41d8ee3fa5e9037fa Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 21 Feb 2017 15:28:01 -0800 Subject: . --- rule-compiler/rule-compiler.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'rule-compiler') diff --git a/rule-compiler/rule-compiler.js b/rule-compiler/rule-compiler.js index 5e8b56c8..1b3599a3 100644 --- a/rule-compiler/rule-compiler.js +++ b/rule-compiler/rule-compiler.js @@ -747,8 +747,8 @@ function compile(src,rules,caps,tags) if ((id < 0)||(id > 0xffffffff)) return [ tag[k][1],tag[k][2],'Invalid or out of range tag ID.' ]; } else if (tkeyword === 'default') { - if (id >= 0) - return [ tag[k][1],tag[k][2],'Duplicate tag id definition.' ]; + if (dfl !== null) + return [ tag[k][1],tag[k][2],'Duplicate tag default directive.' ]; if ((k + 1) >= tag.length) return [ tag[k][1],tag[k][2],'Missing value for default.' ]; dfl = tag[++k][0]||null; -- cgit v1.2.3 From b679ebde3b05efeb3346e20dfb216cf3b3bc2b1d Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Wed, 22 Feb 2017 15:32:55 -0800 Subject: Ad-hoc networks, a cool and easy to implement little feature that allows controllerless networks. These only allow IPv6 6plane, no multicast, and the network ID encodes the allowed port range. --- include/ZeroTierOne.h | 12 ++++----- node/Network.cpp | 66 ++++++++++++++++++++++++++++++++++++++++++++++ rule-compiler/package.json | 2 +- 3 files changed, 73 insertions(+), 7 deletions(-) (limited to 'rule-compiler') diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h index 9690489a..e2380a7b 100644 --- a/include/ZeroTierOne.h +++ b/include/ZeroTierOne.h @@ -610,15 +610,15 @@ enum ZT_VirtualNetworkRuleType typedef struct { /** - * Least significant 7 bits: ZT_VirtualNetworkRuleType, most significant 1 bit is NOT bit + * Type and flags * - * If the NOT bit is set, then matches will be interpreted as "does not - * match." The NOT bit has no effect on actions. + * Bits are: NOTTTTTT * - * Use "& 0x7f" to get the enum and "& 0x80" to get the NOT flag. + * N - If true, sense of match is inverted (no effect on actions) + * O - If true, result is ORed with previous instead of ANDed (no effect on actions) + * T - Rule or action type * - * The union 'v' is a variant type, and this selects which field in 'v' is - * actually used and valid. + * AND with 0x3f to get type, 0x80 to get NOT bit, and 0x40 to get OR bit. */ uint8_t t; diff --git a/node/Network.cpp b/node/Network.cpp index 461e1c20..290ceaf9 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -1099,6 +1099,72 @@ int Network::setConfiguration(const NetworkConfig &nconf,bool saveToDisk) void Network::requestConfiguration() { + /* ZeroTier addresses can't begin with 0xff, so this is used to mark controllerless + * network IDs. Controllerless network IDs only support unicast IPv6 using the 6plane + * addressing scheme and have the following format: 0xffSSSSEEEE000000 where SSSS + * is the 16-bit starting IP port range allowed and EEEE is the 16-bit ending IP port + * range allowed. Remaining digits are reserved for future use and must be zero. */ + if ((_id >> 56) == 0xff) { + const uint16_t startPortRange = (uint16_t)((_id >> 40) & 0xffff); + const uint16_t endPortRange = (uint16_t)((_id >> 24) & 0xffff); + if (((_id & 0xffffff) == 0)&&(endPortRange >= startPortRange)) { + NetworkConfig *const nconf = new NetworkConfig(); + + nconf->networkId = _id; + nconf->timestamp = RR->node->now(); + nconf->credentialTimeMaxDelta = ZT_NETWORKCONFIG_DEFAULT_CREDENTIAL_TIME_MAX_MAX_DELTA; + nconf->revision = 1; + nconf->issuedTo = RR->identity.address(); + nconf->flags = ZT_NETWORKCONFIG_FLAG_ENABLE_IPV6_NDP_EMULATION; + nconf->staticIpCount = 1; + nconf->ruleCount = 14; + nconf->staticIps[0] = InetAddress::makeIpv66plane(_id,RR->identity.address().toInt()); + + // Drop everything but IPv6 + nconf->rules[0].t = (uint8_t)ZT_NETWORK_RULE_MATCH_ETHERTYPE | 0x80; // NOT + nconf->rules[0].v.etherType = 0x86dd; // IPv6 + nconf->rules[1].t = (uint8_t)ZT_NETWORK_RULE_ACTION_DROP; + + // Allow ICMPv6 + nconf->rules[2].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_PROTOCOL; + nconf->rules[2].v.ipProtocol = 0x3a; // ICMPv6 + nconf->rules[3].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT; + + // Allow destination ports within range + nconf->rules[4].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_PROTOCOL; + nconf->rules[4].v.ipProtocol = 0x11; // UDP + nconf->rules[5].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_PROTOCOL | 0x40; // OR + nconf->rules[5].v.ipProtocol = 0x06; // TCP + nconf->rules[6].t = (uint8_t)ZT_NETWORK_RULE_MATCH_IP_DEST_PORT_RANGE; + nconf->rules[6].v.port[0] = startPortRange; + nconf->rules[6].v.port[1] = endPortRange; + nconf->rules[7].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT; + + // Allow non-SYN TCP packets to permit non-connection-initiating traffic + nconf->rules[8].t = (uint8_t)ZT_NETWORK_RULE_MATCH_CHARACTERISTICS | 0x80; // NOT + nconf->rules[8].v.characteristics = ZT_RULE_PACKET_CHARACTERISTICS_TCP_SYN; + nconf->rules[9].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT; + + // Also allow SYN+ACK which are replies to SYN + nconf->rules[10].t = (uint8_t)ZT_NETWORK_RULE_MATCH_CHARACTERISTICS; + nconf->rules[10].v.characteristics = ZT_RULE_PACKET_CHARACTERISTICS_TCP_SYN; + nconf->rules[11].t = (uint8_t)ZT_NETWORK_RULE_MATCH_CHARACTERISTICS; + nconf->rules[11].v.characteristics = ZT_RULE_PACKET_CHARACTERISTICS_TCP_ACK; + nconf->rules[12].t = (uint8_t)ZT_NETWORK_RULE_ACTION_ACCEPT; + + nconf->rules[13].t = (uint8_t)ZT_NETWORK_RULE_ACTION_DROP; + + nconf->type = ZT_NETWORK_TYPE_PUBLIC; + Utils::snprintf(nconf->name,sizeof(nconf->name),"adhoc-%.04x-%.04x",(int)startPortRange,(int)endPortRange); + + this->setConfiguration(*nconf,false); + delete nconf; + } else { + this->setNotFound(); + } + return; + } + const Address ctrl(controller()); Dictionary rmd; diff --git a/rule-compiler/package.json b/rule-compiler/package.json index 6fe70696..16f7a23f 100644 --- a/rule-compiler/package.json +++ b/rule-compiler/package.json @@ -1,6 +1,6 @@ { "name": "zerotier-rule-compiler", - "version": "1.1.17-0", + "version": "1.1.17-1", "description": "ZeroTier Rule Script Compiler", "main": "cli.js", "scripts": { -- cgit v1.2.3 From 2ee53b0e7595dacd8a3dca59293577623435d0b2 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Wed, 22 Feb 2017 15:52:55 -0800 Subject: Fix bug in default capability flag in rule parser. --- rule-compiler/rule-compiler.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'rule-compiler') diff --git a/rule-compiler/rule-compiler.js b/rule-compiler/rule-compiler.js index 1b3599a3..d927e512 100644 --- a/rule-compiler/rule-compiler.js +++ b/rule-compiler/rule-compiler.js @@ -838,7 +838,8 @@ function compile(src,rules,caps,tags) let id = -1; let dfl = false; for(let k=1;k= 0) return [ cap[k][1],cap[k][2],'Duplicate id directive in capability definition.' ]; if ((k + 1) >= cap.length) @@ -850,7 +851,7 @@ function compile(src,rules,caps,tags) if (caps[cn].id === id) return [ cap[k - 1][1],cap[k - 1][2],'Duplicate capability ID.' ]; } - } else if (cap[k][0].toLowerCase() === 'default') { + } else if (dn === 'default') { dfl = true; } else { capRules.push(cap[k]); -- cgit v1.2.3 From 72653e54f951b2a47686d420186f59f533542940 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 23 Feb 2017 12:34:17 -0800 Subject: Finish wiring up ipauth and macauth to Network filter. --- include/ZeroTierOne.h | 10 ++++++++++ node/CertificateOfOwnership.cpp | 17 +++++++++++++++++ node/CertificateOfOwnership.hpp | 29 +++++++---------------------- node/Membership.cpp | 20 +++++++------------- node/Membership.hpp | 35 +++++++++++++++++++++++++---------- node/Network.cpp | 25 +++++++++++++++++++++++++ rule-compiler/package.json | 2 +- rule-compiler/rule-compiler.js | 2 ++ 8 files changed, 94 insertions(+), 46 deletions(-) (limited to 'rule-compiler') diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h index c1dbd8f8..ddd0b404 100644 --- a/include/ZeroTierOne.h +++ b/include/ZeroTierOne.h @@ -194,6 +194,16 @@ extern "C" { */ #define ZT_RULE_PACKET_CHARACTERISTICS_BROADCAST 0x2000000000000000ULL +/** + * Packet characteristics flag: sending IP address has a certificate of ownership + */ +#define ZT_RULE_PACKET_CHARACTERISTICS_SENDER_IP_AUTHENTICATED 0x1000000000000000ULL + +/** + * Packet characteristics flag: sending MAC address has a certificate of ownership + */ +#define ZT_RULE_PACKET_CHARACTERISTICS_SENDER_MAC_AUTHENTICATED 0x0800000000000000ULL + /** * Packet characteristics flag: TCP left-most reserved bit */ diff --git a/node/CertificateOfOwnership.cpp b/node/CertificateOfOwnership.cpp index 8305c489..6fc59ad1 100644 --- a/node/CertificateOfOwnership.cpp +++ b/node/CertificateOfOwnership.cpp @@ -43,4 +43,21 @@ int CertificateOfOwnership::verify(const RuntimeEnvironment *RR) const } } +bool CertificateOfOwnership::_owns(const CertificateOfOwnership::Thing &t,const void *v,unsigned int l) const +{ + for(unsigned int i=0,j=_thingCount;i(v)[k] != _thingValues[i][k]) + break; + ++k; + } + if (k == l) + return true; + } + } + return false; +} + } // namespace ZeroTier diff --git a/node/CertificateOfOwnership.hpp b/node/CertificateOfOwnership.hpp index 69b26aec..7e71c9b2 100644 --- a/node/CertificateOfOwnership.hpp +++ b/node/CertificateOfOwnership.hpp @@ -84,37 +84,20 @@ public: inline const Address &issuedTo() const { return _issuedTo; } - inline bool owns(const Thing &t,const void *v,unsigned int l) - { - for(unsigned int i=0,j=_thingCount;i(v)[k] != _thingValues[i][k]) - break; - ++k; - } - if (k == l) - return true; - } - } - return false; - } - - inline bool owns(const InetAddress &ip) + inline bool owns(const InetAddress &ip) const { if (ip.ss_family == AF_INET) - return this->owns(THING_IPV4_ADDRESS,&(reinterpret_cast(&ip)->sin_addr.s_addr),4); + return this->_owns(THING_IPV4_ADDRESS,&(reinterpret_cast(&ip)->sin_addr.s_addr),4); if (ip.ss_family == AF_INET6) - return this->owns(THING_IPV6_ADDRESS,reinterpret_cast(&ip)->sin6_addr.s6_addr,16); + return this->_owns(THING_IPV6_ADDRESS,reinterpret_cast(&ip)->sin6_addr.s6_addr,16); return false; } - inline bool owns(const MAC &mac) + inline bool owns(const MAC &mac) const { uint8_t tmp[6]; mac.copyTo(tmp,6); - return this->owns(THING_MAC_ADDRESS,tmp,6); + return this->_owns(THING_MAC_ADDRESS,tmp,6); } inline void addThing(const InetAddress &ip) @@ -234,6 +217,8 @@ public: inline bool operator!=(const CertificateOfOwnership &coo) const { return (memcmp(this,&coo,sizeof(CertificateOfOwnership)) != 0); } private: + bool _owns(const Thing &t,const void *v,unsigned int l) const; + uint64_t _networkId; uint64_t _ts; uint64_t _flags; diff --git a/node/Membership.cpp b/node/Membership.cpp index 1eacb93d..5facbe24 100644 --- a/node/Membership.cpp +++ b/node/Membership.cpp @@ -117,16 +117,10 @@ void Membership::pushCredentials(const RuntimeEnvironment *RR,const uint64_t now } } -const Capability *Membership::getCapability(const NetworkConfig &nconf,const uint32_t id) const -{ - const _RemoteCredential *const *c = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)id,_RemoteCredentialComp()); - return ( ((c != &(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*c)->id == (uint64_t)id)) ? ((((*c)->lastReceived)&&(_isCredentialTimestampValid(nconf,(*c)->credential,**c))) ? &((*c)->credential) : (const Capability *)0) : (const Capability *)0); -} - const Tag *Membership::getTag(const NetworkConfig &nconf,const uint32_t id) const { const _RemoteCredential *const *t = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)id,_RemoteCredentialComp()); - return ( ((t != &(_remoteTags[ZT_MAX_NETWORK_CAPABILITIES]))&&((*t)->id == (uint64_t)id)) ? ((((*t)->lastReceived)&&(_isCredentialTimestampValid(nconf,(*t)->credential,**t))) ? &((*t)->credential) : (const Tag *)0) : (const Tag *)0); + return ( ((t != &(_remoteTags[ZT_MAX_NETWORK_CAPABILITIES]))&&((*t)->id == (uint64_t)id)) ? ((((*t)->lastReceived)&&(_isCredentialTimestampValid(nconf,**t))) ? &((*t)->credential) : (const Tag *)0) : (const Tag *)0); } Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironment *RR,const NetworkConfig &nconf,const CertificateOfMembership &com) @@ -165,7 +159,7 @@ Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironme _RemoteCredential *const *htmp = std::lower_bound(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),(uint64_t)tag.id(),_RemoteCredentialComp()); _RemoteCredential *have = ((htmp != &(_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*htmp)->id == (uint64_t)tag.id())) ? *htmp : (_RemoteCredential *)0; if (have) { - if ( (!_isCredentialTimestampValid(nconf,tag,*have)) || (have->credential.timestamp() > tag.timestamp()) ) { + if ( (!_isCredentialTimestampValid(nconf,*have)) || (have->credential.timestamp() > tag.timestamp()) ) { TRACE("addCredential(Tag) for %s on %.16llx REJECTED (revoked or too old)",tag.issuedTo().toString().c_str(),tag.networkId()); return ADD_REJECTED; } @@ -195,7 +189,7 @@ Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironme _RemoteCredential *const *htmp = std::lower_bound(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),(uint64_t)cap.id(),_RemoteCredentialComp()); _RemoteCredential *have = ((htmp != &(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*htmp)->id == (uint64_t)cap.id())) ? *htmp : (_RemoteCredential *)0; if (have) { - if ( (!_isCredentialTimestampValid(nconf,cap,*have)) || (have->credential.timestamp() > cap.timestamp()) ) { + if ( (!_isCredentialTimestampValid(nconf,*have)) || (have->credential.timestamp() > cap.timestamp()) ) { TRACE("addCredential(Capability) for %s on %.16llx REJECTED (revoked or too old)",cap.issuedTo().toString().c_str(),cap.networkId()); return ADD_REJECTED; } @@ -252,7 +246,7 @@ Membership::AddCredentialResult Membership::addCredential(const RuntimeEnvironme _RemoteCredential *const *htmp = std::lower_bound(&(_remoteCoos[0]),&(_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]),(uint64_t)coo.id(),_RemoteCredentialComp()); _RemoteCredential *have = ((htmp != &(_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]))&&((*htmp)->id == (uint64_t)coo.id())) ? *htmp : (_RemoteCredential *)0; if (have) { - if ( (!_isCredentialTimestampValid(nconf,coo,*have)) || (have->credential.timestamp() > coo.timestamp()) ) { + if ( (!_isCredentialTimestampValid(nconf,*have)) || (have->credential.timestamp() > coo.timestamp()) ) { TRACE("addCredential(CertificateOfOwnership) for %s on %.16llx REJECTED (revoked or too old)",cap.issuedTo().toString().c_str(),cap.networkId()); return ADD_REJECTED; } @@ -298,7 +292,7 @@ Membership::_RemoteCredential *Membership::_newTag(const uint64_t id) t->credential = Tag(); } - std::sort(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS])); + std::sort(&(_remoteTags[0]),&(_remoteTags[ZT_MAX_NETWORK_TAGS]),_RemoteCredentialComp()); return t; } @@ -323,7 +317,7 @@ Membership::_RemoteCredential *Membership::_newCapability(const uint c->credential = Capability(); } - std::sort(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES])); + std::sort(&(_remoteCaps[0]),&(_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]),_RemoteCredentialComp()); return c; } @@ -348,7 +342,7 @@ Membership::_RemoteCredential *Membership::_newCoo(const c->credential = CertificateOfOwnership(); } - std::sort(&(_remoteCoos[0]),&(_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP])); + std::sort(&(_remoteCoos[0]),&(_remoteCoos[ZT_MAX_CERTIFICATES_OF_OWNERSHIP]),_RemoteCredentialComp()); return c; } diff --git a/node/Membership.hpp b/node/Membership.hpp index 4e9d7769..a7794328 100644 --- a/node/Membership.hpp +++ b/node/Membership.hpp @@ -99,7 +99,7 @@ public: for(;;) { if ((_i != &(_m->_remoteCaps[ZT_MAX_NETWORK_CAPABILITIES]))&&((*_i)->id != ZT_MEMBERSHIP_CRED_ID_UNUSED)) { const Capability *tmp = &((*_i)->credential); - if (_m->_isCredentialTimestampValid(*_c,*tmp,**_i)) { + if (_m->_isCredentialTimestampValid(*_c,**_i)) { ++_i; return tmp; } else ++_i; @@ -132,7 +132,7 @@ public: for(;;) { if ((_i != &(_m->_remoteTags[ZT_MAX_NETWORK_TAGS]))&&((*_i)->id != ZT_MEMBERSHIP_CRED_ID_UNUSED)) { const Tag *tmp = &((*_i)->credential); - if (_m->_isCredentialTimestampValid(*_c,*tmp,**_i)) { + if (_m->_isCredentialTimestampValid(*_c,**_i)) { ++_i; return tmp; } else ++_i; @@ -197,11 +197,24 @@ public: } /** - * @param nconf Network configuration - * @param id Capablity ID - * @return Pointer to capability or NULL if not found + * Check whether the peer represented by this Membership owns a given resource + * + * @tparam Type of resource: InetAddress or MAC + * @param nconf Our network config + * @param r Resource to check + * @return True if this peer has a certificate of ownership for the given resource */ - const Capability *getCapability(const NetworkConfig &nconf,const uint32_t id) const; + template + inline bool hasCertificateOfOwnershipFor(const NetworkConfig &nconf,const T &r) const + { + for(unsigned int i=0;iid == ZT_MEMBERSHIP_CRED_ID_UNUSED) + break; + if ((_isCredentialTimestampValid(nconf,*_remoteCoos[i]))&&(_remoteCoos[i]->credential.owns(r))) + return true; + } + return false; + } /** * @param nconf Network configuration @@ -244,11 +257,13 @@ private: bool _revokeTag(const Revocation &rev,const uint64_t now); bool _revokeCoo(const Revocation &rev,const uint64_t now); - template - inline bool _isCredentialTimestampValid(const NetworkConfig &nconf,const C &cred,const CS &state) const + template + inline bool _isCredentialTimestampValid(const NetworkConfig &nconf,const _RemoteCredential &remoteCredential) const { - const uint64_t ts = cred.timestamp(); - return ( (((ts >= nconf.timestamp) ? (ts - nconf.timestamp) : (nconf.timestamp - ts)) <= nconf.credentialTimeMaxDelta) && (ts > state.revocationThreshold) ); + if (!remoteCredential.lastReceived) + return false; + const uint64_t ts = remoteCredential.credential.timestamp(); + return ( (((ts >= nconf.timestamp) ? (ts - nconf.timestamp) : (nconf.timestamp - ts)) <= nconf.credentialTimeMaxDelta) && (ts > remoteCredential.revocationThreshold) ); } // Last time we pushed MULTICAST_LIKE(s) diff --git a/node/Network.cpp b/node/Network.cpp index 290ceaf9..50df58bb 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -299,6 +299,7 @@ static _doZtFilterResult _doZtFilter( // If this was not an ACTION evaluate next MATCH and update thisSetMatches with (AND [result]) uint8_t thisRuleMatches = 0; + uint64_t ownershipVerificationMask = 1; // this magic value means it hasn't been computed yet -- this is done lazily the first time it's needed switch(rt) { case ZT_NETWORK_RULE_MATCH_SOURCE_ZEROTIER_ADDRESS: thisRuleMatches = (uint8_t)(rules[rn].v.zt == ztSource.toInt()); @@ -507,6 +508,30 @@ static _doZtFilterResult _doZtFilter( uint64_t cf = (inbound) ? ZT_RULE_PACKET_CHARACTERISTICS_INBOUND : 0ULL; if (macDest.isMulticast()) cf |= ZT_RULE_PACKET_CHARACTERISTICS_MULTICAST; if (macDest.isBroadcast()) cf |= ZT_RULE_PACKET_CHARACTERISTICS_BROADCAST; + if (ownershipVerificationMask == 1) { + ownershipVerificationMask = 0; + InetAddress src; + if ((etherType == ZT_ETHERTYPE_IPV4)&&(frameLen >= 20)) { + src.set((const void *)(frameData + 12),4,0); + } else if ((etherType == ZT_ETHERTYPE_IPV6)&&(frameLen >= 40)) { + src.set((const void *)(frameData + 8),16,0); + } + if (inbound) { + if (membership) { + if ((src)&&(membership->hasCertificateOfOwnershipFor(nconf,src))) + ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_IP_AUTHENTICATED; + if (membership->hasCertificateOfOwnershipFor(nconf,macSource)) + ownershipVerificationMask |= ZT_RULE_PACKET_CHARACTERISTICS_SENDER_MAC_AUTHENTICATED; + } + } else { + for(unsigned int i=0;i= 20)&&(frameData[9] == 0x06)) { const unsigned int headerLen = 4 * (frameData[0] & 0xf); cf |= (uint64_t)frameData[headerLen + 13]; diff --git a/rule-compiler/package.json b/rule-compiler/package.json index 16f7a23f..f0e747a2 100644 --- a/rule-compiler/package.json +++ b/rule-compiler/package.json @@ -1,6 +1,6 @@ { "name": "zerotier-rule-compiler", - "version": "1.1.17-1", + "version": "1.1.17-2", "description": "ZeroTier Rule Script Compiler", "main": "cli.js", "scripts": { diff --git a/rule-compiler/rule-compiler.js b/rule-compiler/rule-compiler.js index d927e512..be4e5c64 100644 --- a/rule-compiler/rule-compiler.js +++ b/rule-compiler/rule-compiler.js @@ -5,6 +5,8 @@ const CHARACTERISTIC_BITS = { 'inbound': 63, 'multicast': 62, 'broadcast': 61, + 'ipauth': 60, + 'macauth': 59, 'tcp_fin': 0, 'tcp_syn': 1, 'tcp_rst': 2, -- cgit v1.2.3 From b6f87565a9b63c0695b48d8dd0036ac7fe080142 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 23 Feb 2017 16:03:43 -0800 Subject: Add wake on LAN (wol) to rules language ethertype shorthand. --- rule-compiler/rule-compiler.js | 1 + 1 file changed, 1 insertion(+) (limited to 'rule-compiler') diff --git a/rule-compiler/rule-compiler.js b/rule-compiler/rule-compiler.js index be4e5c64..7445d39f 100644 --- a/rule-compiler/rule-compiler.js +++ b/rule-compiler/rule-compiler.js @@ -25,6 +25,7 @@ const CHARACTERISTIC_BITS = { const ETHERTYPES = { 'ipv4': 0x0800, 'arp': 0x0806, + 'wol': 0x0842, 'rarp': 0x8035, 'ipv6': 0x86dd, 'atalk': 0x809b, -- cgit v1.2.3 From 2b10a982e9c9aad4a6ed9b10f5d7bda93a55ffcf Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 28 Feb 2017 09:22:10 -0800 Subject: Match on tag sender equals or tag recipient equals. --- controller/EmbeddedNetworkController.cpp | 36 ++++++++++++++++++++---------- include/ZeroTierOne.h | 2 ++ node/Capability.hpp | 8 ++++--- node/Network.cpp | 38 +++++++++++++++++++++++++++++++- rule-compiler/package.json | 2 +- rule-compiler/rule-compiler.js | 14 +++++++++--- 6 files changed, 80 insertions(+), 20 deletions(-) (limited to 'rule-compiler') diff --git a/controller/EmbeddedNetworkController.cpp b/controller/EmbeddedNetworkController.cpp index 78a9b7c7..0e57fc14 100644 --- a/controller/EmbeddedNetworkController.cpp +++ b/controller/EmbeddedNetworkController.cpp @@ -218,6 +218,16 @@ static json _renderRule(ZT_VirtualNetworkRule &rule) r["id"] = rule.v.tag.id; r["value"] = rule.v.tag.value; break; + case ZT_NETWORK_RULE_MATCH_TAG_SENDER: + r["type"] = "MATCH_TAG_SENDER"; + r["id"] = rule.v.tag.id; + r["value"] = rule.v.tag.value; + break; + case ZT_NETWORK_RULE_MATCH_TAG_RECEIVER: + r["type"] = "MATCH_TAG_RECEIVER"; + r["id"] = rule.v.tag.id; + r["value"] = rule.v.tag.value; + break; default: break; } @@ -245,6 +255,7 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule) if (OSUtils::jsonBool(r["or"],false)) rule.t |= 0x40; + bool tag = false; if (t == "ACTION_DROP") { rule.t |= ZT_NETWORK_RULE_ACTION_DROP; return true; @@ -388,26 +399,27 @@ static bool _parseRule(json &r,ZT_VirtualNetworkRule &rule) return true; } else if (t == "MATCH_TAGS_DIFFERENCE") { rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_DIFFERENCE; - rule.v.tag.id = (uint32_t)(OSUtils::jsonInt(r["id"],0ULL) & 0xffffffffULL); - rule.v.tag.value = (uint32_t)(OSUtils::jsonInt(r["value"],0ULL) & 0xffffffffULL); - return true; + tag = true; } else if (t == "MATCH_TAGS_BITWISE_AND") { rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND; - rule.v.tag.id = (uint32_t)(OSUtils::jsonInt(r["id"],0ULL) & 0xffffffffULL); - rule.v.tag.value = (uint32_t)(OSUtils::jsonInt(r["value"],0ULL) & 0xffffffffULL); - return true; + tag = true; } else if (t == "MATCH_TAGS_BITWISE_OR") { rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR; - rule.v.tag.id = (uint32_t)(OSUtils::jsonInt(r["id"],0ULL) & 0xffffffffULL); - rule.v.tag.value = (uint32_t)(OSUtils::jsonInt(r["value"],0ULL) & 0xffffffffULL); - return true; + tag = true; } else if (t == "MATCH_TAGS_BITWISE_XOR") { rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR; - rule.v.tag.id = (uint32_t)(OSUtils::jsonInt(r["id"],0ULL) & 0xffffffffULL); - rule.v.tag.value = (uint32_t)(OSUtils::jsonInt(r["value"],0ULL) & 0xffffffffULL); - return true; + tag = true; } else if (t == "MATCH_TAGS_EQUAL") { rule.t |= ZT_NETWORK_RULE_MATCH_TAGS_EQUAL; + tag = true; + } else if (t == "MATCH_TAG_SENDER") { + rule.t |= ZT_NETWORK_RULE_MATCH_TAG_SENDER; + tag = true; + } else if (t == "MATCH_TAG_RECEIVER") { + rule.t |= ZT_NETWORK_RULE_MATCH_TAG_RECEIVER; + tag = true; + } + if (tag) { rule.v.tag.id = (uint32_t)(OSUtils::jsonInt(r["id"],0ULL) & 0xffffffffULL); rule.v.tag.value = (uint32_t)(OSUtils::jsonInt(r["value"],0ULL) & 0xffffffffULL); return true; diff --git a/include/ZeroTierOne.h b/include/ZeroTierOne.h index 8a13c117..2c141f47 100644 --- a/include/ZeroTierOne.h +++ b/include/ZeroTierOne.h @@ -604,6 +604,8 @@ enum ZT_VirtualNetworkRuleType ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR = 46, ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR = 47, ZT_NETWORK_RULE_MATCH_TAGS_EQUAL = 48, + ZT_NETWORK_RULE_MATCH_TAG_SENDER = 49, + ZT_NETWORK_RULE_MATCH_TAG_RECEIVER = 50, /** * Maximum ID allowed for a MATCH entry in the rules table diff --git a/node/Capability.hpp b/node/Capability.hpp index d884625e..1ad6ea42 100644 --- a/node/Capability.hpp +++ b/node/Capability.hpp @@ -167,9 +167,6 @@ public: // rules to be ignored but still parsed. b.append((uint8_t)rules[i].t); switch((ZT_VirtualNetworkRuleType)(rules[i].t & 0x3f)) { - //case ZT_NETWORK_RULE_ACTION_DROP: - //case ZT_NETWORK_RULE_ACTION_ACCEPT: - //case ZT_NETWORK_RULE_ACTION_DEBUG_LOG: default: b.append((uint8_t)0); break; @@ -258,6 +255,9 @@ public: case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_AND: case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR: case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR: + case ZT_NETWORK_RULE_MATCH_TAGS_EQUAL: + case ZT_NETWORK_RULE_MATCH_TAG_SENDER: + case ZT_NETWORK_RULE_MATCH_TAG_RECEIVER: b.append((uint8_t)8); b.append((uint32_t)rules[i].v.tag.id); b.append((uint32_t)rules[i].v.tag.value); @@ -345,6 +345,8 @@ public: case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_OR: case ZT_NETWORK_RULE_MATCH_TAGS_BITWISE_XOR: case ZT_NETWORK_RULE_MATCH_TAGS_EQUAL: + case ZT_NETWORK_RULE_MATCH_TAG_SENDER: + case ZT_NETWORK_RULE_MATCH_TAG_RECEIVER: rules[ruleCount].v.tag.id = b.template at(p); rules[ruleCount].v.tag.value = b.template at(p + 4); break; diff --git a/node/Network.cpp b/node/Network.cpp index aad6e716..dc976f03 100644 --- a/node/Network.cpp +++ b/node/Network.cpp @@ -515,7 +515,6 @@ static _doZtFilterResult _doZtFilter( src.set((const void *)(frameData + 12),4,0); } else if ((etherType == ZT_ETHERTYPE_IPV6)&&(frameLen >= 40)) { // IPv6 NDP requires special handling, since the src and dest IPs in the packet are empty or link-local. - unsigned int pos = 0,proto = 0; if ( (frameLen >= (40 + 8 + 16)) && (frameData[6] == 0x3a) && ((frameData[40] == 0x87)||(frameData[40] == 0x88)) ) { if (frameData[40] == 0x87) { // Neighbor solicitations contain no reliable source address, so we implement a small @@ -609,6 +608,11 @@ static _doZtFilterResult _doZtFilter( thisRuleMatches = 0; FILTER_TRACE("%u %s %c remote tag %u not found -> 0 (inbound side is strict)",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id); } else { + // Outbound side is not strict since if we have to match both tags and + // we are sending a first packet to a recipient, we probably do not know + // about their tags yet. They will filter on inbound and we will filter + // once we get their tag. If we are a tee/redirect target we are also + // not strict since we likely do not have these tags. thisRuleMatches = 1; FILTER_TRACE("%u %s %c remote tag %u not found -> 1 (outbound side and TEE/REDIRECT targets are not strict)",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id); } @@ -618,6 +622,38 @@ static _doZtFilterResult _doZtFilter( FILTER_TRACE("%u %s %c local tag %u not found -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id); } } break; + case ZT_NETWORK_RULE_MATCH_TAG_SENDER: + case ZT_NETWORK_RULE_MATCH_TAG_RECEIVER: { + if (superAccept) { + thisRuleMatches = 1; + FILTER_TRACE("%u %s %c we are a TEE/REDIRECT target -> 1",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '=')); + } else if ( ((rt == ZT_NETWORK_RULE_MATCH_TAG_SENDER)&&(inbound)) || ((rt == ZT_NETWORK_RULE_MATCH_TAG_RECEIVER)&&(!inbound)) ) { + const Tag *const remoteTag = ((membership) ? membership->getTag(nconf,rules[rn].v.tag.id) : (const Tag *)0); + if (remoteTag) { + thisRuleMatches = (uint8_t)(remoteTag->value() == rules[rn].v.tag.value); + FILTER_TRACE("%u %s %c TAG %u %.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,remoteTag->value(),(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches); + } else { + if (rt == ZT_NETWORK_RULE_MATCH_TAG_RECEIVER) { + // If we are checking the receiver and this is an outbound packet, we + // can't be strict since we may not yet know the receiver's tag. + thisRuleMatches = 1; + FILTER_TRACE("%u %s %c (inbound) remote tag %u not found -> 1 (outbound receiver match is not strict)",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id); + } else { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c (inbound) remote tag %u not found -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id); + } + } + } else { // sender and outbound or receiver and inbound + const Tag *const localTag = std::lower_bound(&(nconf.tags[0]),&(nconf.tags[nconf.tagCount]),rules[rn].v.tag.id,Tag::IdComparePredicate()); + if ((localTag != &(nconf.tags[nconf.tagCount]))&&(localTag->id() == rules[rn].v.tag.id)) { + thisRuleMatches = (uint8_t)(localTag->value() == rules[rn].v.tag.value); + FILTER_TRACE("%u %s %c TAG %u %.8x == %.8x -> %u",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id,remoteTag->value(),(unsigned int)rules[rn].v.tag.value,(unsigned int)thisRuleMatches); + } else { + thisRuleMatches = 0; + FILTER_TRACE("%u %s %c local tag %u not found -> 0",rn,_rtn(rt),(((rules[rn].t & 0x80) != 0) ? '!' : '='),(unsigned int)rules[rn].v.tag.id); + } + } + } break; // The result of an unsupported MATCH is configurable at the network // level via a flag. diff --git a/rule-compiler/package.json b/rule-compiler/package.json index f0e747a2..451295a4 100644 --- a/rule-compiler/package.json +++ b/rule-compiler/package.json @@ -1,6 +1,6 @@ { "name": "zerotier-rule-compiler", - "version": "1.1.17-2", + "version": "1.1.17-3", "description": "ZeroTier Rule Script Compiler", "main": "cli.js", "scripts": { diff --git a/rule-compiler/rule-compiler.js b/rule-compiler/rule-compiler.js index 7445d39f..feb30f90 100644 --- a/rule-compiler/rule-compiler.js +++ b/rule-compiler/rule-compiler.js @@ -105,6 +105,8 @@ const RESERVED_WORDS = { 'txor': true, 'tdiff': true, 'teq': true, + 'tseq': true, + 'treq': true, 'type': true, 'enum': true, @@ -152,7 +154,9 @@ const KEYWORD_TO_API_MAP = { 'tor': 'MATCH_TAGS_BITWISE_OR', 'txor': 'MATCH_TAGS_BITWISE_XOR', 'tdiff': 'MATCH_TAGS_DIFFERENCE', - 'teq': 'MATCH_TAGS_EQUAL' + 'teq': 'MATCH_TAGS_EQUAL', + 'tseq': 'MATCH_TAG_SENDER', + 'treq': 'MATCH_TAG_RECEIVER' }; // Number of args for each match @@ -179,7 +183,9 @@ const MATCH_ARG_COUNTS = { 'tor': 2, 'txor': 2, 'tdiff': 2, - 'teq': 2 + 'teq': 2, + 'tseq': 2, + 'treq': 2 }; // Regex of all alphanumeric characters in Unicode @@ -477,7 +483,9 @@ function _renderMatches(mtree,rules,macros,caps,tags,params) case 'tor': case 'txor': case 'tdiff': - case 'teq': { + case 'teq': + case 'tseq': + case 'treq': { let tag = tags[args[0][0]]; let tagId = -1; let tagValue = -1; -- cgit v1.2.3 From 1ef3069a7ef7692bb27d64b85dd2cfdf201e33b2 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 14 Mar 2017 21:21:12 -0700 Subject: 1.2.0 release notes and a few final tweaks and cleanup. --- RELEASE-NOTES.md | 84 +++++++++++++++++++++++++++++++--------------- rule-compiler/README.md | 3 ++ rule-compiler/cli.js | 35 ++++++++++++++----- rule-compiler/package.json | 2 +- service/OneService.cpp | 8 ----- service/README.md | 4 ++- 6 files changed, 90 insertions(+), 46 deletions(-) (limited to 'rule-compiler') diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 42a2aaa4..d0ad47d8 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,32 +1,30 @@ ZeroTier Release Notes ====== -*As of 1.2.0 this will serve as a detailed changelog, which we've needed for a long time.* +# 2017-03-14 -- Version 1.2.0 -# 2017-03-13 -- Version 1.2.0 +Version 1.2.0 is a major milestone release representing almost nine months of work. It includes our rules engine for distributed network packet filtering and security monitoring, federated roots, and many other architectural and UI improvements and bug fixes. -Version 1.2.0 is a major milestone release and introduces a large number of new capabilities to the ZeroTier core network hypervisor. It also includes some security tightening, major UI improvements for Windows and Macintosh platforms, and a number of bug fixes and platform issue workarounds. - -## Features in 1.2.0 +## New Features in 1.2.0 ### The ZeroTier Rules Engine The largest new feature in 1.2.0, and the product of many months of work, is our advanced network rules engine. With this release we achieve traffic control, security monitoring, and micro-segmentation capability on par with many enterprise SDN solutions designed for use in advanced data centers and corporate networks. -Rules allow you to filter packets on your network and vector traffic to security observers (e.g. a node running Snort). Security observation can be performed in-band using REDIRECT or out of band using TEE, and for tha latter it can be done for headers only, for select traffic, or probabilistically to reduce overhead on large distributed networks. +Rules allow you to filter packets on your network and vector traffic to security observers. Security observation can be performed in-band using REDIRECT or out of band using TEE. Tags and capabilites provide advanced methods for implementing fine grained permission structures and micro-segmentation schemes without bloating the size and complexity of your rules table. -See our manual for more information. +See the [rules engine announcement blog post](https://www.zerotier.com/blog/?p=927) for an in-depth discussion of theory and implementation. The [manual](https://www.zerotier.com/manual.shtml) contains detailed information on rule, tag, and capability use, and the `rule-compiler/` subfolder of the ZeroTier source tree contains a JavaScript function to compile rules in our human-readable rule definition language into rules suitable for import into a network controller. (ZeroTier Central uses this same script to compile rules on [my.zerotier.com](https://my.zerotier.com/).) ### Root Server Federation -It's now possible to create your own root servers and add them to the root server pool on your nodes. This is done by creating what's called a "moon," which is a signed enumeration of root servers and their stable points on the network. Refer to the manual for more details on how to do this and how it works. +It's now possible to create your own root servers and add them to the root server pool on your nodes. This is done by creating what's called a "moon," which is a signed enumeration of root servers and their stable points on the network. Refer to the [manual](https://www.zerotier.com/manual.shtml) for instructions. Federated roots achieve a number of things: * You can deploy your own infrastructure to reduce dependency on ours. - * You can deploy them *inside your LAN* to ensure that network connectivity inside your facility still works if the Internet goes down. This is the first step toward making ZeroTier viable as an in-house SDN solution. + * You can deploy roots *inside your LAN* to ensure that network connectivity inside your facility still works if the Internet goes down. This is the first step toward making ZeroTier viable as an in-house SDN solution. * Roots can be deployed inside national boundaries for countries with data residency laws or "great firewalls." (As of 1.2.0 there is still no way to force all traffic to use these roots, but that will be easy to do in a later version.) * Last but not least this makes ZeroTier somewhat less centralized by eliminating any hard dependency on ZeroTier, Inc.'s infrastructure. @@ -50,23 +48,28 @@ A good software update system for Windows and Mac clients has been a missing fea We've greatly improved this mechanism in 1.2.0. Not only does it now do a better job of actually invoking the update, but it also transfers updates in-band using the ZeroTier protocol. This means it can work in environments that do not allows http/https traffic or that force it through proxies. There's also now an update channel setting: `beta` or `release` (the default). -As before software updates are authenticated in two ways: +Software updates are authenticated three ways: + + 1. ZeroTier's own signing key is used to sign all updates and this signature is checked prior to installation. ZeroTier, Inc.'s signatures are performed on an air-gapped machine. - 1. ZeroTier's own signing key is used to sign all updates and this signature is checked prior to installation. Our signatures are done on an air-gapped machine. + 2. Updates for Mac and Windows are signed using Apple and Microsoft (DigiCert EV) keys and will not install unless these signatures are also valid. - 2. Updates for Mac and Windows are signed using Apple and Microsoft (DigiCert) keys and will not install unless these signatures are also valid. + 3. The new in-band update mechanism also authenticates the source of the update via ZeroTier's built-in security features. This provides transport security, while 1 and 2 provide security of the update at rest. -Version 1.2.0's in-band mechanism effectively adds a third way: updates are fetched in-band from a designated ZeroTier node, thus authenticating the source using ZeroTier's built-in encryption and authentication mechanisms. +Updates are now configurable via `local.conf`. There are three options: `disable`, `download`, and `apply`. The third (apply) is the default for official builds on Windows and Mac, making updates happen silently and automatically as they do for popular browsers like Chrome and Firefox. Updates are disabled by default on Linux and other Unix-type systems as these are typically updated through package managers. -Updates are now configurable via `local.conf`. There are three options: `disable`, `download`, and `apply`. The third is the default for official builds on Windows and Mac, making updates happen silently and automatically as they do for popular browsers like Chrome and Firefox. For managed enterprise deployments IT people could ship a local.conf that disables updates and instead push updates via their management capabilities. Updates are disabled on Linux and other Unix-type platforms as these get updates through package repositories. +### Path Link Quality Awareness -### Path Quality Monitoring (QoS and SD-WAN phase one) +Version 1.2.0 is now aware of the link quality of direct paths with other 1.2.0 nodes. This information isn't used yet but is visible through the JSON API. (Quality always shows as 100% with pre-1.2.0 nodes.) Quality is measured passively with no additional overhead using a counter based packet loss detection algorithm. -Version 1.2.0 is now aware of the link quality of direct paths with other 1.2.0 nodes. This information isn't used yet but is visible through the JSON API. (Quality always shows as 100% with pre-1.2.0 nodes.) +This information is visible from the command line via `listpeers`: -Link quality monitoring is a precursor to intelligent multi-path and QoS support, which will in future versions bring us to feature parity with SD-WAN products like Cisco iWAN. + 200 listpeers XXXXXXXXXX 199.XXX.XXX.XXX/9993;10574;15250;1.00 48 1.2.0 LEAF + 200 listpeers XXXXXXXXXX 195.XXX.XXX.XXX/45584;467;7608;0.44 290 1.2.0 LEAF -"Connect all the things!" +The first peer's path is at 100% (1.00), while the second peer's path is suffering quite a bit of packet loss (0.44). + +Link quality awareness is a precursor to intelligent multi-path and QoS support, which will in future versions bring us to feature parity with SD-WAN products like Cisco iWAN. ### Security Improvements @@ -78,15 +81,42 @@ Revocations propagate using a "rumor mill" peer to peer algorithm. This means th ### Windows and Macintosh UI Improvements (ZeroTier One) -The Mac has a whole new UI built natively in Objective-C. It provides a pulldown similar in appearance and operation to the Mac WiFi task bar menu. The Windows UI has also been improved and now provides a task bar icon that can be right-clicked to manage networks. Both now expose managed route and IP permissions, allowing nodes to easily opt in to full tunnel operation if you have a router configured on your network. +The Mac has a whole new UI built natively in Objective-C. It provides a pulldown similar in appearance and operation to the Mac WiFi task bar menu. + +The Windows UI has also been improved and now provides a task bar icon that can be right-clicked to manage networks. Both now expose managed route and IP permissions, allowing nodes to easily opt in to full tunnel operation if you have a router configured on your network. + +### Ad-Hoc Networks + +A special kind of public network called an ad-hoc network may be accessed by joining a network ID with the format: + + ffSSSSEEEE000000 + | | | | + | | | Reserved for future use, must be 0 + | | End of port range (hex) + | Start of port range (hex) + Reserved ZeroTier address prefix indicating a controller-less network + +Ad-hoc networks are public (no access control) networks that have no network controller. Instead their configuration and other credentials are generated locally. Ad-hoc networks permit only IPv6 UDP and TCP unicast traffic (no multicast or broadcast) using 6plane format NDP-emulated IPv6 addresses. In addition an ad-hoc network ID encodes an IP port range. UDP packets and TCP SYN (connection open) packets are only allowed to desintation ports within the encoded range. + +For example `ff00160016000000` is an ad-hoc network allowing only SSH, while `ff0000ffff000000` is an ad-hoc network allowing any UDP or TCP port. + +Keep in mind that these networks are public and anyone in the entire world can join them. Care must be taken to avoid exposing vulnerable services or sharing unwanted files or other resources. + +### Network Controller (Partial) Rewrite + +The network controller has been largely rewritten to use a simple in-filesystem JSON data store in place of SQLite, and it is now included by default in all Windows, Mac, Linux, and BSD builds. This means any desktop or server node running ZeroTier One can now be a controller with no recompilation needed. + +If you have data in an old SQLite3 controller we've included a NodeJS script in `controller/migrate-sqlite` to migrate data to the new format. If you don't migrate, members will start getting `NOT_FOUND` when they attempt to query for updates. ## Major Bug Fixes in 1.2.0 - * **The Windows HyperV 100% CPU bug** - * This long-running problem turns out to have been an issue with Windows itself, but one we were triggering by placing invalid data into the Windows registry. Microsoft is aware of the issue but we've also fixed the triggering problem on our side. ZeroTier should now co-exist quite well with HyperV and should now be able to be bridged with a HyperV virtual switch. - * **Segmenation Faults on musl-libc based Linux Systems** - * Alpine Linux and some embedded Linux systems that use musl libc (a minimal libc) experienced segmentation faults. These were due to a smaller default stack size. A work-around that sets the stack size for new threads has been added. - * **Windows Firewall Blocks Local JSON API** - * On some Windows systems the firewall likes to block 127.0.0.1:9993 for mysterious reasons. This is now fixed in the installer via the addition of another firewall exemption rule. - * **UI Crash on Embedded Windows Due to Missing Fonts** - * The MSI installer now ships fonts and will install them if they are not present, so this should be fixed. + * **The Windows HyperV 100% CPU bug is FINALLY DEAD**: This long-running problem turns out to have been an issue with Windows itself, but one we were triggering by placing invalid data into the Windows registry. Microsoft is aware of the issue but we've also fixed the triggering problem on our side. ZeroTier should now co-exist quite well with HyperV and should now be able to be bridged with a HyperV virtual switch. + * **Segmenation faults on musl-libc based Linux systems**: Alpine Linux and some embedded Linux systems that use musl libc (a minimal libc) experienced segmentation faults. These were due to a smaller default stack size. A work-around that sets the stack size for new threads has been added. + * **Windows firewall blocks local JSON API**: On some Windows systems the firewall likes to block 127.0.0.1:9993 for mysterious reasons. This is now fixed in the installer via the addition of another firewall exemption rule. + * **UI crash on embedded Windows due to missing fonts**: The MSI installer now ships fonts and will install them if they are not present, so this should be fixed. + +## Other Improvements in 1.2.0 + + * **Improved dead path detection**: ZeroTier is now more aggressive about expiring paths that do not seem to be active. If a path seems marginal it is re-confirmed before re-use. + * **Minor performance improvements**: We've reduced unnecessary memcpy's and made a few other performance improvements in the core. + * **Linux static binaries**: For our official packages (the ones in the download.zerotier.com apt and yum repositories) we now build Linux binaries with static linking. Hopefully this will stop all the bug reports relating to library inconsistencies, as well as allowing our deb packages to run on a wider variety of Debian-based distributions. (There are far too many of these to support officially!) The overhead for this is very small, especially since we built our static versions against musl-libc. Distribution maintainers are of course free to build dynamically linked versions for inclusion into distributions; this only affects our official binaries. diff --git a/rule-compiler/README.md b/rule-compiler/README.md index e3aa2615..1ceeb713 100644 --- a/rule-compiler/README.md +++ b/rule-compiler/README.md @@ -3,3 +3,6 @@ ZeroTier Rules Compiler This script converts ZeroTier rules in human-readable format into rules suitable for import into a ZeroTier network controller. It's the script that is used in the rules editor on [ZeroTier Central](https://my.zerotier.com/). +A command line interface is included that may be invoked as: `node cli.js `. + +See the [manual](https://www.zerotier.com/manual.shtml) for information about the rules engine and rules script syntax. diff --git a/rule-compiler/cli.js b/rule-compiler/cli.js index c4a3b291..a0ff5197 100644 --- a/rule-compiler/cli.js +++ b/rule-compiler/cli.js @@ -1,7 +1,6 @@ 'use strict'; var fs = require('fs'); - var RuleCompiler = require('./rule-compiler.js'); if (process.argv.length < 3) { @@ -9,21 +8,39 @@ if (process.argv.length < 3) { process.exit(1); } -var src = fs.readFileSync(process.argv[2]).toString(); - var rules = []; var caps = {}; var tags = {}; -var err = RuleCompiler.compile(src,rules,caps,tags); +var err = RuleCompiler.compile(fs.readFileSync(process.argv[2]).toString(),rules,caps,tags); if (err) { - console.log('ERROR parsing '+process.argv[2]+' line '+err[0]+' column '+err[1]+': '+err[2]); + console.error('ERROR parsing '+process.argv[2]+' line '+err[0]+' column '+err[1]+': '+err[2]); process.exit(1); } else { + let capsArray = []; + let capabilitiesByName = {}; + for(let n in caps) { + capsArray.push(caps[n]); + capabilitiesByName[n] = caps[n].id; + } + let tagsArray = []; + for(let n in tags) { + let t = tags[n]; + tagsArray.push({ + 'id': t.id, + 'default': t['default']||null + }); + } + console.log(JSON.stringify({ - rules: rules, - caps: caps, - tags: tags - },null,2)); + config: { + rules: rules, + capabilities: capsArray, + tags: tagsArray + }, + capabilitiesByName: capabilitiesByName, + tagsByName: tags + },null,1)); + process.exit(0); } diff --git a/rule-compiler/package.json b/rule-compiler/package.json index 451295a4..e12d3759 100644 --- a/rule-compiler/package.json +++ b/rule-compiler/package.json @@ -1,6 +1,6 @@ { "name": "zerotier-rule-compiler", - "version": "1.1.17-3", + "version": "1.2.0-1", "description": "ZeroTier Rule Script Compiler", "main": "cli.js", "scripts": { diff --git a/service/OneService.cpp b/service/OneService.cpp index 129c0499..4a2102f1 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -745,14 +745,6 @@ public: for(int i=0;i<3;++i) _portsBE[i] = Utils::hton((uint16_t)_ports[i]); - // Check for legacy controller.db and terminate if present to prevent nasty surprises for DIY controller folks - if (OSUtils::fileExists((_homePath + ZT_PATH_SEPARATOR_S "controller.db").c_str())) { - Mutex::Lock _l(_termReason_m); - _termReason = ONE_UNRECOVERABLE_ERROR; - _fatalErrorMessage = "controller.db is present in our home path! run migrate-sqlite to migrate to new controller.d format."; - return _termReason; - } - _controller = new EmbeddedNetworkController(_node,(_homePath + ZT_PATH_SEPARATOR_S ZT_CONTROLLER_DB_PATH).c_str()); _node->setNetconfMaster((void *)_controller); diff --git a/service/README.md b/service/README.md index bdf713c1..f5223f2d 100644 --- a/service/README.md +++ b/service/README.md @@ -27,6 +27,7 @@ Settings available in `local.conf` (this is not valid JSON, and JSON does not al "primaryPort": 0-65535, /* If set, override default port of 9993 and any command line port */ "portMappingEnabled": true|false, /* If true (the default), try to use uPnP or NAT-PMP to map ports */ "softwareUpdate": "apply"|"download"|"disable", /* Automatically apply updates, just download, or disable built-in software updates */ + "softwareUpdateChannel": "release"|"beta", /* Software update channel */ "softwareUpdateDist": true|false, /* If true, distribute software updates (only really useful to ZeroTier, Inc. itself, default is false) */ "interfacePrefixBlacklist": [ "XXX",... ], /* Array of interface name prefixes (e.g. eth for eth#) to blacklist for ZT traffic */ "allowManagementFrom": "NETWORK/bits"|null /* If non-NULL, allow JSON/HTTP management from this IP network. Default is 127.0.0.1 only. */ @@ -57,7 +58,8 @@ An example `local.conf`: } }, "settings": { - "relayPolicy": "ALWAYS" + "softwareUpdate": "apply", + "softwraeUpdateChannel": "release" } } ``` -- cgit v1.2.3 From b95914844753060005395d51a7d9886cba8d17ca Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 28 Mar 2017 21:50:44 -0700 Subject: Small rule compiler fix with tag defaults. --- rule-compiler/package.json | 2 +- rule-compiler/rule-compiler.js | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) (limited to 'rule-compiler') diff --git a/rule-compiler/package.json b/rule-compiler/package.json index e12d3759..1def45a5 100644 --- a/rule-compiler/package.json +++ b/rule-compiler/package.json @@ -1,6 +1,6 @@ { "name": "zerotier-rule-compiler", - "version": "1.2.0-1", + "version": "1.2.2-1", "description": "ZeroTier Rule Script Compiler", "main": "cli.js", "scripts": { diff --git a/rule-compiler/rule-compiler.js b/rule-compiler/rule-compiler.js index feb30f90..89936213 100644 --- a/rule-compiler/rule-compiler.js +++ b/rule-compiler/rule-compiler.js @@ -762,7 +762,7 @@ function compile(src,rules,caps,tags) return [ tag[k][1],tag[k][2],'Duplicate tag default directive.' ]; if ((k + 1) >= tag.length) return [ tag[k][1],tag[k][2],'Missing value for default.' ]; - dfl = tag[++k][0]||null; + dfl = tag[++k][0]||0; } else if (tkeyword === 'flag') { if ((k + 2) >= tag.length) return [ tag[k][1],tag[k][2],'Missing tag flag name or bit index.' ]; @@ -806,7 +806,7 @@ function compile(src,rules,caps,tags) if (id < 0) return [ tag[0][1],tag[0][2],'Tag definition is missing a numeric ID.' ]; - if (dfl) { + if (dfl !== null) { let dfl2 = enums[dfl]; if (typeof dfl2 === 'number') { dfl = dfl2; @@ -815,10 +815,7 @@ function compile(src,rules,caps,tags) if (typeof dfl2 === 'number') { dfl = dfl2; } else { - dfl = _parseNum(dfl)||0; - if (dfl < 0) - dfl = 0; - else dfl &= 0xffffffff; + dfl = Math.abs(_parseNum(dfl)||0) & 0xffffffff; } } } -- cgit v1.2.3 From 3f4f7145a3814bf5e3250208186d55a1def2b3f2 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 28 Mar 2017 22:25:24 -0700 Subject: Another rule compiler fix. --- rule-compiler/cli.js | 3 ++- rule-compiler/package.json | 2 +- rule-compiler/rule-compiler.js | 8 +++++--- 3 files changed, 8 insertions(+), 5 deletions(-) (limited to 'rule-compiler') diff --git a/rule-compiler/cli.js b/rule-compiler/cli.js index a0ff5197..75235ac8 100644 --- a/rule-compiler/cli.js +++ b/rule-compiler/cli.js @@ -26,9 +26,10 @@ if (err) { let tagsArray = []; for(let n in tags) { let t = tags[n]; + let dfl = t['default']; tagsArray.push({ 'id': t.id, - 'default': t['default']||null + 'default': (((dfl)||(dfl === 0)) ? dfl : null) }); } diff --git a/rule-compiler/package.json b/rule-compiler/package.json index 1def45a5..1db006b3 100644 --- a/rule-compiler/package.json +++ b/rule-compiler/package.json @@ -1,6 +1,6 @@ { "name": "zerotier-rule-compiler", - "version": "1.2.2-1", + "version": "1.2.2-2", "description": "ZeroTier Rule Script Compiler", "main": "cli.js", "scripts": { diff --git a/rule-compiler/rule-compiler.js b/rule-compiler/rule-compiler.js index 89936213..bd84824e 100644 --- a/rule-compiler/rule-compiler.js +++ b/rule-compiler/rule-compiler.js @@ -762,7 +762,7 @@ function compile(src,rules,caps,tags) return [ tag[k][1],tag[k][2],'Duplicate tag default directive.' ]; if ((k + 1) >= tag.length) return [ tag[k][1],tag[k][2],'Missing value for default.' ]; - dfl = tag[++k][0]||0; + dfl = tag[++k][0]; } else if (tkeyword === 'flag') { if ((k + 2) >= tag.length) return [ tag[k][1],tag[k][2],'Missing tag flag name or bit index.' ]; @@ -806,7 +806,7 @@ function compile(src,rules,caps,tags) if (id < 0) return [ tag[0][1],tag[0][2],'Tag definition is missing a numeric ID.' ]; - if (dfl !== null) { + if (typeof dfl === 'string') { let dfl2 = enums[dfl]; if (typeof dfl2 === 'number') { dfl = dfl2; @@ -815,9 +815,11 @@ function compile(src,rules,caps,tags) if (typeof dfl2 === 'number') { dfl = dfl2; } else { - dfl = Math.abs(_parseNum(dfl)||0) & 0xffffffff; + dfl = Math.abs(parseInt(dfl)||0) & 0xffffffff; } } + } else if (typeof dfl === 'number') { + dfl = Math.abs(dfl) & 0xffffffff; } tags[tagName] = { -- cgit v1.2.3