iosignal 0.0.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +170 -0
  2. package/auth_file.json +13 -0
  3. package/auth_file.mjs +23 -0
  4. package/dist/iosignal.cjs +2 -0
  5. package/dist/iosignal.esm.js +3 -0
  6. package/dist/iosignal.esm.js.map +1 -0
  7. package/dist/iosignal.min.js +3 -0
  8. package/dist/iosignal.min.js.map +1 -0
  9. package/img/iosignal_stack.png +0 -0
  10. package/index.js +19 -0
  11. package/indexWebBrowser.js +13 -0
  12. package/package.json +48 -6
  13. package/rollup.config.js +69 -0
  14. package/src/api/RedisAPI.js +68 -0
  15. package/src/api/api_constant.js +7 -0
  16. package/src/api/api_reply.js +34 -0
  17. package/src/api/api_sudo.js +96 -0
  18. package/src/auth/Auth_Env.js +78 -0
  19. package/src/auth/Auth_File.js +75 -0
  20. package/src/auth/Auth_Redis.js +50 -0
  21. package/src/auth/BohoAuth.js +168 -0
  22. package/src/client/CongPacket.js +150 -0
  23. package/src/client/IOCongSocket.js +88 -0
  24. package/src/client/IOCore.js +959 -0
  25. package/src/client/IOWS.js +81 -0
  26. package/src/client/IOWebSocket.js +96 -0
  27. package/src/common/constants.js +159 -0
  28. package/src/common/quotaTable.js +61 -0
  29. package/src/common/util.js +61 -0
  30. package/src/server/FileLogger.js +30 -0
  31. package/src/server/Manager.js +521 -0
  32. package/src/server/Metric.js +154 -0
  33. package/src/server/Server.js +136 -0
  34. package/src/server/ServerRemote.js +235 -0
  35. package/src/server/ServerRemoteCore.js +467 -0
  36. package/src/server/serverOption.js +55 -0
  37. package/test-browser/favicon.ico +0 -0
  38. package/test-browser/index.html +93 -0
  39. package/test-browser/server.js +8 -0
  40. package/test-client-nodejs/client.cjs +17 -0
  41. package/test-client-nodejs/client.mjs +17 -0
  42. package/test-server/server-commonjs.cjs +8 -0
  43. package/test-server/server-esm.mjs +19 -0
  44. package/test-server/test-Auth_Env.js +17 -0
  45. package/test-server/test-Auth_File.js +9 -0
  46. package/test-server/test-api_reply.js +10 -0
  47. package/test-server/test-api_sudo.js +11 -0
