netplaySniff – Diff between revs 5 and 6

Subversion Repositories:
Rev:
Show entire fileIgnore whitespace
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}`)