1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
|
'use strict';
var sqlite3 = require('sqlite3').verbose();
var fs = require('fs');
var async = require('async');
function blobToIPv4(b)
{
if (!b)
return null;
if (b.length !== 16)
return null;
return b.readUInt8(12).toString()+'.'+b.readUInt8(13).toString()+'.'+b.readUInt8(14).toString()+'.'+b.readUInt8(15).toString();
}
function blobToIPv6(b)
{
if (!b)
return null;
if (b.length !== 16)
return null;
var s = '';
for(var i=0;i<16;++i) {
var x = b.readUInt8(i).toString(16);
if (x.length === 1)
s += '0';
s += x;
if ((((i+1) & 1) === 0)&&(i !== 15))
s += ':';
}
return s;
}
if (process.argv.length !== 4) {
console.log('ZeroTier Old Sqlite3 Controller DB Migration Utility');
console.log('(c)2017 ZeroTier, Inc. [GPL3]');
console.log('');
console.log('Usage: node migrate.js </path/to/controller.db> </path/to/controller.d>');
console.log('');
console.log('The first argument must be the path to the old Sqlite3 controller.db');
console.log('file. The second must be the path to the EMPTY controller.d database');
console.log('directory for a new (1.1.17 or newer) controller. If this path does');
console.log('not exist it will be created.');
console.log('');
console.log('WARNING: this will ONLY work correctly on a 1.1.14 controller database.');
console.log('If your controller is old you should first upgrade to 1.1.14 and run the');
console.log('controller so that it will brings its Sqlite3 database up to the latest');
console.log('version before running this migration.');
console.log('');
process.exit(1);
}
var oldDbPath = process.argv[2];
var newDbPath = process.argv[3];
console.log('Starting migrate of "'+oldDbPath+'" to "'+newDbPath+'"...');
console.log('');
var old = new sqlite3.Database(oldDbPath);
var networks = {};
var nodeIdentities = {};
var networkCount = 0;
var memberCount = 0;
var routeCount = 0;
var ipAssignmentPoolCount = 0;
var ipAssignmentCount = 0;
var ruleCount = 0;
var oldSchemaVersion = -1;
async.series([function(nextStep) {
old.each('SELECT v from Config WHERE k = \'schemaVersion\'',function(err,row) {
oldSchemaVersion = parseInt(row.v)||-1;
},nextStep);
},function(nextStep) {
if (oldSchemaVersion !== 4) {
console.log('FATAL: this MUST be run on a 1.1.14 controller.db! Upgrade your old');
console.log('controller to 1.1.14 first and run it once to bring its DB up to date.');
return process.exit(1);
}
console.log('Reading networks...');
old.each('SELECT * FROM Network',function(err,row) {
if ((typeof row.id === 'string')&&(row.id.length === 16)) {
var flags = parseInt(row.flags)||0;
networks[row.id] = {
id: row.id,
nwid: row.id,
objtype: 'network',
authTokens: [],
capabilities: [],
creationTime: parseInt(row.creationTime)||0,
enableBroadcast: !!row.enableBroadcast,
ipAssignmentPools: [],
multicastLimit: row.multicastLimit||32,
name: row.name||'',
private: !!row.private,
revision: parseInt(row.revision)||1,
rules: [{ 'type': 'ACTION_ACCEPT' }], // populated later if there are defined rules, otherwise default is allow all
routes: [],
v4AssignMode: {
'zt': ((flags & 1) !== 0)
},
v6AssignMode: {
'6plane': ((flags & 4) !== 0),
'rfc4193': ((flags & 2) !== 0),
'zt': ((flags & 8) !== 0)
},
_members: {} // temporary
};
++networkCount;
//console.log(networks[row.id]);
}
},nextStep);
},function(nextStep) {
console.log(' '+networkCount+' networks.');
console.log('Reading network route definitions...');
old.each('SELECT * from Route WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) {
var network = networks[row.networkId];
if (network) {
var rt = {
target: (((row.ipVersion == 4) ? blobToIPv4(row.target) : blobToIPv6(row.target))+'/'+row.targetNetmaskBits),
via: ((row.via) ? ((row.ipVersion == 4) ? blobToIPv4(row.via) : blobToIPv6(row.via)) : null)
};
network.routes.push(rt);
++routeCount;
}
},nextStep);
},function(nextStep) {
console.log(' '+routeCount+' routes in '+networkCount+' networks.');
console.log('Reading IP assignment pools...');
old.each('SELECT * FROM IpAssignmentPool WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) {
var network = networks[row.networkId];
if (network) {
var p = {
ipRangeStart: ((row.ipVersion == 4) ? blobToIPv4(row.ipRangeStart) : blobToIPv6(row.ipRangeStart)),
ipRangeEnd: ((row.ipVersion == 4) ? blobToIPv4(row.ipRangeEnd) : blobToIPv6(row.ipRangeEnd))
};
network.ipAssignmentPools.push(p);
++ipAssignmentPoolCount;
}
},nextStep);
},function(nextStep) {
console.log(' '+ipAssignmentPoolCount+' IP assignment pools in '+networkCount+' networks.');
console.log('Reading known node identities...');
old.each('SELECT * FROM Node',function(err,row) {
nodeIdentities[row.id] = row.identity;
},nextStep);
},function(nextStep) {
console.log(' '+Object.keys(nodeIdentities).length+' known identities.');
console.log('Reading network members...');
old.each('SELECT * FROM Member',function(err,row) {
var network = networks[row.networkId];
if (network) {
network._members[row.nodeId] = {
id: row.nodeId,
address: row.nodeId,
objtype: 'member',
authorized: !!row.authorized,
activeBridge: !!row.activeBridge,
authHistory: [],
capabilities: [],
creationTime: 0,
identity: nodeIdentities[row.nodeId]||null,
ipAssignments: [],
lastAuthorizedTime: (row.authorized) ? Date.now() : 0,
lastDeauthorizedTime: (row.authorized) ? 0 : Date.now(),
lastRequestMetaData: '',
noAutoAssignIps: false,
nwid: row.networkId,
revision: parseInt(row.memberRevision)||1,
tags: [],
recentLog: []
};
++memberCount;
//console.log(network._members[row.nodeId]);
}
},nextStep);
},function(nextStep) {
console.log(' '+memberCount+' members of '+networkCount+' networks.');
console.log('Reading static IP assignments...');
old.each('SELECT * FROM IpAssignment WHERE ipVersion = 4 OR ipVersion = 6',function(err,row) {
var network = networks[row.networkId];
if (network) {
var member = network._members[row.nodeId];
if ((member)&&((member.authorized)||(!network['private']))) { // don't mirror assignments to unauthorized members to avoid conflicts
if (row.ipVersion == 4) {
member.ipAssignments.push(blobToIPv4(row.ip));
++ipAssignmentCount;
} else if (row.ipVersion == 6) {
member.ipAssignments.push(blobToIPv6(row.ip));
++ipAssignmentCount;
}
}
}
},nextStep);
},function(nextStep) {
// Old versions only supported Ethertype whitelisting, so that's
// all we mirror forward. The other fields were always unused.
console.log(' '+ipAssignmentCount+' IP assignments for '+memberCount+' authorized members of '+networkCount+' networks.');
console.log('Reading allowed Ethernet types (old basic rules)...');
var etherTypesByNetwork = {};
old.each('SELECT DISTINCT networkId,ruleNo,etherType FROM Rule WHERE "action" = \'accept\'',function(err,row) {
if (row.networkId in networks) {
var et = parseInt(row.etherType)||0;
var ets = etherTypesByNetwork[row.networkId];
if (!ets)
etherTypesByNetwork[row.networkId] = [ et ];
else ets.push(et);
}
},function(err) {
if (err) return nextStep(err);
for(var nwid in etherTypesByNetwork) {
var ets = etherTypesByNetwork[nwid].sort();
var network = networks[nwid];
if (network) {
var rules = [];
if (ets.indexOf(0) >= 0) {
// If 0 is in the list, all Ethernet types are allowed so we accept all.
rules.push({ 'type': 'ACTION_ACCEPT' });
} else {
// Otherwise we whitelist.
for(var i=0;i<ets.length;++i) {
rules.push({
'etherType': ets[i],
'not': true,
'or': false,
'type': 'MATCH_ETHERTYPE'
});
}
rules.push({ 'type': 'ACTION_DROP' });
rules.push({ 'type': 'ACTION_ACCEPT' });
}
network.rules = rules;
++ruleCount;
}
}
return nextStep(null);
});
}],function(err) {
if (err) {
console.log('FATAL: '+err.toString());
return process.exit(1);
}
console.log(' '+ruleCount+' ethernet type whitelists converted to new format rules.');
old.close();
console.log('Done reading and converting Sqlite3 database! Writing JSONDB files...');
try {
fs.mkdirSync(newDbPath,0o700);
} catch (e) {}
var nwBase = newDbPath+'/network';
try {
fs.mkdirSync(nwBase,0o700);
} catch (e) {}
nwBase = nwBase + '/';
var nwids = Object.keys(networks).sort();
var fileCount = 0;
for(var ni=0;ni<nwids.length;++ni) {
var network = networks[nwids[ni]];
var mids = Object.keys(network._members).sort();
if (mids.length > 0) {
try {
fs.mkdirSync(nwBase+network.id);
} catch (e) {}
var mbase = nwBase+network.id+'/member';
try {
fs.mkdirSync(mbase,0o700);
} catch (e) {}
mbase = mbase + '/';
for(var mi=0;mi<mids.length;++mi) {
var member = network._members[mids[mi]];
fs.writeFileSync(mbase+member.id+'.json',JSON.stringify(member,null,1),{ mode: 0o600 });
++fileCount;
//console.log(mbase+member.id+'.json');
}
}
delete network._members; // temporary field, not part of actual JSONDB, so don't write
fs.writeFileSync(nwBase+network.id+'.json',JSON.stringify(network,null,1),{ mode: 0o600 });
++fileCount;
//console.log(nwBase+network.id+'.json');
}
console.log('');
console.log('SUCCESS! Wrote '+fileCount+' JSONDB files.');
console.log('');
console.log('You should still inspect the new DB before going live. Also be sure');
console.log('to "chown -R" and "chgrp -R" the new DB to the user and group under');
console.log('which the ZeroTier One instance acting as controller will be running.');
console.log('The controller must be able to read and write the DB, of course.');
console.log('');
console.log('Have fun!');
return process.exit(0);
});
|