netplaySniff – Diff between revs 5 and 6
?pathlinks?
Rev 5 | Rev 6 | |||
---|---|---|---|---|
Line 14... | Line 14... | |||
14 | const shortHash = require('short-hash') |
14 | const shortHash = require('short-hash') |
|
15 | const { exec } = require("child_process") |
15 | const { exec } = require("child_process") |
|
16 | const Inotify = require('inotify-remastered').Inotify |
16 | const Inotify = require('inotify-remastered').Inotify |
|
17 | const inotify = new Inotify() |
17 | const inotify = new Inotify() |
|
18 | const sqlite = require('sqlite3') |
18 | const sqlite = require('sqlite3') |
|
- | 19 | const { Command } = require('commander') |
||
Line 19... | Line -... | |||
19 | |
- | ||
20 | // load configuration file. |
- | ||
21 | let config = YAML.load('config.yml') |
- | ||
22 | |
20 | |
|
23 | // set up logger. |
21 | // set up logger |
|
24 | const logger = createLogger({ |
22 | const logger = createLogger({ |
|
25 | format: format.combine( |
23 | format: format.combine( |
|
26 | format.timestamp({ |
24 | format.timestamp({ |
|
27 | format: 'YYYYMMDDHHmmss' |
25 | format: 'YYYYMMDDHHmmss' |
|
28 | }), |
26 | }), |
|
29 | format.printf(info => |
27 | format.printf(info => |
|
30 | `${info.timestamp} ${info.level}: ${info.message}`+(info.splat !== undefined?`${info.splat}`:" ") |
28 | `${info.timestamp} ${info.level}: ${info.message}` + (info.splat !== undefined ? `${info.splat}` : " ") |
|
31 | ) |
29 | ) |
|
32 | ), |
30 | ), |
|
33 | transports: [ |
31 | transports: [ |
|
34 | new transports.Console({ |
32 | new transports.Console({ |
|
Line 41... | Line 39... | |||
41 | } |
39 | } |
|
42 | ) |
40 | ) |
|
43 | ] |
41 | ] |
|
44 | }) |
42 | }) |
|
Line 45... | Line 43... | |||
45 | |
43 | |
|
- | 44 | const program = new Command() |
||
46 | // set up packet capture |
45 | program |
|
47 | const cap = new Cap() |
46 | .name('netplaySniff') |
|
- | 47 | .description(` |
||
48 | const device = Cap.findDevice(`${config.router}`) |
48 | +--+ |
|
49 | const filter = `tcp and dst port ${config.netplay.port} and dst host ${config.netplay.host}` |
49 | | | Monitor netplay traffic, sniff users and their |
|
- | 50 | | || | IP addresses and store them in a database. |
||
- | 51 | +--+ |
||
50 | const bufSize = 10 * 1024 * 1024 |
52 | `) |
|
51 | const buffer = Buffer.alloc(65535) |
- | ||
52 | const linkType = cap.open(device, filter, bufSize, buffer) |
53 | .version('1.0') |
|
- | 54 | |
||
- | 55 | program |
||
- | 56 | .command('run') |
||
- | 57 | .option('-c, --config <path>', 'path to the configuration file', 'config.yml') |
||
53 | |
58 | .option('-d, --database <path>', 'the path where to store a database', 'db/players.db') |
|
- | 59 | .description('run the program as a daemon') |
||
- | 60 | .action((options) => { |
||
- | 61 | logger.info(`running as a daemon: ${options.database}`) |
||
54 | cap.setMinBytes && cap.setMinBytes(0) |
62 | logger.info(`running as a daemon: ${options.config}`) |
|
- | 63 | |
||
55 | |
64 | // load configuration file. |
|
56 | let nickBanSet = new Set(config.bans.nicknames) |
65 | var config = YAML.load(options.config) |
|
57 | |
66 | |
|
58 | // Watch the configuration file for changes. |
67 | // Watch the configuration file for changes. |
|
59 | const configWatch = inotify.addWatch({ |
68 | const configWatch = inotify.addWatch({ |
|
60 | path: 'config.yml', |
69 | path: options.config, |
|
61 | watch_for: Inotify.IN_MODIFY, |
70 | watch_for: Inotify.IN_MODIFY, |
|
62 | callback: function(event) { |
71 | callback: function (event) { |
|
63 | logger.info(`Reloading configuration file config.yml`) |
72 | logger.info(`Reloading configuration file config.yml`) |
|
64 | config = YAML.load('config.yml') |
73 | config = YAML.load(options.config) |
|
65 | nickBanSet = new Set(config.bans.nicknames) |
- | ||
66 | |
74 | } |
|
67 | } |
- | ||
Line 68... | Line 75... | |||
68 | }) |
75 | }) |
|
Line 69... | Line 76... | |||
69 | |
76 | |
|
70 | const mqttClient = mqtt.connect(config.mqtt.connect) |
77 | const mqttClient = mqtt.connect(config.mqtt.connect) |
|
- | 78 | |
||
- | 79 | mqttClient.on('reconnect', () => { |
||
- | 80 | logger.info('Reconnecting to MQTT server...') |
||
- | 81 | }) |
||
- | 82 | |
||
- | 83 | mqttClient.on('connect', () => { |
||
- | 84 | logger.info('Connected to MQTT server.') |
||
- | 85 | // Subscribe to group message notifications with group name and password. |
||
- | 86 | mqttClient.subscribe(`${config.mqtt.topic}`, (error) => { |
||
- | 87 | if (error) { |
||
- | 88 | logger.info('Error subscribing to MQTT server.') |
||
- | 89 | return |
||
- | 90 | } |
||
- | 91 | |
||
71 | |
92 | logger.info('Subscribed to MQTT server.') |
|
- | 93 | }) |
||
- | 94 | }) |
||
- | 95 | |
||
Line 72... | Line 96... | |||
72 | mqttClient.on('reconnect', () => { |
96 | mqttClient.on('close', () => { |
|
73 | logger.info('Reconnecting to MQTT server...') |
97 | logger.error('Disconnected from MQTT server.') |
|
- | 98 | }) |
||
- | 99 | |
||
- | 100 | mqttClient.on('error', (error) => { |
||
- | 101 | logger.error(`MQTT ${error}`) |
||
- | 102 | console.log(error) |
||
- | 103 | }) |
||
74 | }) |
104 | |
|
- | 105 | // set up packet capture |
||
- | 106 | const cap = new Cap() |
||
75 | |
107 | const device = Cap.findDevice(`${config.router}`) |
|
- | 108 | const filter = `tcp and dst port ${config.netplay.port} and dst host ${config.netplay.host}` |
||
- | 109 | const bufSize = 10 * 1024 * 1024 |
||
- | 110 | const buffer = Buffer.alloc(65535) |
||
76 | mqttClient.on('connect', () => { |
111 | const linkType = cap.open(device, filter, bufSize, buffer) |
|
77 | logger.info('Connected to MQTT server.') |
112 | cap.setMinBytes && cap.setMinBytes(0) |
|
78 | // Subscribe to group message notifications with group name and password. |
113 | cap.on('packet', (bytes, truncated) => processPacket(bytes, truncated, config, mqttClient)) |
|
79 | mqttClient.subscribe(`${config.mqtt.topic}`, (error) => { |
114 | |
|
Line 80... | Line 115... | |||
80 | if (error) { |
115 | let netplay = {} |
|
81 | logger.info('Error subscribing to MQTT server.') |
- | ||
82 | return |
- | ||
Line 83... | Line 116... | |||
83 | } |
116 | if (linkType !== 'ETHERNET') { |
|
84 | |
117 | return |
|
85 | logger.info('Subscribed to MQTT server.') |
118 | } |
|
Line 86... | Line 119... | |||
86 | }) |
119 | |
|
87 | }) |
120 | var ret = decoders.Ethernet(buffer) |
|
- | 121 | |
||
- | 122 | if (ret.info.type !== PROTOCOL.ETHERNET.IPV4) { |
||
88 | |
123 | return |
|
- | 124 | } |
||
- | 125 | |
||
- | 126 | ret = decoders.IPV4(buffer, ret.offset) |
||
89 | mqttClient.on('close', () => { |
127 | netplay.ip = ret.info.srcaddr |
|
- | 128 | |
||
- | 129 | if (ret.info.protocol !== PROTOCOL.IP.TCP) { |
||
Line 90... | Line 130... | |||
90 | logger.error('Disconnected from MQTT server.') |
130 | return |
|
91 | }) |
- | ||
Line 92... | Line -... | |||
92 | |
- | ||
93 | mqttClient.on('error', (error) => { |
- | ||
94 | logger.error(`MQTT ${error}`) |
- | ||
95 | console.log(error) |
- | ||
96 | }) |
- | ||
97 | |
- | ||
98 | cap.on('packet', function(bytes, truncated) { |
- | ||
99 | let netplay = {} |
- | ||
100 | |
- | ||
101 | if(linkType !== 'ETHERNET') { |
- | ||
102 | return |
- | ||
103 | } |
- | ||
104 | |
- | ||
105 | var ret = decoders.Ethernet(buffer) |
- | ||
106 | |
- | ||
107 | if (ret.info.type !== PROTOCOL.ETHERNET.IPV4) { |
- | ||
108 | return |
- | ||
109 | } |
- | ||
110 | |
- | ||
111 | ret = decoders.IPV4(buffer, ret.offset) |
- | ||
112 | netplay.ip = ret.info.srcaddr |
- | ||
113 | |
- | ||
114 | if (ret.info.protocol !== PROTOCOL.IP.TCP) { |
- | ||
115 | return |
- | ||
116 | } |
131 | } |
|
117 | |
132 | |
|
118 | var dataLength = ret.info.totallen - ret.hdrlen |
- | ||
119 | |
- | ||
120 | ret = decoders.TCP(buffer, ret.offset) |
- | ||
121 | dataLength -= ret.hdrlen |
- | ||
122 | |
- | ||
123 | var payload = buffer.subarray(ret.offset, ret.offset + dataLength) |
- | ||
124 | |
- | ||
125 | // look for the NETPLAY_CMD_NICK in "netplay_private.h" data marker. |
- | ||
126 | if(payload.indexOf('0020', 0, "hex") !== 2) { |
- | ||
127 | return |
- | ||
128 | } |
- | ||
129 | |
- | ||
130 | // remove NULL and NETPLAY_CMD_NICK |
- | ||
131 | netplay.nick = payload.toString().replace(/[\u0000\u0020]+/gi, '') |
133 | var dataLength = ret.info.totallen - ret.hdrlen |
|
132 | netplay.hash = shortHash(`${netplay.nick}${netplay.ip}`) |
134 | |
|
133 | netplay.time = new Date().toISOString() |
135 | ret = decoders.TCP(buffer, ret.offset) |
|
- | 136 | dataLength -= ret.hdrlen |
||
- | 137 | |
||
- | 138 | var payload = buffer.subarray(ret.offset, ret.offset + dataLength) |
||
- | 139 | |
||
- | 140 | // look for the NETPLAY_CMD_NICK in "netplay_private.h" data marker. |
||
- | 141 | if (payload.indexOf('0020', 0, "hex") !== 2) { |
||
- | 142 | return |
||
134 | |
143 | } |
|
135 | logger.info(`Player ${netplay.nick} joined via IP ${netplay.ip}`); |
144 | |
|
136 | |
145 | // remove NULL and NETPLAY_CMD_NICK |
|
137 | const db = new sqlite.Database(config.db.file, sqlite.OPEN_CREATE | sqlite.OPEN_READWRITE | sqlite.OPEN_FULLMUTEX, (error) => { |
146 | netplay.nick = payload.toString().replace(/[\u0000\u0020]+/gi, '') |
|
138 | if(error) { |
147 | netplay.hash = shortHash(`${netplay.nick}${netplay.ip}`) |
|
- | 148 | netplay.time = new Date().toISOString() |
||
139 | logger.error(`failed to open database: ${config.db.file}`) |
149 | |
|
140 | return |
150 | logger.info(`Player ${netplay.nick} joined via IP ${netplay.ip}`); |
|
141 | } |
151 | |
|
142 | |
152 | const db = new sqlite.Database(config.db.file, sqlite.OPEN_CREATE | sqlite.OPEN_READWRITE | sqlite.OPEN_FULLMUTEX, (error) => { |
|
143 | db.run(`CREATE TABLE IF NOT EXISTS "players" ("nick" TEXT(15) NOT NULL, "ip" TEXT NOT NULL)`, (error, result) => { |
153 | if (error) { |
|
- | 154 | logger.error(`failed to open database: ${config.db.file}`) |
||
- | 155 | return |
||
- | 156 | } |
||
- | 157 | |
||
- | 158 | db.run(`CREATE TABLE IF NOT EXISTS "players" ("nick" TEXT(15) NOT NULL, "ip" TEXT NOT NULL)`, (error, result) => { |
||
144 | if(error) { |
159 | if (error) { |
|
145 | logger.error(`could not create database table: ${error}`); |
160 | logger.error(`could not create database table: ${error}`); |
|
- | 161 | return |
||
146 | return |
162 | } |
|
147 | } |
163 | db.run(`INSERT INTO "players" ("nick", "ip") VALUES ($nick, $ip)`, { $nick: netplay.nick, $ip: netplay.ip }, (error) => { |
|
148 | db.run(`INSERT INTO "players" ("nick", "ip") VALUES ($nick, $ip)`, { $nick: netplay.nick, $ip: netplay.ip }, (error) => { |
- | ||
Line 149... | Line 164... | |||
149 | if(error) { |
164 | if (error) { |
|
150 | logger.error(`could not insert player and IP into database: ${error}`) |
165 | logger.error(`could not insert player and IP into database: ${error}`) |
|
151 | return |
166 | return |
|
152 | } |
167 | } |
|
153 | |
- | ||
154 | logger.info(`player added to database`) |
- | ||
155 | }) |
- | ||
156 | }) |
- | ||
157 | }) |
- | ||
158 | |
- | ||
159 | // send data to MQTT server |
- | ||
160 | const data = JSON.stringify(netplay, null, 4) |
- | ||
161 | mqttClient.publish(`${config.mqtt.topic}`, data, (error, packet) => { |
- | ||
162 | logger.info(`player data sent to MQTT broker`) |
- | ||
163 | }) |
- | ||
164 | |
- | ||
165 | // ban by nick. |
- | ||
166 | if(nickBanSet.has(netplay.nick)) { |
- | ||
167 | logger.info(`nick found to be banned: ${netplay.nick}`) |
- | ||
168 | exec(`iptables -t mangle -A PREROUTING -p tcp --src ${netplay.ip} --dport ${config.netplay.port} -j DROP`, (error, stdout, stderr) => { |
- | ||
169 | if (error) { |
- | ||
170 | logger.error(`Error returned while banning connecting client ${error.message}`) |
- | ||
171 | return |
168 | |
|
172 | } |
- | ||
Line -... | Line 169... | |||
- | 169 | logger.info(`player added to database`) |
||
- | 170 | }) |
||
- | 171 | }) |
||
- | 172 | }) |
||
- | 173 | |
||
- | 174 | // send data to MQTT server |
||
- | 175 | const data = JSON.stringify(netplay, null, 4) |
||
- | 176 | mqttClient.publish(`${config.mqtt.topic}`, data, (error, packet) => { |
||
- | 177 | logger.info(`player data sent to MQTT broker`) |
||
- | 178 | }) |
||
- | 179 | |
||
- | 180 | // ban by nick. |
||
- | 181 | let nickBanSet = new Set(config.bans.nicknames) |
||
- | 182 | if (nickBanSet.has(netplay.nick)) { |
||
- | 183 | logger.info(`nick found to be banned: ${netplay.nick}`) |
||
- | 184 | exec(`iptables -t mangle -A PREROUTING -p tcp --src ${netplay.ip} --dport ${config.netplay.port} -j DROP`, (error, stdout, stderr) => { |
||
- | 185 | if (error) { |
||
- | 186 | logger.error(`Error returned while banning connecting client ${error.message}`) |
||
- | 187 | return |
||
- | 188 | } |
||
173 | if (stderr) { |
189 | if (stderr) { |
|
- | 190 | logger.error(`Standard error returned ${stderr}`) |