netplaySniff – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 #!/usr/bin/env node
2 ///////////////////////////////////////////////////////////////////////////
3 // Copyright (C) 2023 Wizardry and Steamworks - License: MIT //
4 ///////////////////////////////////////////////////////////////////////////
5  
6 const fs = require('fs')
7 const path = require('path')
8 const { createLogger, format, transports } = require('winston')
9 const mqtt = require('mqtt')
10 const YAML = require('yamljs')
11 const Cap = require('cap').Cap
12 const decoders = require('cap').decoders
13 const PROTOCOL = decoders.PROTOCOL
14 const shortHash = require('short-hash')
15 const { exec } = require("child_process")
16 const Inotify = require('inotify-remastered').Inotify
17 const inotify = new Inotify()
18  
19 // load configuration file.
20 let config = YAML.load('config.yml')
21  
22 // set up logger.
23 const logger = createLogger({
24 format: format.combine(
25 format.splat(),
26 format.simple()
27 ),
28 transports: [
29 new transports.Console({
30 timestamp: true
31 }),
32 new transports.File(
33 {
34 timestamp: true,
35 filename: path.join(path.dirname(fs.realpathSync(__filename)), "log/netplaySniff.log")
36 }
37 )
38 ]
39 })
40  
41 // set up packet capture
42 const cap = new Cap()
43 const device = Cap.findDevice(`${config.router}`)
44 const filter = `tcp and dst port ${config.netplay.port} and dst host ${config.netplay.host}`
45 const bufSize = 10 * 1024 * 1024
46 const buffer = Buffer.alloc(65535)
47 const linkType = cap.open(device, filter, bufSize, buffer)
48  
49 cap.setMinBytes && cap.setMinBytes(0)
50  
51 let nickBanSet = new Set(config.bans.nicknames)
52  
53 // Watch the configuration file for changes.
54 const configWatch = inotify.addWatch({
55 path: 'config.yml',
56 watch_for: Inotify.IN_MODIFY,
57 callback: function(event) {
58 logger.info(`Reloading configuration file config.yml`)
59 config = YAML.load('config.yml')
60 nickBanSet = new Set(config.bans.nicknames)
61  
62 }
63 })
64  
65 const mqttClient = mqtt.connect(config.mqtt.connect)
66  
67 mqttClient.on('reconnect', () => {
68 logger.info('Reconnecting to Corrade MQTT server...')
69 })
70  
71 mqttClient.on('connect', () => {
72 logger.info('Connected to Corrade MQTT server.')
73 // Subscribe to group message notifications with group name and password.
74 mqttClient.subscribe(`${config.mqtt.topic}`, (error) => {
75 if (error) {
76 logger.info('Error subscribing to Corrade MQTT group messages.')
77 return
78 }
79  
80 logger.info('Subscribed to Corrade MQTT group messages.')
81 })
82 })
83  
84 mqttClient.on('close', () => {
85 logger.error('Disconnected from Corrade MQTT server...')
86 })
87  
88 mqttClient.on('error', (error) => {
89 logger.error(`Error found while connecting to Corrade MQTT server ${error}`)
90 })
91  
92 cap.on('packet', function(bytes, truncated) {
93 let netplay = {}
94  
95 if(linkType !== 'ETHERNET') {
96 return
97 }
98  
99 var ret = decoders.Ethernet(buffer)
100  
101 if (ret.info.type !== PROTOCOL.ETHERNET.IPV4) {
102 return
103 }
104  
105 ret = decoders.IPV4(buffer, ret.offset)
106 netplay.ip = ret.info.srcaddr
107  
108 if (ret.info.protocol !== PROTOCOL.IP.TCP) {
109 return
110 }
111  
112 var dataLength = ret.info.totallen - ret.hdrlen
113  
114 ret = decoders.TCP(buffer, ret.offset)
115 dataLength -= ret.hdrlen
116  
117 var payload = buffer.subarray(ret.offset, ret.offset + dataLength)
118  
119 // look for the NETPLAY_CMD_NICK in "netplay_private.h" data marker.
120 if(payload.indexOf('0020', 0, "hex") !== 2) {
121 return
122 }
123  
124 // remove NULL and NETPLAY_CMD_NICK
125 netplay.nick = payload.toString().replace(/[\u0000\u0020]+/gi, '')
126 netplay.hash = shortHash(`${netplay.nick}${netplay.ip}`)
127 netplay.time = new Date().toISOString()
128  
129 // ban by nick.
130 if(nickBanSet.has(netplay.nick)) {
131 logger.info(`nick found to be banned: ${netplay.nick}`)
132 exec(`/usr/sbin/iptables -t mangle -A PREROUTING -p tcp --src ${netplay.ip} --dport ${config.netplay.port} -j DROP`, (error, stdout, stderr) => {
133 if (error) {
134 logger.error(`Error returned while banning connecting client ${error.message}`)
135 return
136 }
137 if (stderr) {
138 logger.error(`Standard error returned ${stderr}`)
139 return
140 }
141 if(stdout) {
142 logger.info(`Standard error reported while banning ${typeof stdout}`)
143 return
144 }
145  
146 // only send notification if the connecting nick isn't banned
147 const data = JSON.stringify(netplay, null, 4)
148 mqttClient.publish(`${config.mqtt.topic}`, data)
149 })
150 }
151  
152 })