@@ -0,0 +1,136 @@
1
+ import net from 'net'
2
+ import EventEmitter from 'events'
3
+ import { WebSocketServer } from 'ws'
4
+ import { Manager } from './Manager.js'
5
+ import { serverOption } from './serverOption.js'
6
+ import { STATUS } from '../api/api_constant.js'
7
+
8
+ export class Server extends EventEmitter {
9
+
10
+ constructor(options, authManager , remoteManager) {
11
+ super();
12
+ // console.log('Server input options', options )
13
+ this.manager = new Manager(this, authManager, remoteManager)
14
+ this.apiNames = new Set()
15
+
16
+ if(options.port != 0){
17
+ this.startWSServer(options)
18
+ }
19
+ if (options.congPort) {
20
+ this.startCongServer(options)
21
+ }
22
+
23
+
24
+ }
25
+
26
+
27
+ startWSServer(options) {
28
+ if (options.timeout) {
29
+ let pingT = parseInt(options.timeout)
30
+ if (pingT && pingT >= 1000) serverOption.timeout = pingT
31
+ }
32
+
33
+ if (options.monitorPeriod) {
34
+ let monitorT = parseInt(options.monitorPeriod)
35
+ if (monitorT && monitorT >= 1000) serverOption.monitorPeriod = monitorT
36
+ }
37
+
38
+ if (options.port) {
39
+ let port = parseInt(options.port)
40
+ if (port) serverOption.port = port
41
+ }
42
+
43
+ console.log('WebSocketServer listen:', options.port)
44
+ this.wss = new WebSocketServer(options)
45
+ // this.wss.setMaxListeners(0)
46
+
47
+ this.wss.on('error', (err) => {
48
+ console.error('### ws server error:', err.message)
49
+ if(err.code == 'EADDRINUSE'){
50
+ process.exit()
51
+ }
52
+ })
53
+
54
+ this.wss.on('close', (err) => {
55
+ console.log('### WS server closed.', err)
56
+ })
57
+
58
+ this.wss.on('connection', (ws, req) => {
59
+ ws.socketType = 'websocket'
60
+ this.manager.addRemote(ws, req)
61
+ })
62
+
63
+
64
+ }
65
+
66
+ startCongServer(options) {
67
+ console.log('congServer listen:', options.congPort)
68
+
69
+ this.congServer = net.createServer((socket) => {
70
+ this.manager.addRemote(socket)
71
+ })
72
+ .on('error', (err) => {
73
+ console.log('### cong server error:', err.message)
74
+ if(err.code == 'EADDRINUSE'){
75
+ process.exit()
76
+ }
77
+ }).listen(serverOption.congPort, () => {
78
+ // console.log('congsocket server bound' );
79
+ });
80
+ }
81
+
82
+
83
+
84
+ api(target, api) {
85
+
86
+ this.apiNames.add(target)
87
+
88
+ // common checkPermission
89
+ if (!api.checkPermission || typeof api.checkPermission != 'function') {
90
+ throw new Error('wrong api interface. no checkPermission function.')
91
+ }
92
+
93
+
94
+ if(typeof api.request == 'function' && Array.isArray( api.commands )){
95
+ console.log(`API TYPE1. A Class with one request function. target: ${target} list: ${api.commands}` )
96
+
97
+ this.on(target, (remote, req) => {
98
+ if (api.checkPermission(remote, req)) {
99
+ api.request(remote, req)
100
+ } else {
101
+ remote.response(req.mid, STATUS.ERROR, "NO_PERMISSION.")
102
+ }
103
+ })
104
+
105
+ }else{
106
+ let apiList = []
107
+ Object.keys(api).forEach(v => {
108
+ if (typeof api[v] === 'function') apiList.push(v)
109
+ console.log('api list',typeof api[v] ,v )
110
+ })
111
+
112
+ console.log(`API TYPE2. Multiple function list. target: ${target} list:${apiList}`)
113
+
114
+ this.on(target, (remote, req) => {
115
+ let r;
116
+ if (!api.checkPermission(remote, req)) {
117
+ r = "NO_PERMISSION."
118
+ } else {
119
+ // if (apiList.includes(req.topic)) {
120
+ if ( api[req.topic] ) {
121
+ console.log(`API TYPE2. request target: ${target} has topic:${req.topic}`)
122
+ api[req.topic](remote, req)
123
+ return
124
+ } else {
125
+ r = `target: ${req.target} has not topic name: ${req.topic}`
126
+ }
127
+ }
128
+
129
+ remote.response(req.mid, STATUS.ERROR, r)
130
+ })
131
+ }
132
+
133
+ return this
134
+ }
135
+
136
+ }
@@ -0,0 +1,235 @@
1
+ import { pack, CongRx } from '../client/CongPacket.js'
2
+ import { ServerRemoteCore } from './ServerRemoteCore.js';
3
+ import { serverOption } from './serverOption.js';
4
+ import { IOMsg } from '../common/constants.js';
5
+ import * as util from '../common/util.js'
6
+
7
+ const decoder = new TextDecoder()
8
+
9
+ export class ServerRemote extends ServerRemoteCore {
10
+ constructor(socket, req, manager) {
11
+ super(socket, manager);
12
+ this.socketType = socket.socketType
13
+
14
+ if( this.socketType === 'websocket' ){
15
+ this.TLS = req.headers["x-forwarded-proto"] === 'https' ? true : false;
16
+ this.ip = this.getRemoteIP(req);
17
+ }else{
18
+ this.TLS = false;
19
+ this.ip = this.getRemoteIP( socket.remoteAddress )
20
+ }
21
+
22
+
23
+ // default channel : HOME_CHANNEL
24
+ // console.log('connected ip:', this.ip)
25
+
26
+ if( util.isPrivateIP(this.ip)){
27
+ // connection from local or private network has default private channel.
28
+ this.HOME_CHANNEL = 'PRIVATE:' //this.manager.getPrivateChannel(this)
29
+ this.privateNode = true
30
+ }else{
31
+ // client have default channel name as global IP address.
32
+ this.HOME_CHANNEL = 'IP:'+ util.getIPv4HexString(this.ip)
33
+ }
34
+
35
+
36
+ if(this.socketType === 'websocket' ){ // WS
37
+ if(serverOption.debug.slow){
38
+ socket.on("message", this.onTimeDelayMessage.bind(this));
39
+ }else{
40
+ socket.on("message", this.onSocketMessage.bind(this));
41
+ }
42
+ socket.on("pong", this.receiveMonitor.bind(this));
43
+ socket.on("ping", this.receiveMonitor.bind(this));
44
+ socket.on("error", (e) => { console.log('Websocket error',e, e.code) });
45
+ socket.onclose = (e) => {
46
+ this.manager.removeRemote(this);
47
+ };
48
+ }else{ // TCP else
49
+ this.congRx = new CongRx();
50
+ socket.on('data', data=>{ this.congRx.write(data)} )
51
+
52
+ this.congRx.on('wrong',this.onWrongCongPacketMessage.bind(this))
53
+ // readable.pipe( destination )
54
+ // tcpSocket -> congRx(parser) -> onMessage
55
+ // socket.pipe( this.congRx )
56
+
57
+ if(serverOption.debug.slow){
58
+ this.congRx.on('data', this.onTimeDelayMessage.bind(this))
59
+ }else{
60
+ this.congRx.on('data', this.onSocketMessage.bind(this))
61
+ }
62
+
63
+ socket.on('error', e=>{ console.log('TCP Socket error',e )})
64
+ socket.on('close', e=>{
65
+ // console.log('cong socket close event')
66
+ this.manager.removeRemote(this);
67
+ })
68
+
69
+ }
70
+
71
+
72
+ }
73
+ onWrongCongPacketMessage( message){
74
+ /*
75
+ * three category CongSocket connection.
76
+ 1. anonymouse, tcp connection.
77
+ > no cid_req, no auth_req
78
+ > It's attacker. close it.
79
+ 2. not authorized but cid ready connnection.
80
+ > anonymouse remote client.
81
+ > allowed some wrong(broken) message. small quota.
82
+ 3. authorized remote connection.
83
+ > allowed some wrong(broken) message. large quota.
84
+ */
85
+ if(this.cid){ // category 2 and 3.
86
+ // temporary broken message.
87
+ console.log('wrong congpack from user:', Date(), this.ip, this.ssid, this.cid )
88
+ }else{
89
+ // attacker
90
+ let msg =""
91
+ try {
92
+ msg = decoder.decode(message)
93
+ } catch (error) {
94
+ msg += 'Len: '+ message.byteLength
95
+ }
96
+
97
+ // console.log('attacker message:', attackMsg)
98
+ if(this.manager.attackLogger){
99
+ let attackMsg = `> ${this.ssid} ${this.ip} ${msg}`
100
+ this.manager.attackLogger.log( attackMsg )
101
+ }
102
+
103
+ this.close( true );
104
+ }
105
+ }
106
+
107
+ //
108
+ permissionChecker(){
109
+
110
+ // type1.
111
+ // accept some traffic before authorize.
112
+
113
+ const limitBytes = 100
114
+
115
+ let txBytes = this.socket.bytesWritten || this.socket._socket?.bytesWritten
116
+ let rxBytes = this.socket.bytesRead || this.socket._socket?.bytesRead
117
+
118
+ if( txBytes == undefined) txBytes = 0
119
+ if( rxBytes == undefined) rxBytes = 0
120
+
121
+ // console.log( txBytes, rxBytes )
122
+
123
+
124
+ }
125
+
126
+
127
+ // any type of received messages: message, ping, pong
128
+ receiveMonitor(){
129
+ this.socket.rxCounter++
130
+ this.socket.isAlive = true;
131
+ // manager manages connection (ping) check.
132
+ }
133
+
134
+ isConnectionHTTPS(req){
135
+ //In case of reverse proxy environment. i.e. using nginx proxy_pass.
136
+ return req.headers["x-forwarded-proto"]; //https or undefined
137
+ }
138
+
139
+ // 정리요함
140
+ // nginx https reversproxy , http redirect , direct tcp
141
+ getRemoteIP(req) { //req or ip string
142
+ let ip;
143
+ if( req && req.headers ){
144
+ ip = req.headers["x-forwarded-for"];
145
+ if (ip == undefined ) ip = req.socket.remoteAddress;
146
+ }else{
147
+ ip = req;
148
+ }
149
+
150
+ if (ip) {
151
+ if (ip.indexOf("::ffff:") == 0) ip = ip.substring(7);
152
+ if (ip == "::1") ip = "127.0.0.1";
153
+ } else {
154
+ ip = "0.0.0.0";
155
+ }
156
+ // console.log('getRemoteIP', ip )
157
+
158
+ return ip;
159
+ }
160
+
161
+
162
+ ping(){
163
+ if(this.socketType === 'websocket'){
164
+ this.socket.ping()
165
+ this.socket.txCounter++
166
+ }else{
167
+ this.send( Buffer.from( [ IOMsg.PING ]))
168
+ }
169
+ }
170
+
171
+ pong(){
172
+ if(this.socketType === 'websocket'){
173
+ this.socket.pong()
174
+ this.socket.txCounter++
175
+ }else{
176
+ this.send( Buffer.from( [ IOMsg.PONG ]))
177
+ }
178
+ }
179
+
180
+ getTraffic(){
181
+ let txb = this.socket.bytesWritten || this.socket._socket?.bytesWritten
182
+ let rxb = this.socket.bytesRead || this.socket._socket?.bytesRead
183
+ let tx = this.socket.txCounter
184
+ let rx = this.socket.rxCounter
185
+ let traffic = { tx, txb, rx, rxb }
186
+ // console.log('traffic before close', traffic )
187
+ }
188
+
189
+ close( terminateNow = false){
190
+ this.getTraffic()
191
+ // let info = `call close() [${this.ssid}] socket: readyState:${this.socket.readyState} ${this.socket}`
192
+ // console.log( info )
193
+ if( this.socketType === 'websocket' ){
194
+ if( terminateNow ) this.socket.terminate();
195
+ else this.socket.close();
196
+ }else{
197
+ if( terminateNow ) this.socket.destroy();
198
+ else this.socket.end();
199
+ }
200
+ }
201
+
202
+ send( message, isBinary ){
203
+ this.manager.txBytes += message.byteLength;
204
+ this.socket.txCounter++;
205
+ if(this.socketType === 'websocket' ){ //WebSocket
206
+ if( this.socket.readyState === 1) {
207
+ if( isBinary != undefined ){
208
+ this.socket.send(message, { binary: isBinary });
209
+ }else{
210
+ this.socket.send(message );
211
+ }
212
+ }else{
213
+ // not open
214
+ // console.log('ServerRemote::send(), WS not open. #'+this.ssid + ' cid:', this.cid +":"+ this.getStateName() )
215
+ // console.log('message not sent:', message )
216
+ this.close( true )
217
+ }
218
+
219
+ }else{ //CongSocket
220
+ if( this.socket.readyState == 'open'){
221
+ this.socket.write( pack(message) )
222
+ }else{
223
+ // console.log('ServerRemote::send(), CongSocket not open #'+this.ssid + ' cid:' , this.cid + ':' + this.getStateName() )
224
+ this.close( true )
225
+ }
226
+ }
227
+
228
+ }
229
+
230
+ }
231
+
232
+
233
+ // process.on('uncaughtException', (err, origin) => {
234
+ // console.log('serverRemote::uncaughtException', err,origin)
235
+ // })