netplaySniff – Diff between revs 9 and 10
?pathlinks?
Rev 9 | Rev 10 | |||
---|---|---|---|---|
Line 15... | Line 15... | |||
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') |
19 | const { Command } = require('commander') |
|
- | 20 | const net = require('net') |
||
Line 20... | Line 21... | |||
20 | |
21 | |
|
21 | // set up logger |
22 | // set up logger |
|
22 | const logger = createLogger({ |
23 | const logger = createLogger({ |
|
23 | format: format.combine( |
24 | format: format.combine( |
|
Line 33... | Line 34... | |||
33 | timestamp: true |
34 | timestamp: true |
|
34 | }), |
35 | }), |
|
35 | new transports.File( |
36 | new transports.File( |
|
36 | { |
37 | { |
|
37 | timestamp: true, |
38 | timestamp: true, |
|
38 | filename: path.join(path.dirname(fs.realpathSync(__filename)), "log/netplaySniff.log") |
39 | filename: path.join(path.dirname(fs.realpathSync(__filename)), 'log/netplaySniff.log') |
|
39 | } |
40 | } |
|
40 | ) |
41 | ) |
|
41 | ] |
42 | ] |
|
42 | }) |
43 | }) |
|
Line 56... | Line 57... | |||
56 | .command('run') |
57 | .command('run') |
|
57 | .option('-c, --config <path>', 'path to the configuration file', 'config.yml') |
58 | .option('-c, --config <path>', 'path to the configuration file', 'config.yml') |
|
58 | .option('-d, --database <path>', 'the path where to store a database', 'db/players.db') |
59 | .option('-d, --database <path>', 'the path where to store a database', 'db/players.db') |
|
59 | .description('run the program as a daemon') |
60 | .description('run the program as a daemon') |
|
60 | .action((options) => { |
61 | .action((options) => { |
|
61 | logger.info(`running as a daemon with configuraton options from ${options.config} and database at ${options.database}`) |
62 | logger.info(`running as a daemon with configuration from ${options.config} and database at ${options.database}`) |
|
Line -... | Line 63... | |||
- | 63 | |
||
- | 64 | // load configuration file. |
||
Line 62... | Line 65... | |||
62 | |
65 | var config = YAML.load(options.config) |
|
63 | |
66 | |
|
64 | // Watch the configuration file for changes. |
67 | // Watch the configuration file for changes. |
|
65 | const configWatch = inotify.addWatch({ |
68 | inotify.addWatch({ |
|
66 | path: options.config, |
69 | path: options.config, |
|
67 | watch_for: Inotify.IN_MODIFY, |
70 | watch_for: Inotify.IN_MODIFY, |
|
68 | callback: function (event) { |
71 | callback: function (event) { |
|
69 | logger.info(`Reloading configuration file config.yml`) |
- | ||
70 | config = YAML.load(options.config) |
72 | logger.info(`Reloading configuration file config.yml`) |
|
71 | config.db.file = options.database |
73 | config = YAML.load(options.config) |
|
Line 72... | Line -... | |||
72 | } |
- | ||
73 | }) |
- | ||
74 | |
- | ||
75 | // load configuration file. |
- | ||
76 | var config = YAML.load(options.config) |
- | ||
77 | // override configuration options with command-line options |
74 | } |
|
Line 78... | Line 75... | |||
78 | config.db.file = options.database |
75 | }) |
|
79 | |
76 | |
|
80 | const mqttClient = mqtt.connect(config.mqtt.connect) |
77 | const mqttClient = mqtt.connect(config.mqtt.connect) |
|
Line 104... | Line 101... | |||
104 | logger.error(`MQTT ${error}`) |
101 | logger.error(`MQTT ${error}`) |
|
105 | console.log(error) |
102 | console.log(error) |
|
106 | }) |
103 | }) |
|
Line 107... | Line 104... | |||
107 | |
104 | |
|
- | 105 | // set up packet capture |
||
- | 106 | logger.info(`setting up packet capture`) |
||
108 | // set up packet capture |
107 | |
|
109 | const cap = new Cap() |
- | ||
110 | let device = {} |
- | ||
111 | switch (config.router) { |
- | ||
112 | case 'any': |
- | ||
113 | device = config.router |
- | ||
114 | break; |
- | ||
115 | default: |
- | ||
116 | device = Cap.findDevice(`${config.router}`) |
- | ||
117 | break; |
- | ||
118 | } |
108 | const cap = new Cap() |
|
119 | const filter = `tcp and dst port ${config.netplay.port} and dst host ${config.netplay.host}` |
109 | const filter = `tcp and dst port ${config.netplay.port}` |
|
120 | const bufSize = 10 * 1024 * 1024 |
110 | const bufSize = 10 * 1024 * 1024 |
|
121 | const buffer = Buffer.alloc(65535) |
111 | const buffer = Buffer.alloc(65535) |
|
122 | const linkType = cap.open(device, filter, bufSize, buffer) |
112 | const linkType = cap.open('any', filter, bufSize, buffer) |
|
123 | cap.setMinBytes && cap.setMinBytes(0) |
113 | cap.setMinBytes && cap.setMinBytes(0) |
|
124 | cap.on('packet', (bytes, truncated) => processPacket(bytes, truncated, config, mqttClient)) |
- | ||
125 | |
114 | cap.on('packet', () => { |
|
126 | let netplay = {} |
115 | let netplay = {} |
|
127 | if (linkType !== 'ETHERNET') { |
- | ||
128 | return |
- | ||
129 | } |
- | ||
130 | |
- | ||
131 | var ret = decoders.Ethernet(buffer) |
- | ||
132 | |
- | ||
133 | if (ret.info.type !== PROTOCOL.ETHERNET.IPV4) { |
- | ||
134 | return |
- | ||
135 | } |
- | ||
136 | |
- | ||
137 | ret = decoders.IPV4(buffer, ret.offset) |
- | ||
138 | netplay.ip = ret.info.srcaddr |
- | ||
139 | |
- | ||
140 | if (ret.info.protocol !== PROTOCOL.IP.TCP) { |
116 | if (linkType !== 'ETHERNET') { |
|
141 | return |
117 | return |
|
142 | } |
- | ||
143 | |
- | ||
144 | var dataLength = ret.info.totallen - ret.hdrlen |
- | ||
145 | |
- | ||
146 | ret = decoders.TCP(buffer, ret.offset) |
- | ||
Line 147... | Line 118... | |||
147 | dataLength -= ret.hdrlen |
118 | } |
|
- | 119 | |
||
- | 120 | var ret = decoders.Ethernet(buffer) |
||
- | 121 | if (ret.info.type !== PROTOCOL.ETHERNET.IPV4) { |
||
Line 148... | Line 122... | |||
148 | |
122 | return |
|
- | 123 | } |
||
- | 124 | |
||
149 | var payload = buffer.subarray(ret.offset, ret.offset + dataLength) |
125 | ret = decoders.IPV4(buffer, ret.offset) |
|
- | 126 | netplay.ip = ret.info.srcaddr |
||
150 | |
127 | netplay.to = ret.info.dstaddr |
|
151 | // look for the NETPLAY_CMD_NICK in "netplay_private.h" data marker. |
128 | if (ret.info.protocol !== PROTOCOL.IP.TCP) { |
|
Line 152... | Line -... | |||
152 | if (payload.indexOf('0020', 0, "hex") !== 2) { |
- | ||
153 | return |
- | ||
154 | } |
- | ||
155 | |
129 | logger.info(`not a tcp protocol`) |
|
Line 156... | Line 130... | |||
156 | // remove NULL and NETPLAY_CMD_NICK |
130 | return |
|
- | 131 | } |
||
Line 157... | Line 132... | |||
157 | netplay.nick = payload.toString().replace(/[\u0000\u0020]+/gi, '') |
132 | |
|
158 | netplay.hash = shortHash(`${netplay.nick}${netplay.ip}`) |
133 | var dataLength = ret.info.totallen - ret.hdrlen |
|
159 | netplay.time = new Date().toISOString() |
134 | |
|
160 | |
135 | ret = decoders.TCP(buffer, ret.offset) |
|
161 | logger.info(`Player ${netplay.nick} joined via IP ${netplay.ip}`); |
136 | dataLength -= ret.hdrlen |
|
Line -... | Line 137... | |||
- | 137 | |
||
- | 138 | var payload = buffer.subarray(ret.offset, ret.offset + dataLength) |
||
- | 139 | // look for the NETPLAY_CMD_NICK in "netplay_private.h" data marker. |
||
- | 140 | if (payload.indexOf('0020', 0, "hex") !== 2) { |
||
- | 141 | return |
||
- | 142 | } |
||
- | 143 | |
||
162 | |
144 | // remove NULL and NETPLAY_CMD_NICK |
|
163 | const db = new sqlite.Database(config.db.file, sqlite.OPEN_CREATE | sqlite.OPEN_READWRITE | sqlite.OPEN_FULLMUTEX, (error) => { |
145 | netplay.nick = payload.toString().replace(/[\u0000\u0020]+/gi, '') |
|
164 | if (error) { |
146 | netplay.hash = shortHash(`${netplay.nick}${netplay.ip}`) |
|
165 | logger.error(`failed to open database: ${error}`) |
147 | netplay.time = new Date().toISOString() |
|
166 | return |
148 | |
|
- | 149 | logger.info(`Player ${netplay.nick} joined via IP ${netplay.ip}`); |
||
167 | } |
150 | |
|
168 | |
151 | const db = new sqlite.Database(config.db.file, sqlite.OPEN_CREATE | sqlite.OPEN_READWRITE | sqlite.OPEN_FULLMUTEX, (error) => { |
|
169 | db.run(`CREATE TABLE IF NOT EXISTS "players" ("nick" TEXT(15) NOT NULL, "ip" TEXT NOT NULL)`, (error, result) => { |
152 | if (error) { |
|
170 | if (error) { |
153 | logger.error(`failed to open database: ${config.db.file}`) |
|
171 | logger.error(`could not create database table: ${error}`); |
154 | return |
|
- | 155 | } |
||
- | 156 | |
||
- | 157 | db.run(`CREATE TABLE IF NOT EXISTS "players" ("nick" TEXT(15) NOT NULL, "ip" TEXT NOT NULL)`, (error, result) => { |
||
- | 158 | if (error) { |
||
- | 159 | logger.error(`could not create database table: ${error}`); |
||
Line 172... | Line 160... | |||
172 | return |
160 | return |
|
- | 161 | } |
||
173 | } |
162 | db.run(`INSERT INTO "players" ("nick", "ip") VALUES ($nick, $ip)`, { $nick: netplay.nick, $ip: netplay.ip }, (error) => { |
|
174 | db.run(`INSERT INTO "players" ("nick", "ip") VALUES ($nick, $ip)`, { $nick: netplay.nick, $ip: netplay.ip }, (error) => { |
163 | if (error) { |
|
175 | if (error) { |
- | ||
176 | logger.error(`could not insert player and IP into database: ${error}`) |
- | ||
177 | return |
- | ||
178 | } |
- | ||
179 | |
- | ||
180 | logger.info(`player added to database`) |
- | ||
181 | }) |
- | ||
Line 182... | Line 164... | |||
182 | }) |
164 | logger.error(`could not insert player and IP into database: ${error}`) |
|
183 | }) |
- | ||
184 | |
165 | return |
|
185 | // send data to MQTT server |
166 | } |
|
186 | const data = JSON.stringify(netplay, null, 4) |
- | ||
187 | mqttClient.publish(`${config.mqtt.topic}`, data, (error, packet) => { |
- | ||
188 | logger.info(`player data sent to MQTT broker`) |
- | ||
189 | }) |
- | ||
190 | |
- | ||
191 | // ban by nick. |
- | ||
192 | let nickBanSet = new Set(config.bans.nicknames) |
167 | |
|
193 | if (nickBanSet.has(netplay.nick)) { |
- | ||
194 | logger.info(`nick found to be banned: ${netplay.nick}`) |
- | ||
195 | exec(`iptables -t mangle -A PREROUTING -p tcp --src ${netplay.ip} --dport ${config.netplay.port} -j DROP`, (error, stdout, stderr) => { |
- | ||
196 | if (error) { |
- | ||
197 | logger.error(`Error returned while banning connecting client ${error.message}`) |
- | ||
198 | return |
- | ||
199 | } |
168 | logger.info(`player added to database`) |
|
- | 169 | }) |
||
- | 170 | }) |
||
- | 171 | }) |
||
- | 172 | |
||
- | 173 | // send data to MQTT server |
||
- | 174 | const data = JSON.stringify(netplay, null, 4) |
||
- | 175 | mqttClient.publish(`${config.mqtt.topic}`, data, (error, packet) => { |
||
- | 176 | logger.info(`player data sent to MQTT broker`) |
||
- | 177 | }) |
||
- | 178 | |
||
- | 179 | // ban by nick. |
||
- | 180 | let nickBanSet = new Set(config.bans.nicknames) |
||
- | 181 | if (nickBanSet.has(netplay.nick)) { |
||
- | 182 | logger.info(`nick found to be banned: ${netplay.nick}`) |
||
- | 183 | exec(`iptables -t mangle -A PREROUTING -p tcp --src ${netplay.ip} --dport ${config.netplay.port} -j DROP`, (error, stdout, stderr) => { |
||
- | 184 | if (error) { |
||
- | 185 | logger.error(`Error returned while banning connecting client ${error.message}`) |
||
- | 186 | return |
||
- | 187 | } |
||
- | 188 | if (stderr) { |
||
200 | if (stderr) { |
189 | logger.error(`Standard error returned ${stderr}`) |
|
201 | logger.error(`Standard error returned ${stderr}`) |
190 | return |
|
Line 202... | Line 191... | |||
202 | return |
191 | } |