├── server
├── obj
│ └── .gitignore
├── .gitignore
├── Makefile
├── src
│ ├── server.h
│ ├── network.h
│ ├── network.cpp
│ ├── network_client.cpp
│ ├── main.h
│ ├── netban.h
│ ├── server.cpp
│ ├── argparse.c
│ ├── netban.cpp
│ └── json.c
├── config.json
└── include
│ ├── argparse.h
│ ├── detect.h
│ ├── json.h
│ └── system.h
├── web
├── json
│ └── .gitignore
├── favicon.svg
├── index.html
└── css
│ └── app.css
├── .gitignore
├── plugin
├── Dockerfile-telegram
├── docker-compose-telegram.yml
└── bot-telegram.py
├── service
├── status-client.service
└── status-server.service
├── docker-compose.yml
├── Dockerfile
├── LICENSE
├── README.md
└── clients
├── client-psutil.py
└── client-linux.py
/server/obj/.gitignore:
--------------------------------------------------------------------------------
1 | *.o
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | sergate
2 | .tags*
3 |
--------------------------------------------------------------------------------
/web/json/.gitignore:
--------------------------------------------------------------------------------
1 | stats.json
2 | stats.json~
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | default.sublime-workspace
2 |
3 | # pycharm
4 | .idea
5 |
--------------------------------------------------------------------------------
/plugin/Dockerfile-telegram:
--------------------------------------------------------------------------------
1 | FROM python:alpine
2 |
3 | LABEL maintainer="lidalao"
4 | LABEL version="0.0.1"
5 | LABEL description="Telegram Bot for ServerStatus"
6 |
7 | WORKDIR /app
8 | RUN pip install requests
9 | COPY ./bot-telegram.py .
10 | CMD [ "python", "./bot-telegram.py" ]
11 |
--------------------------------------------------------------------------------
/service/status-client.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=ServerStatus-Client
3 | After=network.target
4 | [Service]
5 | ExecStart=/usr/bin/python3 /usr/local/ServerStatus/clients/client-linux.py
6 | ExecReload=/bin/kill -HUP $MAINPID
7 | Restart=on-failure
8 | [Install]
9 | WantedBy=multi-user.target
10 |
--------------------------------------------------------------------------------
/service/status-server.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=ServerStatus-Server
3 | After=network.target
4 | [Service]
5 | ExecStart=/usr/local/ServerStatus/server/sergate --config=/usr/local/ServerStatus/server/config.json --web-dir=/usr/local/ServerStatus/web
6 | ExecReload=/bin/kill -HUP $MAINPID
7 | Restart=on-failure
8 | [Install]
9 | WantedBy=multi-user.target
10 |
--------------------------------------------------------------------------------
/web/favicon.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/server/Makefile:
--------------------------------------------------------------------------------
1 | OUT = sergate
2 |
3 | #CC = clang
4 | CC = gcc
5 | CFLAGS = -Wall -O2
6 |
7 | #CXX = clang++
8 | CXX = g++
9 | CXXFLAGS = -Wall -O2 -std=c++11
10 |
11 | ODIR = obj
12 | SDIR = src
13 | LIBS = -pthread -lm
14 | INC = -Iinclude
15 |
16 | C_SRCS := $(wildcard $(SDIR)/*.c)
17 | CXX_SRCS := $(wildcard $(SDIR)/*.cpp)
18 | C_OBJS := $(patsubst $(SDIR)/%.c,$(ODIR)/%.o,$(C_SRCS))
19 | CXX_OBJS := $(patsubst $(SDIR)/%.cpp,$(ODIR)/%.o,$(CXX_SRCS))
20 | OBJS := $(C_OBJS) $(CXX_OBJS)
21 |
22 | $(ODIR)/%.o: $(SDIR)/%.c
23 | $(CC) -c $(INC) $(CFLAGS) $< -o $@
24 |
25 | $(ODIR)/%.o: $(SDIR)/%.cpp
26 | $(CXX) -c $(INC) $(CXXFLAGS) $< -o $@
27 |
28 | $(OUT): $(OBJS)
29 | $(CXX) $(LIBS) $^ -o $(OUT) -lcurl
30 |
31 | .PHONY: clean
32 |
33 | clean:
34 | rm -f $(ODIR)/*.o $(OUT)
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | serverstatus:
4 | build:
5 | context: .
6 | dockerfile: Dockerfile
7 | image: cppla/serverstatus:latest
8 | healthcheck:
9 | test: curl --fail http://localhost:80 || bash -c 'kill -s 15 -1 && (sleep 10; kill -s 9 -1)'
10 | interval: 30s
11 | timeout: 10s
12 | retries: 5
13 | container_name: serverstatus
14 | restart: unless-stopped
15 | networks:
16 | serverstatus-network:
17 | ipv4_address: 172.23.0.2
18 | volumes:
19 | - ./server/config.json:/ServerStatus/server/config.json
20 | - ./web/json:/usr/share/nginx/html/json
21 | ports:
22 | - 35601:35601
23 | - 8080:80
24 |
25 | networks:
26 | serverstatus-network:
27 | ipam:
28 | config:
29 | - subnet: 172.23.0.0/24
30 |
--------------------------------------------------------------------------------
/server/src/server.h:
--------------------------------------------------------------------------------
1 | #ifndef SERVER_H
2 | #define SERVER_H
3 |
4 | #include "netban.h"
5 | #include "network.h"
6 |
7 | class CServer
8 | {
9 | class CClient
10 | {
11 | public:
12 | enum
13 | {
14 | STATE_EMPTY=0,
15 | STATE_CONNECTED,
16 | STATE_AUTHED,
17 | };
18 |
19 | int m_State;
20 | int64 m_TimeConnected;
21 | int64 m_LastReceived;
22 | };
23 | CClient m_aClients[NET_MAX_CLIENTS];
24 |
25 | CNetwork m_Network;
26 | CNetBan m_NetBan;
27 |
28 | class CMain *m_pMain;
29 |
30 | bool m_Ready;
31 |
32 | static int NewClientCallback(int ClientID, void *pUser);
33 | static int DelClientCallback(int ClientID, const char *pReason, void *pUser);
34 |
35 | public:
36 | int Init(CMain *pMain, const char *Bind, int Port);
37 | void Update();
38 | void Send(int ClientID, const char *pLine);
39 | void Shutdown();
40 |
41 | CNetwork *Network() { return &m_Network; }
42 | CNetBan *NetBan() { return &m_NetBan; }
43 | CMain *Main() { return m_pMain; }
44 | };
45 |
46 | #endif
47 |
--------------------------------------------------------------------------------
/plugin/docker-compose-telegram.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | serverstatus:
4 | build:
5 | context: ..
6 | dockerfile: Dockerfile
7 | image: serverstatus_server
8 | container_name: serverstatus
9 | restart: unless-stopped
10 | networks:
11 | serverstatus-network:
12 | ipv4_address: 172.23.0.2
13 | volumes:
14 | - ../server/config.json:/ServerStatus/server/config.json
15 | - ../web/json:/usr/share/nginx/html/json
16 | ports:
17 | - 35601:35601
18 | - 8080:80
19 | bot:
20 | build:
21 | context: .
22 | dockerfile: Dockerfile-telegram
23 | image: serverstatus_bot
24 | container_name: bot4sss
25 | restart: unless-stopped
26 | networks:
27 | serverstatus-network:
28 | ipv4_address: 172.23.0.3
29 | environment:
30 | - TG_CHAT_ID=${TG_CHAT_ID}
31 | - TG_BOT_TOKEN=${TG_BOT_TOKEN}
32 |
33 | networks:
34 | serverstatus-network:
35 | name: serverstatus-network
36 | ipam:
37 | config:
38 | - subnet: 172.23.0.0/24
39 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # The Dockerfile for build localhost source, not git repo
2 | FROM debian:bookworm AS builder
3 |
4 | LABEL maintainer="cppla "
5 |
6 | RUN apt-get update -y && apt-get -y install gcc g++ make libcurl4-openssl-dev
7 |
8 | COPY . .
9 |
10 | WORKDIR /server
11 |
12 | RUN make -j
13 | RUN pwd && ls -a
14 |
15 | # glibc env run
16 | FROM nginx:latest
17 |
18 | RUN mkdir -p /ServerStatus/server/ && ln -sf /dev/null /var/log/nginx/access.log && ln -sf /dev/null /var/log/nginx/error.log
19 |
20 | COPY --from=builder server /ServerStatus/server/
21 | COPY --from=builder web /usr/share/nginx/html/
22 |
23 | # china time
24 | ENV TZ=Asia/Shanghai
25 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
26 |
27 | EXPOSE 80 35601
28 | HEALTHCHECK --interval=5s --timeout=3s --retries=3 CMD curl --fail http://localhost:80 || bash -c 'kill -s 15 -1 && (sleep 10; kill -s 9 -1)'
29 | CMD ["sh", "-c", "/etc/init.d/nginx start && /ServerStatus/server/sergate --config=/ServerStatus/server/config.json --web-dir=/usr/share/nginx/html"]
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/server/src/network.h:
--------------------------------------------------------------------------------
1 | #ifndef NETWORK_H
2 | #define NETWORK_H
3 |
4 | enum
5 | {
6 | NET_CONNSTATE_OFFLINE=0,
7 | NET_CONNSTATE_CONNECT=1,
8 | NET_CONNSTATE_PENDING=2,
9 | NET_CONNSTATE_ONLINE=3,
10 | NET_CONNSTATE_ERROR=4,
11 |
12 | NET_MAX_PACKETSIZE = 1400,
13 | NET_MAX_CLIENTS = 512
14 | };
15 |
16 | typedef int (*NETFUNC_DELCLIENT)(int ClientID, const char* pReason, void *pUser);
17 | typedef int (*NETFUNC_NEWCLIENT)(int ClientID, void *pUser);
18 |
19 | class CNetworkClient
20 | {
21 | private:
22 | int m_State;
23 |
24 | NETADDR m_PeerAddr;
25 | NETSOCKET m_Socket;
26 |
27 | char m_aBuffer[NET_MAX_PACKETSIZE];
28 | int m_BufferOffset;
29 |
30 | char m_aErrorString[256];
31 |
32 | bool m_LineEndingDetected;
33 | char m_aLineEnding[3];
34 |
35 | public:
36 | void Init(NETSOCKET Socket, const NETADDR *pAddr);
37 | void Disconnect(const char *pReason);
38 |
39 | int State() const { return m_State; }
40 | const NETADDR *PeerAddress() const { return &m_PeerAddr; }
41 | const char *ErrorString() const { return m_aErrorString; }
42 |
43 | void Reset();
44 | int Update();
45 | int Send(const char *pLine);
46 | int Recv(char *pLine, int MaxLength);
47 | };
48 |
49 | class CNetwork
50 | {
51 | private:
52 | struct CSlot
53 | {
54 | CNetworkClient m_Connection;
55 | };
56 |
57 | NETSOCKET m_Socket;
58 | class CNetBan *m_pNetBan;
59 | CSlot m_aSlots[NET_MAX_CLIENTS];
60 |
61 | NETFUNC_NEWCLIENT m_pfnNewClient;
62 | NETFUNC_DELCLIENT m_pfnDelClient;
63 | void *m_UserPtr;
64 |
65 | public:
66 | void SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser);
67 |
68 | //
69 | bool Open(NETADDR BindAddr, CNetBan *pNetBan);
70 | int Close();
71 |
72 | //
73 | int Recv(char *pLine, int MaxLength, int *pClientID = 0);
74 | int Send(int ClientID, const char *pLine);
75 | int Update();
76 |
77 | //
78 | int AcceptClient(NETSOCKET Socket, const NETADDR *pAddr);
79 | int Drop(int ClientID, const char *pReason);
80 |
81 | // status requests
82 | const NETADDR *ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); }
83 | const NETSOCKET *Socket() const { return &m_Socket; }
84 | class CNetBan *NetBan() const { return m_pNetBan; }
85 | };
86 |
87 | #endif
88 |
--------------------------------------------------------------------------------
/plugin/bot-telegram.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # coding: utf-8
3 | # Create by : https://github.com/lidalao/ServerStatus
4 | # 版本:0.0.1, 支持Python版本:2.7 to 3.9
5 | # 支持操作系统: Linux, OSX, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures
6 |
7 | import os
8 | import sys
9 | import requests
10 | import time
11 | import traceback
12 |
13 | NODE_STATUS_URL = 'http://serverstatus/json/stats.json'
14 |
15 | offs = []
16 | counterOff = {}
17 | counterOn = {}
18 |
19 | def _send(text):
20 | chat_id = os.getenv('TG_CHAT_ID')
21 | bot_token = os.environ.get('TG_BOT_TOKEN')
22 | url = f"https://api.telegram.org/bot{bot_token}/sendMessage?parse_mode=HTML&disable_web_page_preview=true&chat_id=" + chat_id + "&text=" + text
23 | try:
24 | requests.get(url)
25 | except Exception as e:
26 | print("catch exception: ", traceback.format_exc())
27 |
28 | def send2tg(srv, flag):
29 | if srv not in counterOff:
30 | counterOff[srv] = 0
31 | if srv not in counterOn:
32 | counterOn[srv] = 0
33 |
34 | if flag == 1 : # online
35 | if srv in offs:
36 | if counterOn[srv] < 10:
37 | counterOn[srv] += 1
38 | return
39 | #1. Remove srv from offs; 2. Send to tg: I am online
40 | offs.remove(srv)
41 | counterOn[srv] = 0
42 | text = 'Server Status' + '\n主机上线: ' + srv
43 | _send(text)
44 | else: #offline
45 | if srv not in offs:
46 | if counterOff[srv] < 10:
47 | counterOff[srv] += 1
48 | return
49 | #1. Append srv to offs; 2. Send to tg: I am offline
50 | offs.append(srv)
51 | counterOff[srv] = 0
52 | text = 'Server Status' + '\n主机下线: ' + srv
53 | _send(text)
54 |
55 | def sscmd(address):
56 | while True:
57 | r = requests.get(url=address, headers={"User-Agent": "ServerStatus/20211116"})
58 | try:
59 | jsonR = r.json()
60 | except Exception as e:
61 | print('未发现任何节点')
62 | continue
63 | for i in jsonR["servers"]:
64 | if i["online4"] is False and i["online6"] is False:
65 | send2tg(i["name"], 0)
66 | else:
67 | send2tg(i["name"], 1)
68 |
69 |
70 | time.sleep(3)
71 |
72 | if __name__ == '__main__':
73 | sscmd(NODE_STATUS_URL)
74 |
--------------------------------------------------------------------------------
/server/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "servers": [
3 | {
4 | "username": "s01",
5 | "name": "node1",
6 | "type": "xen",
7 | "host": "host1",
8 | "location": "🇨🇳",
9 | "password": "USER_DEFAULT_PASSWORD",
10 | "monthstart": 1
11 | },
12 | {
13 | "username": "s02",
14 | "name": "node2",
15 | "type": "vmware",
16 | "host": "host2",
17 | "location": "🇯🇵",
18 | "password": "USER_DEFAULT_PASSWORD",
19 | "monthstart": 1
20 | },
21 | {
22 | "disabled": true,
23 | "username": "s03",
24 | "name": "node3",
25 | "type": "hyper",
26 | "host": "host3",
27 | "location": "🇫🇷",
28 | "password": "USER_DEFAULT_PASSWORD",
29 | "monthstart": 1
30 | },
31 | {
32 | "username": "s04",
33 | "name": "node4",
34 | "type": "kvm",
35 | "host": "host4",
36 | "location": "🇰🇷",
37 | "password": "USER_DEFAULT_PASSWORD",
38 | "monthstart": 1
39 | }
40 | ],
41 | "monitors": [
42 | {
43 | "name": "抖音",
44 | "host": "https://www.douyin.com",
45 | "interval": 600,
46 | "type": "https"
47 | },
48 | {
49 | "name": "百度",
50 | "host": "https://www.baidu.com",
51 | "interval": 600,
52 | "type": "https"
53 | }
54 | ],
55 | "sslcerts": [
56 | {
57 | "name": "my.cloudcpp.com",
58 | "domain": "https://my.cloudcpp.com",
59 | "port": 443,
60 | "interval": 7200,
61 | "callback": "https://yourSMSurl"
62 | },
63 | {
64 | "name": "tz.cloudcpp.com",
65 | "domain": "https://tz.cloudcpp.com",
66 | "port": 443,
67 | "interval": 7200,
68 | "callback": "https://yourSMSurl"
69 | },
70 | {
71 | "name": "3.0.2.1",
72 | "domain": "https://3.0.2.1",
73 | "port": 443,
74 | "interval": 7200,
75 | "callback": "https://yourSMSurl"
76 | }
77 | ],
78 | "watchdog": [
79 | {
80 | "name": "cpu high warning,exclude username s01",
81 | "rule": "cpu>90&load_1>5&username!='s01'",
82 | "interval": 600,
83 | "callback": "https://yourSMSurl"
84 | },
85 | {
86 | "name": "memory high warning, exclude less than 1GB vps",
87 | "rule": "(memory_used/memory_total)*100>90&memory_total>1048576",
88 | "interval": 300,
89 | "callback": "https://yourSMSurl"
90 | },
91 | {
92 | "name": "offline warning",
93 | "rule": "online4=0&online6=0",
94 | "interval": 600,
95 | "callback": "https://yourSMSurl"
96 | },
97 | {
98 | "name": "ddcc attack,limit type Oracle",
99 | "rule": "tcp_count>600&type='Oracle'",
100 | "interval": 300,
101 | "callback": "https://yourSMSurl"
102 | },
103 | {
104 | "name": "month 999GB traffic warning",
105 | "rule": "(network_out-last_network_out)/1024/1024/1024>999",
106 | "interval": 3600,
107 | "callback": "https://yourSMSurl"
108 | },
109 | {
110 | "name": "aliyun china free 18GB traffic warning",
111 | "rule": "(network_out-last_network_out)/1024/1024/1024>18&(username='aliyun1'|username='aliyun2')",
112 | "interval": 3600,
113 | "callback": "https://yourSMSurl"
114 | },
115 | {
116 | "name": "packet loss rate warning",
117 | "rule": "(ping_10010>10|ping_189>10|ping_10086>10)&(host='sgp'|host='qqhk'|host='hk-21-x'|host='hk-31-x')",
118 | "interval": 3600,
119 | "callback": "https://yourSMSurl"
120 | },
121 | {
122 | "name": "you can parse an expression combining any known field",
123 | "rule": "load_5>3",
124 | "interval": 900,
125 | "callback": "https://yourSMSurl"
126 | }
127 | ]
128 | }
129 |
--------------------------------------------------------------------------------
/server/src/network.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "netban.h"
3 | #include "network.h"
4 |
5 | bool CNetwork::Open(NETADDR BindAddr, CNetBan *pNetBan)
6 | {
7 | // zero out the whole structure
8 | mem_zero(this, sizeof(*this));
9 | m_Socket.type = NETTYPE_INVALID;
10 | m_Socket.ipv4sock = -1;
11 | m_Socket.ipv6sock = -1;
12 | m_pNetBan = pNetBan;
13 |
14 | // open socket
15 | m_Socket = net_tcp_create(BindAddr);
16 | if(!m_Socket.type)
17 | return false;
18 | if(net_tcp_listen(m_Socket, NET_MAX_CLIENTS))
19 | return false;
20 | net_set_non_blocking(m_Socket);
21 |
22 | for(int i = 0; i < NET_MAX_CLIENTS; i++)
23 | m_aSlots[i].m_Connection.Reset();
24 |
25 | return true;
26 | }
27 |
28 | void CNetwork::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser)
29 | {
30 | m_pfnNewClient = pfnNewClient;
31 | m_pfnDelClient = pfnDelClient;
32 | m_UserPtr = pUser;
33 | }
34 |
35 | int CNetwork::Close()
36 | {
37 | for(int i = 0; i < NET_MAX_CLIENTS; i++)
38 | m_aSlots[i].m_Connection.Disconnect("Closing connection.");
39 |
40 | net_tcp_close(m_Socket);
41 |
42 | return 0;
43 | }
44 |
45 | int CNetwork::Drop(int ClientID, const char *pReason)
46 | {
47 | if(m_pfnDelClient)
48 | m_pfnDelClient(ClientID, pReason, m_UserPtr);
49 |
50 | m_aSlots[ClientID].m_Connection.Disconnect(pReason);
51 |
52 | return 0;
53 | }
54 |
55 | int CNetwork::AcceptClient(NETSOCKET Socket, const NETADDR *pAddr)
56 | {
57 | char aError[256] = { 0 };
58 | int FreeSlot = -1;
59 |
60 | // look for free slot or multiple client
61 | for(int i = 0; i < NET_MAX_CLIENTS; i++)
62 | {
63 | if(FreeSlot == -1 && m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE)
64 | FreeSlot = i;
65 | if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE)
66 | {
67 | if(net_addr_comp(pAddr, m_aSlots[i].m_Connection.PeerAddress()) == 0)
68 | {
69 | str_copy(aError, "Only one client per IP allowed.", sizeof(aError));
70 | break;
71 | }
72 | }
73 | }
74 |
75 | // accept client
76 | if(!aError[0] && FreeSlot != -1)
77 | {
78 | m_aSlots[FreeSlot].m_Connection.Init(Socket, pAddr);
79 | if(m_pfnNewClient)
80 | m_pfnNewClient(FreeSlot, m_UserPtr);
81 | return 0;
82 | }
83 |
84 | // reject client
85 | if(!aError[0])
86 | str_copy(aError, "No free slot available.", sizeof(aError));
87 |
88 | net_tcp_send(Socket, aError, str_length(aError));
89 | net_tcp_close(Socket);
90 |
91 | return -1;
92 | }
93 |
94 | int CNetwork::Update()
95 | {
96 | NETSOCKET Socket;
97 | NETADDR Addr;
98 |
99 | if(net_tcp_accept(m_Socket, &Socket, &Addr) > 0)
100 | {
101 | // check if we should just drop the packet
102 | char aBuf[128];
103 | if(NetBan() && NetBan()->IsBanned(&Addr, aBuf, sizeof(aBuf)))
104 | {
105 | // banned, reply with a message and drop
106 | net_tcp_send(Socket, aBuf, str_length(aBuf));
107 | net_tcp_close(Socket);
108 | }
109 | else
110 | AcceptClient(Socket, &Addr);
111 | }
112 |
113 | for(int i = 0; i < NET_MAX_CLIENTS; i++)
114 | {
115 | if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE)
116 | m_aSlots[i].m_Connection.Update();
117 | if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR)
118 | Drop(i, m_aSlots[i].m_Connection.ErrorString());
119 | }
120 |
121 | return 0;
122 | }
123 |
124 | int CNetwork::Recv(char *pLine, int MaxLength, int *pClientID)
125 | {
126 | for(int i = 0; i < NET_MAX_CLIENTS; i++)
127 | {
128 | if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE && m_aSlots[i].m_Connection.Recv(pLine, MaxLength))
129 | {
130 | if(pClientID)
131 | *pClientID = i;
132 | return 1;
133 | }
134 | }
135 | return 0;
136 | }
137 |
138 | int CNetwork::Send(int ClientID, const char *pLine)
139 | {
140 | if(m_aSlots[ClientID].m_Connection.State() == NET_CONNSTATE_ONLINE)
141 | return m_aSlots[ClientID].m_Connection.Send(pLine);
142 | else
143 | return -1;
144 | }
145 |
--------------------------------------------------------------------------------
/server/include/argparse.h:
--------------------------------------------------------------------------------
1 | #ifndef ARGPARSE_H
2 | #define ARGPARSE_H
3 |
4 | /**
5 | * Command-line arguments parsing library.
6 | *
7 | * This module is inspired by parse-options.c (git) and python's argparse
8 | * module.
9 | *
10 | * Arguments parsing is common task in cli program, but traditional `getopt`
11 | * libraries are not easy to use. This library provides high-level arguments
12 | * parsing solutions.
13 | *
14 | * The program defines what arguments it requires, and `argparse` will figure
15 | * out how to parse those out of `argc` and `argv`, it also automatically
16 | * generates help and usage messages and issues errors when users give the
17 | * program invalid arguments.
18 | *
19 | * Reserved namespaces:
20 | * argparse
21 | * OPT
22 | * Author: Yecheng Fu
23 | */
24 |
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 |
32 | #ifdef __cplusplus
33 | extern "C" {
34 | #endif
35 |
36 | struct argparse;
37 | struct argparse_option;
38 |
39 | typedef int argparse_callback(struct argparse *this_,
40 | const struct argparse_option *option);
41 |
42 | enum argparse_flag {
43 | ARGPARSE_STOP_AT_NON_OPTION = 1,
44 | };
45 |
46 | enum argparse_option_type {
47 | /* special */
48 | ARGPARSE_OPT_END,
49 | /* options with no arguments */
50 | ARGPARSE_OPT_BOOLEAN,
51 | ARGPARSE_OPT_BIT,
52 | /* options with arguments (optional or required) */
53 | ARGPARSE_OPT_INTEGER,
54 | ARGPARSE_OPT_STRING,
55 | };
56 |
57 | enum argparse_option_flags {
58 | OPT_NONEG = 1, /* Negation disabled. */
59 | };
60 |
61 | /*
62 | * Argparse option struct.
63 | *
64 | * `type`:
65 | * holds the type of the option, you must have an ARGPARSE_OPT_END last in your
66 | * array.
67 | *
68 | * `short_name`:
69 | * the character to use as a short option name, '\0' if none.
70 | *
71 | * `long_name`:
72 | * the long option name, without the leading dash, NULL if none.
73 | *
74 | * `value`:
75 | * stores pointer to the value to be filled.
76 | *
77 | * `help`:
78 | * the short help message associated to what the option does.
79 | * Must never be NULL (except for ARGPARSE_OPT_END).
80 | *
81 | * `callback`:
82 | * function is called when corresponding argument is parsed.
83 | *
84 | * `data`:
85 | * associated data. Callbacks can use it like they want.
86 | *
87 | * `flags`:
88 | * option flags.
89 | *
90 | */
91 | struct argparse_option {
92 | enum argparse_option_type type;
93 | const char short_name;
94 | const char *long_name;
95 | void *value;
96 | const char *help;
97 | argparse_callback *callback;
98 | intptr_t data;
99 | int flags;
100 | };
101 |
102 | /*
103 | * argpparse
104 | */
105 | struct argparse {
106 | // user supplied
107 | const struct argparse_option *options;
108 | const char *usage;
109 | int flags;
110 | // internal context
111 | int argc;
112 | const char **argv;
113 | const char **out;
114 | int cpidx;
115 | const char *optvalue; // current option value
116 | };
117 |
118 | // builtin callbacks
119 | int argparse_help_cb(struct argparse *this_,
120 | const struct argparse_option *option);
121 |
122 | // builtin option macros
123 | #define OPT_END() { ARGPARSE_OPT_END, 0 }
124 | #define OPT_BOOLEAN(...) { ARGPARSE_OPT_BOOLEAN, __VA_ARGS__ }
125 | #define OPT_BIT(...) { ARGPARSE_OPT_BIT, __VA_ARGS__ }
126 | #define OPT_INTEGER(...) { ARGPARSE_OPT_INTEGER, __VA_ARGS__ }
127 | #define OPT_STRING(...) { ARGPARSE_OPT_STRING, __VA_ARGS__ }
128 | #define OPT_HELP() OPT_BOOLEAN('h', "help", 0, "Show this help message and exit", argparse_help_cb)
129 |
130 | int argparse_init(struct argparse *this_, struct argparse_option *options,
131 | const char *usage, int flags);
132 | int argparse_parse(struct argparse *this_, int argc, const char **argv);
133 | void argparse_usage(struct argparse *this_);
134 |
135 | #ifdef __cplusplus
136 | }
137 | #endif
138 |
139 | #endif
140 |
--------------------------------------------------------------------------------
/server/include/detect.h:
--------------------------------------------------------------------------------
1 | /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2 | /* If you are missing that file, acquire a complete release at teeworlds.com. */
3 | #ifndef BASE_DETECT_H
4 | #define BASE_DETECT_H
5 |
6 | /*
7 | this file detected the family, platform and architecture
8 | to compile for.
9 | */
10 |
11 | /* platforms */
12 |
13 | /* windows Family */
14 | #if defined(WIN64) || defined(_WIN64)
15 | /* Hmm, is this IA64 or x86-64? */
16 | #define CONF_FAMILY_WINDOWS 1
17 | #define CONF_FAMILY_STRING "windows"
18 | #define CONF_PLATFORM_WIN64 1
19 | #define CONF_PLATFORM_STRING "win64"
20 | #elif defined(WIN32) || defined(_WIN32) || defined(__CYGWIN32__) || defined(__MINGW32__)
21 | #define CONF_FAMILY_WINDOWS 1
22 | #define CONF_FAMILY_STRING "windows"
23 | #define CONF_PLATFORM_WIN32 1
24 | #define CONF_PLATFORM_STRING "win32"
25 | #endif
26 |
27 | /* unix family */
28 | #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
29 | #define CONF_FAMILY_UNIX 1
30 | #define CONF_FAMILY_STRING "unix"
31 | #define CONF_PLATFORM_FREEBSD 1
32 | #define CONF_PLATFORM_STRING "freebsd"
33 | #endif
34 |
35 | #if defined(__OpenBSD__)
36 | #define CONF_FAMILY_UNIX 1
37 | #define CONF_FAMILY_STRING "unix"
38 | #define CONF_PLATFORM_OPENBSD 1
39 | #define CONF_PLATFORM_STRING "openbsd"
40 | #endif
41 |
42 | #if defined(__LINUX__) || defined(__linux__)
43 | #define CONF_FAMILY_UNIX 1
44 | #define CONF_FAMILY_STRING "unix"
45 | #define CONF_PLATFORM_LINUX 1
46 | #define CONF_PLATFORM_STRING "linux"
47 | #endif
48 |
49 | #if defined(__GNU__) || defined(__gnu__)
50 | #define CONF_FAMILY_UNIX 1
51 | #define CONF_FAMILY_STRING "unix"
52 | #define CONF_PLATFORM_HURD 1
53 | #define CONF_PLATFORM_STRING "gnu"
54 | #endif
55 |
56 | #if defined(MACOSX) || defined(__APPLE__) || defined(__DARWIN__)
57 | #define CONF_FAMILY_UNIX 1
58 | #define CONF_FAMILY_STRING "unix"
59 | #define CONF_PLATFORM_MACOSX 1
60 | #define CONF_PLATFORM_STRING "macosx"
61 | #endif
62 |
63 | #if defined(__sun)
64 | #define CONF_FAMILY_UNIX 1
65 | #define CONF_FAMILY_STRING "unix"
66 | #define CONF_PLATFORM_SOLARIS 1
67 | #define CONF_PLATFORM_STRING "solaris"
68 | #endif
69 |
70 | /* beos family */
71 | #if defined(__BeOS) || defined(__BEOS__)
72 | #define CONF_FAMILY_BEOS 1
73 | #define CONF_FAMILY_STRING "beos"
74 | #define CONF_PLATFORM_BEOS 1
75 | #define CONF_PLATFORM_STRING "beos"
76 | #endif
77 |
78 |
79 | /* use gcc endianness definitions when available */
80 | #if defined(__GNUC__) && !defined(__APPLE__) && !defined(__MINGW32__) && !defined(__sun)
81 | #if defined(__FreeBSD__) || defined(__OpenBSD__)
82 | #include
83 | #else
84 | #include
85 | #endif
86 |
87 | #if __BYTE_ORDER == __LITTLE_ENDIAN
88 | #define CONF_ARCH_ENDIAN_LITTLE 1
89 | #elif __BYTE_ORDER == __BIG_ENDIAN
90 | #define CONF_ARCH_ENDIAN_BIG 1
91 | #endif
92 | #endif
93 |
94 |
95 | /* architectures */
96 | #if defined(i386) || defined(__i386__) || defined(__x86__) || defined(CONF_PLATFORM_WIN32)
97 | #define CONF_ARCH_IA32 1
98 | #define CONF_ARCH_STRING "ia32"
99 | #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG)
100 | #define CONF_ARCH_ENDIAN_LITTLE 1
101 | #endif
102 | #endif
103 |
104 | #if defined(__ia64__) || defined(_M_IA64)
105 | #define CONF_ARCH_IA64 1
106 | #define CONF_ARCH_STRING "ia64"
107 | #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG)
108 | #define CONF_ARCH_ENDIAN_LITTLE 1
109 | #endif
110 | #endif
111 |
112 | #if defined(__amd64__) || defined(__x86_64__) || defined(_M_X64)
113 | #define CONF_ARCH_AMD64 1
114 | #define CONF_ARCH_STRING "amd64"
115 | #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG)
116 | #define CONF_ARCH_ENDIAN_LITTLE 1
117 | #endif
118 | #endif
119 |
120 | #if defined(__powerpc__) || defined(__ppc__)
121 | #define CONF_ARCH_PPC 1
122 | #define CONF_ARCH_STRING "ppc"
123 | #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG)
124 | #define CONF_ARCH_ENDIAN_BIG 1
125 | #endif
126 | #endif
127 |
128 | #if defined(__sparc__)
129 | #define CONF_ARCH_SPARC 1
130 | #define CONF_ARCH_STRING "sparc"
131 | #if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG)
132 | #define CONF_ARCH_ENDIAN_BIG 1
133 | #endif
134 | #endif
135 |
136 |
137 | #ifndef CONF_FAMILY_STRING
138 | #define CONF_FAMILY_STRING "unknown"
139 | #endif
140 |
141 | #ifndef CONF_PLATFORM_STRING
142 | #define CONF_PLATFORM_STRING "unknown"
143 | #endif
144 |
145 | #ifndef CONF_ARCH_STRING
146 | #define CONF_ARCH_STRING "unknown"
147 | #endif
148 |
149 | #endif
150 |
--------------------------------------------------------------------------------
/server/src/network_client.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "network.h"
3 |
4 | void CNetworkClient::Reset()
5 | {
6 | m_State = NET_CONNSTATE_OFFLINE;
7 | mem_zero(&m_PeerAddr, sizeof(m_PeerAddr));
8 | m_aErrorString[0] = 0;
9 |
10 | m_Socket.type = NETTYPE_INVALID;
11 | m_Socket.ipv4sock = -1;
12 | m_Socket.ipv6sock = -1;
13 | m_aBuffer[0] = 0;
14 | m_BufferOffset = 0;
15 |
16 | m_LineEndingDetected = false;
17 | #if defined(CONF_FAMILY_WINDOWS)
18 | m_aLineEnding[0] = '\r';
19 | m_aLineEnding[1] = '\n';
20 | m_aLineEnding[2] = 0;
21 | #else
22 | m_aLineEnding[0] = '\n';
23 | m_aLineEnding[1] = 0;
24 | m_aLineEnding[2] = 0;
25 | #endif
26 | }
27 |
28 | void CNetworkClient::Init(NETSOCKET Socket, const NETADDR *pAddr)
29 | {
30 | Reset();
31 |
32 | m_Socket = Socket;
33 | net_set_non_blocking(m_Socket);
34 |
35 | m_PeerAddr = *pAddr;
36 | m_State = NET_CONNSTATE_ONLINE;
37 | }
38 |
39 | void CNetworkClient::Disconnect(const char *pReason)
40 | {
41 | if(State() == NET_CONNSTATE_OFFLINE)
42 | return;
43 |
44 | if(pReason && pReason[0])
45 | Send(pReason);
46 |
47 | net_tcp_close(m_Socket);
48 |
49 | Reset();
50 | }
51 |
52 | int CNetworkClient::Update()
53 | {
54 | if(State() == NET_CONNSTATE_ONLINE)
55 | {
56 | if((int)(sizeof(m_aBuffer)) <= m_BufferOffset)
57 | {
58 | m_State = NET_CONNSTATE_ERROR;
59 | str_copy(m_aErrorString, "too weak connection (out of buffer)", sizeof(m_aErrorString));
60 | return -1;
61 | }
62 |
63 | int Bytes = net_tcp_recv(m_Socket, m_aBuffer+m_BufferOffset, (int)(sizeof(m_aBuffer))-m_BufferOffset);
64 |
65 | if(Bytes > 0)
66 | {
67 | m_BufferOffset += Bytes;
68 | }
69 | else if(Bytes < 0)
70 | {
71 | if(net_would_block()) // no data received
72 | return 0;
73 |
74 | m_State = NET_CONNSTATE_ERROR; // error
75 | str_copy(m_aErrorString, "connection failure", sizeof(m_aErrorString));
76 | return -1;
77 | }
78 | else
79 | {
80 | m_State = NET_CONNSTATE_ERROR;
81 | str_copy(m_aErrorString, "remote end closed the connection", sizeof(m_aErrorString));
82 | return -1;
83 | }
84 | }
85 |
86 | return 0;
87 | }
88 |
89 | int CNetworkClient::Recv(char *pLine, int MaxLength)
90 | {
91 | if(State() == NET_CONNSTATE_ONLINE)
92 | {
93 | if(m_BufferOffset)
94 | {
95 | // find message start
96 | int StartOffset = 0;
97 | while(m_aBuffer[StartOffset] == '\r' || m_aBuffer[StartOffset] == '\n')
98 | {
99 | // detect clients line ending format
100 | if(!m_LineEndingDetected)
101 | {
102 | m_aLineEnding[0] = m_aBuffer[StartOffset];
103 | if(StartOffset+1 < m_BufferOffset && (m_aBuffer[StartOffset+1] == '\r' || m_aBuffer[StartOffset+1] == '\n') &&
104 | m_aBuffer[StartOffset] != m_aBuffer[StartOffset+1])
105 | m_aLineEnding[1] = m_aBuffer[StartOffset+1];
106 | m_LineEndingDetected = true;
107 | }
108 |
109 | if(++StartOffset >= m_BufferOffset)
110 | {
111 | m_BufferOffset = 0;
112 | return 0;
113 | }
114 | }
115 |
116 | // find message end
117 | int EndOffset = StartOffset;
118 | while(m_aBuffer[EndOffset] != '\r' && m_aBuffer[EndOffset] != '\n')
119 | {
120 | if(++EndOffset >= m_BufferOffset)
121 | {
122 | if(StartOffset > 0)
123 | {
124 | mem_move(m_aBuffer, m_aBuffer+StartOffset, m_BufferOffset-StartOffset);
125 | m_BufferOffset -= StartOffset;
126 | }
127 | return 0;
128 | }
129 | }
130 |
131 | // extract message and update buffer
132 | if(MaxLength-1 < EndOffset-StartOffset)
133 | {
134 | if(StartOffset > 0)
135 | {
136 | mem_move(m_aBuffer, m_aBuffer+StartOffset, m_BufferOffset-StartOffset);
137 | m_BufferOffset -= StartOffset;
138 | }
139 | return 0;
140 | }
141 | mem_copy(pLine, m_aBuffer+StartOffset, EndOffset-StartOffset);
142 | pLine[EndOffset-StartOffset] = 0;
143 | str_sanitize_cc(pLine);
144 | mem_move(m_aBuffer, m_aBuffer+EndOffset, m_BufferOffset-EndOffset);
145 | m_BufferOffset -= EndOffset;
146 | return 1;
147 | }
148 | }
149 | return 0;
150 | }
151 |
152 | int CNetworkClient::Send(const char *pLine)
153 | {
154 | if(State() != NET_CONNSTATE_ONLINE)
155 | return -1;
156 |
157 | char aBuf[1024];
158 | str_copy(aBuf, pLine, (int)(sizeof(aBuf))-2);
159 | int Length = str_length(aBuf);
160 | aBuf[Length] = m_aLineEnding[0];
161 | aBuf[Length+1] = m_aLineEnding[1];
162 | aBuf[Length+2] = m_aLineEnding[2];
163 | Length += 3;
164 | const char *pData = aBuf;
165 |
166 | while(1)
167 | {
168 | int Send = net_tcp_send(m_Socket, pData, Length);
169 | if(Send < 0)
170 | {
171 | m_State = NET_CONNSTATE_ERROR;
172 | str_copy(m_aErrorString, "failed to send packet", sizeof(m_aErrorString));
173 | return -1;
174 | }
175 |
176 | if(Send >= Length)
177 | break;
178 |
179 | pData += Send;
180 | Length -= Send;
181 | }
182 |
183 | return 0;
184 | }
185 |
--------------------------------------------------------------------------------
/server/src/main.h:
--------------------------------------------------------------------------------
1 | #ifndef MAIN_H
2 | #define MAIN_H
3 |
4 | #include
5 | #include "server.h"
6 |
7 | class CConfig
8 | {
9 | public:
10 | bool m_Verbose;
11 | char m_aConfigFile[1024];
12 | char m_aWebDir[1024];
13 | char m_aTemplateFile[1024];
14 | char m_aJSONFile[1024];
15 | char m_aBindAddr[256];
16 | int m_Port;
17 |
18 | CConfig();
19 | };
20 |
21 | class CMain
22 | {
23 | CConfig m_Config;
24 | CServer m_Server;
25 |
26 | struct CClient
27 | {
28 | bool m_Active;
29 | bool m_Disabled;
30 | bool m_Connected;
31 | int m_ClientNetID;
32 | int m_ClientNetType;
33 | char m_aUsername[128];
34 | char m_aName[128];
35 | char m_aType[128];
36 | char m_aHost[128];
37 | char m_aLocation[128];
38 | char m_aPassword[128];
39 | int m_aMonthStart; //track month network traffic. by: https://cpp.la
40 |
41 | int64_t m_LastNetworkIN; //restore month traffic info.
42 | int64_t m_LastNetworkOUT; //restore month traffic info.
43 | int64_t m_TimeConnected;
44 | int64_t m_LastUpdate;
45 | int64_t m_AlarmLastTime; //record last alarm time.
46 |
47 | struct CStats
48 | {
49 | bool m_Online4;
50 | bool m_Online6;
51 | // bool m_IpStatus, delete ip_status check, Duplicate packet loss rate detection
52 | // mh361 or mh370, mourn mh370, 2014-03-08 01:20 lost from all over the world. by:https://cpp.la
53 | int64_t m_Uptime;
54 | double m_Load_1;
55 | double m_Load_5;
56 | double m_Load_15;
57 | double m_ping_10010;
58 | double m_ping_189;
59 | double m_ping_10086;
60 | int64_t m_time_10010;
61 | int64_t m_time_189;
62 | int64_t m_time_10086;
63 | int64_t m_NetworkRx;
64 | int64_t m_NetworkTx;
65 | int64_t m_NetworkIN;
66 | int64_t m_NetworkOUT;
67 | int64_t m_MemTotal;
68 | int64_t m_MemUsed;
69 | int64_t m_SwapTotal;
70 | int64_t m_SwapUsed;
71 | int64_t m_HDDTotal;
72 | int64_t m_HDDUsed;
73 | int64_t m_tcpCount;
74 | int64_t m_udpCount;
75 | int64_t m_processCount;
76 | int64_t m_threadCount;
77 | int64_t m_IORead;
78 | int64_t m_IOWrite;
79 | double m_CPU;
80 | char m_aCustom[1024];
81 | // OS name reported by client (e.g. linux/windows/darwin/freebsd)
82 | char m_aOS[64];
83 | // Options
84 | bool m_Pong;
85 | } m_Stats;
86 | } m_aClients[NET_MAX_CLIENTS];
87 |
88 | struct CWatchDog{
89 | char m_aName[128];
90 | char m_aRule[128];
91 | int m_aInterval;
92 | char m_aCallback[1024];
93 | } m_aCWatchDogs[NET_MAX_CLIENTS];
94 |
95 | struct CMonitors{
96 | char m_aName[128];
97 | char m_aHost[128];
98 | int m_aInterval;
99 | char m_aType[128];
100 | } m_aCMonitors[NET_MAX_CLIENTS];
101 | public:
102 | struct CSSLCerts{
103 | char m_aName[128];
104 | char m_aDomain[256];
105 | int m_aPort;
106 | int m_aInterval; // seconds
107 | char m_aCallback[1024];
108 | int64_t m_aExpireTS; // epoch seconds cache
109 | int64_t m_aLastCheck; // last check time
110 | int64_t m_aLastAlarm7;
111 | int64_t m_aLastAlarm3;
112 | int64_t m_aLastAlarm1;
113 | int m_aHostnameMismatch; // 1: 域名与证书不匹配
114 | int64_t m_aLastAlarmMismatch; // 上次不匹配告警时间
115 | } m_aCSSLCerts[NET_MAX_CLIENTS];
116 |
117 | struct CJSONUpdateThreadData
118 | {
119 | CClient *pClients;
120 | CConfig *pConfig;
121 | CWatchDog *pWatchDogs;
122 | CMain *pMain;
123 | volatile short m_ReloadRequired;
124 | } m_JSONUpdateThreadData, m_OfflineAlarmThreadData;
125 |
126 | static void JSONUpdateThread(void *pUser);
127 | static void offlineAlarmThread(void *pUser);
128 | public:
129 | CMain(CConfig Config);
130 |
131 | void OnNewClient(int ClienNettID, int ClientID);
132 | void OnDelClient(int ClientNetID);
133 | int HandleMessage(int ClientNetID, char *pMessage);
134 | int ReadConfig();
135 | int Run();
136 |
137 | CWatchDog *Watchdog(int ruleID) { return &m_aCWatchDogs[ruleID]; }
138 | CMonitors *Monitors(int ruleID) { return &m_aCMonitors[ruleID]; }
139 | CSSLCerts *SSLCert(int ruleID) { return &m_aCSSLCerts[ruleID]; }
140 |
141 | void WatchdogMessage(int ClientNetID,
142 | double load_1, double load_5, double load_15, double ping_10010, double ping_189, double ping_10086,
143 | double time_10010, double time_189, double time_10086, double tcp_count, double udp_count, double process_count, double thread_count,
144 | double network_rx, double network_tx, double network_in, double network_out, double last_network_in, double last_network_out,
145 | double memory_total, double memory_used,double swap_total, double swap_used, double hdd_total,
146 | double hdd_used, double io_read, double io_write, double cpu,double online4, double online6);
147 |
148 | CClient *Client(int ClientID) { return &m_aClients[ClientID]; }
149 | CClient *ClientNet(int ClientNetID);
150 | const CConfig *Config() const { return &m_Config; }
151 | int ClientNetToClient(int ClientNetID);
152 | };
153 |
154 |
155 | #endif
156 |
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 云监控
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
21 |
22 |
云监控
23 |
24 |
29 |
30 |
31 | 更新中...
32 |
33 |
34 |
35 |
36 | 初始化中...
37 |
38 |
39 |
40 |
41 |
42 |
43 | | 协议 |
44 | 月流量 ↓|↑ |
45 | 节点 |
46 | 虚拟化 |
47 | 位置 |
48 | 在线 |
49 | 负载 |
50 | 当前网络 ↓|↑ |
51 | 总流量 ↓|↑ |
52 | CPU |
53 | 内存 |
54 | 硬盘 |
55 | 联通|电信|移动 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | | 协议 |
71 | 监测节点 |
72 | 监测位置 |
73 | 监测内容 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | | 名称 |
89 | 域名 |
90 | 端口 |
91 | 剩余(天) |
92 | 到期(UTC) |
93 | 状态 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
节点详情
109 |
加载中...
110 |
111 |
112 |
113 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/server/src/netban.h:
--------------------------------------------------------------------------------
1 | #ifndef NETBAN_H
2 | #define NETBAN_H
3 |
4 | #include
5 |
6 | inline int NetComp(const NETADDR *pAddr1, const NETADDR *pAddr2)
7 | {
8 | return mem_comp(pAddr1, pAddr2, pAddr1->type==NETTYPE_IPV4 ? 8 : 20);
9 | }
10 |
11 | class CNetRange
12 | {
13 | public:
14 | NETADDR m_LB;
15 | NETADDR m_UB;
16 |
17 | bool IsValid() const { return m_LB.type == m_UB.type && NetComp(&m_LB, &m_UB) < 0; }
18 | };
19 |
20 | inline int NetComp(const CNetRange *pRange1, const CNetRange *pRange2)
21 | {
22 | return NetComp(&pRange1->m_LB, &pRange2->m_LB) || NetComp(&pRange1->m_UB, &pRange2->m_UB);
23 | }
24 |
25 |
26 | class CNetBan
27 | {
28 | protected:
29 | bool NetMatch(const NETADDR *pAddr1, const NETADDR *pAddr2) const
30 | {
31 | return NetComp(pAddr1, pAddr2) == 0;
32 | }
33 |
34 | bool NetMatch(const CNetRange *pRange, const NETADDR *pAddr, int Start, int Length) const
35 | {
36 | return pRange->m_LB.type == pAddr->type && (Start == 0 || mem_comp(&pRange->m_LB.ip[0], &pAddr->ip[0], Start) == 0) &&
37 | mem_comp(&pRange->m_LB.ip[Start], &pAddr->ip[Start], Length-Start) <= 0 && mem_comp(&pRange->m_UB.ip[Start], &pAddr->ip[Start], Length-Start) >= 0;
38 | }
39 |
40 | bool NetMatch(const CNetRange *pRange, const NETADDR *pAddr) const
41 | {
42 | return NetMatch(pRange, pAddr, 0, pRange->m_LB.type==NETTYPE_IPV4 ? 4 : 16);
43 | }
44 |
45 | const char *NetToString(const NETADDR *pData, char *pBuffer, unsigned BufferSize) const
46 | {
47 | char aAddrStr[NETADDR_MAXSTRSIZE];
48 | net_addr_str(pData, aAddrStr, sizeof(aAddrStr), false);
49 | str_format(pBuffer, BufferSize, "'%s'", aAddrStr);
50 | return pBuffer;
51 | }
52 |
53 | const char *NetToString(const CNetRange *pData, char *pBuffer, unsigned BufferSize) const
54 | {
55 | char aAddrStr1[NETADDR_MAXSTRSIZE], aAddrStr2[NETADDR_MAXSTRSIZE];
56 | net_addr_str(&pData->m_LB, aAddrStr1, sizeof(aAddrStr1), false);
57 | net_addr_str(&pData->m_UB, aAddrStr2, sizeof(aAddrStr2), false);
58 | str_format(pBuffer, BufferSize, "'%s' - '%s'", aAddrStr1, aAddrStr2);
59 | return pBuffer;
60 | }
61 |
62 | // todo: move?
63 | static bool StrAllnum(const char *pStr);
64 |
65 | class CNetHash
66 | {
67 | public:
68 | int m_Hash;
69 | int m_HashIndex; // matching parts for ranges, 0 for addr
70 |
71 | CNetHash() {}
72 | CNetHash(const NETADDR *pAddr);
73 | CNetHash(const CNetRange *pRange);
74 |
75 | static int MakeHashArray(const NETADDR *pAddr, CNetHash aHash[17]);
76 | };
77 |
78 | struct CBanInfo
79 | {
80 | enum
81 | {
82 | EXPIRES_NEVER=-1,
83 | REASON_LENGTH=64,
84 | };
85 | int m_Expires;
86 | char m_aReason[REASON_LENGTH];
87 | };
88 |
89 | template struct CBan
90 | {
91 | T m_Data;
92 | CBanInfo m_Info;
93 | CNetHash m_NetHash;
94 |
95 | // hash list
96 | CBan *m_pHashNext;
97 | CBan *m_pHashPrev;
98 |
99 | // used or free list
100 | CBan *m_pNext;
101 | CBan *m_pPrev;
102 | };
103 |
104 | template class CBanPool
105 | {
106 | public:
107 | typedef T CDataType;
108 |
109 | CBan *Add(const CDataType *pData, const CBanInfo *pInfo, const CNetHash *pNetHash);
110 | int Remove(CBan *pBan);
111 | void Update(CBan *pBan, const CBanInfo *pInfo);
112 | void Reset();
113 |
114 | int Num() const { return m_CountUsed; }
115 | bool IsFull() const { return m_CountUsed == MAX_BANS; }
116 |
117 | CBan *First() const { return m_pFirstUsed; }
118 | CBan *First(const CNetHash *pNetHash) const { return m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]; }
119 | CBan *Find(const CDataType *pData, const CNetHash *pNetHash) const
120 | {
121 | for(CBan *pBan = m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]; pBan; pBan = pBan->m_pHashNext)
122 | {
123 | if(NetComp(&pBan->m_Data, pData) == 0)
124 | return pBan;
125 | }
126 |
127 | return 0;
128 | }
129 | CBan *Get(int Index) const;
130 |
131 | private:
132 | enum
133 | {
134 | MAX_BANS=1024,
135 | };
136 |
137 | CBan *m_paaHashList[HashCount][256];
138 | CBan m_aBans[MAX_BANS];
139 | CBan *m_pFirstFree;
140 | CBan *m_pFirstUsed;
141 | int m_CountUsed;
142 | };
143 |
144 | typedef CBanPool CBanAddrPool;
145 | typedef CBanPool CBanRangePool;
146 | typedef CBan CBanAddr;
147 | typedef CBan CBanRange;
148 |
149 | template void MakeBanInfo(const CBan *pBan, char *pBuf, unsigned BuffSize, int Type) const;
150 | template int Ban(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason);
151 | template int Unban(T *pBanPool, const typename T::CDataType *pData);
152 |
153 | CBanAddrPool m_BanAddrPool;
154 | CBanRangePool m_BanRangePool;
155 | NETADDR m_LocalhostIPV4, m_LocalhostIPV6;
156 |
157 | public:
158 | enum
159 | {
160 | MSGTYPE_PLAYER=0,
161 | MSGTYPE_LIST,
162 | MSGTYPE_BANADD,
163 | MSGTYPE_BANREM,
164 | };
165 |
166 | virtual ~CNetBan() {}
167 | void Init();
168 | void Update();
169 |
170 | virtual int BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason);
171 | virtual int BanRange(const CNetRange *pRange, int Seconds, const char *pReason);
172 | int UnbanByAddr(const NETADDR *pAddr);
173 | int UnbanByRange(const CNetRange *pRange);
174 | int UnbanByIndex(int Index);
175 | void UnbanAll();
176 | bool IsBanned(const NETADDR *pAddr, char *pBuf, unsigned BufferSize) const;
177 | };
178 |
179 | #endif
180 |
--------------------------------------------------------------------------------
/server/src/server.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "netban.h"
3 | #include "network.h"
4 | #include "main.h"
5 | #include "server.h"
6 |
7 | int CServer::NewClientCallback(int ClientID, void *pUser)
8 | {
9 | CServer *pThis = (CServer *)pUser;
10 |
11 | char aAddrStr[NETADDR_MAXSTRSIZE];
12 | net_addr_str(pThis->m_Network.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true);
13 | if(pThis->Main()->Config()->m_Verbose)
14 | dbg_msg("server", "Connection accepted. ncid=%d addr=%s'", ClientID, aAddrStr);
15 |
16 | pThis->m_aClients[ClientID].m_State = CClient::STATE_CONNECTED;
17 | pThis->m_aClients[ClientID].m_TimeConnected = time_get();
18 | pThis->m_Network.Send(ClientID, "Authentication required:");
19 |
20 | return 0;
21 | }
22 |
23 | int CServer::DelClientCallback(int ClientID, const char *pReason, void *pUser)
24 | {
25 | CServer *pThis = (CServer *)pUser;
26 |
27 | char aAddrStr[NETADDR_MAXSTRSIZE];
28 | net_addr_str(pThis->m_Network.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true);
29 | if(pThis->Main()->Config()->m_Verbose)
30 | dbg_msg("server", "Client dropped. ncid=%d addr=%s reason='%s'", ClientID, aAddrStr, pReason);
31 |
32 | if(pThis->m_aClients[ClientID].m_State == CClient::STATE_AUTHED)
33 | pThis->Main()->OnDelClient(ClientID);
34 | pThis->m_aClients[ClientID].m_State = CClient::STATE_EMPTY;
35 |
36 | return 0;
37 | }
38 |
39 | int CServer::Init(CMain *pMain, const char *Bind, int Port)
40 | {
41 | m_pMain = pMain;
42 | m_NetBan.Init();
43 |
44 | for(int i = 0; i < NET_MAX_CLIENTS; i++)
45 | m_aClients[i].m_State = CClient::STATE_EMPTY;
46 |
47 | m_Ready = false;
48 |
49 | if(Port == 0)
50 | {
51 | dbg_msg("server", "Will not bind to port 0.");
52 | return 1;
53 | }
54 |
55 | NETADDR BindAddr;
56 | if(Bind[0] && net_host_lookup(Bind, &BindAddr, NETTYPE_ALL) == 0)
57 | {
58 | // got bindaddr
59 | BindAddr.type = NETTYPE_ALL;
60 | BindAddr.port = Port;
61 | }
62 | else
63 | {
64 | mem_zero(&BindAddr, sizeof(BindAddr));
65 | BindAddr.type = NETTYPE_ALL;
66 | BindAddr.port = Port;
67 | }
68 |
69 | if(m_Network.Open(BindAddr, &m_NetBan))
70 | {
71 | m_Network.SetCallbacks(NewClientCallback, DelClientCallback, this);
72 | m_Ready = true;
73 | dbg_msg("server", "Bound to %s:%d", Bind, Port);
74 | return 0;
75 | }
76 | else
77 | dbg_msg("server", "Couldn't open socket. Port (%d) might already be in use.", Port);
78 |
79 | return 1;
80 | }
81 |
82 | void CServer::Update()
83 | {
84 | if(!m_Ready)
85 | return;
86 |
87 | m_NetBan.Update();
88 | m_Network.Update();
89 |
90 | char aBuf[NET_MAX_PACKETSIZE];
91 | int ClientID;
92 |
93 | while(m_Network.Recv(aBuf, (int)(sizeof(aBuf))-1, &ClientID))
94 | {
95 | dbg_assert(m_aClients[ClientID].m_State != CClient::STATE_EMPTY, "Got message from empty slot.");
96 | if(m_aClients[ClientID].m_State == CClient::STATE_CONNECTED)
97 | {
98 | int ID = -1;
99 | char aUsername[128] = {0};
100 | char aPassword[128] = {0};
101 | const char *pTmp;
102 |
103 | if(!(pTmp = str_find(aBuf, ":"))
104 | || (unsigned)(pTmp - aBuf) > sizeof(aUsername) || (unsigned)(str_length(pTmp) - 1) > sizeof(aPassword))
105 | {
106 | m_Network.NetBan()->BanAddr(m_Network.ClientAddr(ClientID), 60, "You're an idiot, go away.");
107 | m_Network.Drop(ClientID, "Fuck off.");
108 | return;
109 | }
110 |
111 | str_copy(aUsername, aBuf, pTmp - aBuf + 1);
112 | str_copy(aPassword, pTmp + 1, sizeof(aPassword));
113 | if(!*aUsername || !*aPassword)
114 | {
115 | m_Network.NetBan()->BanAddr(m_Network.ClientAddr(ClientID), 60, "You're an idiot, go away.");
116 | m_Network.Drop(ClientID, "Username and password must not be blank.");
117 | return;
118 | }
119 |
120 | for(int i = 0; i < NET_MAX_CLIENTS; i++)
121 | {
122 | if(!Main()->Client(i)->m_Active)
123 | continue;
124 |
125 | if(str_comp(Main()->Client(i)->m_aUsername, aUsername) == 0 && str_comp(Main()->Client(i)->m_aPassword, aPassword) == 0)
126 | ID = i;
127 | }
128 |
129 | if(ID == -1)
130 | {
131 | m_Network.NetBan()->BanAddr(m_Network.ClientAddr(ClientID), 60, "Wrong username and/or password.");
132 | m_Network.Drop(ClientID, "Wrong username and/or password.");
133 | }
134 | else if(Main()->Client(ID)->m_ClientNetID != -1)
135 | {
136 | m_Network.Drop(ClientID, "Only one connection per user allowed.");
137 | }
138 | else
139 | {
140 | m_aClients[ClientID].m_State = CClient::STATE_AUTHED;
141 | m_aClients[ClientID].m_LastReceived = time_get();
142 | m_Network.Send(ClientID, "Authentication successful. Access granted.");
143 |
144 | if(m_Network.ClientAddr(ClientID)->type == NETTYPE_IPV4)
145 | m_Network.Send(ClientID, "You are connecting via: IPv4");
146 | else if(m_Network.ClientAddr(ClientID)->type == NETTYPE_IPV6)
147 | m_Network.Send(ClientID, "You are connecting via: IPv6");
148 |
149 | if(Main()->Config()->m_Verbose)
150 | dbg_msg("server", "ncid=%d authed", ClientID);
151 | Main()->OnNewClient(ClientID, ID);
152 | }
153 | }
154 | else if(m_aClients[ClientID].m_State == CClient::STATE_AUTHED)
155 | {
156 | m_aClients[ClientID].m_LastReceived = time_get();
157 | if(Main()->Config()->m_Verbose)
158 | dbg_msg("server", "ncid=%d cmd='%s'", ClientID, aBuf);
159 |
160 | if(str_comp(aBuf, "logout") == 0)
161 | m_Network.Drop(ClientID, "Logout. Bye Bye ~");
162 | else
163 | Main()->HandleMessage(ClientID, aBuf);
164 | }
165 | }
166 |
167 | for(int i = 0; i < NET_MAX_CLIENTS; ++i)
168 | {
169 | if(m_aClients[i].m_State == CClient::STATE_CONNECTED &&
170 | time_get() > m_aClients[i].m_TimeConnected + 5 * time_freq())
171 | {
172 | m_Network.NetBan()->BanAddr(m_Network.ClientAddr(i), 30, "Authentication timeout.");
173 | m_Network.Drop(i, "Authentication timeout.");
174 | }
175 | else if(m_aClients[i].m_State == CClient::STATE_AUTHED &&
176 | time_get() > m_aClients[i].m_LastReceived + 15 * time_freq())
177 | m_Network.Drop(i, "Timeout.");
178 | }
179 | }
180 |
181 | void CServer::Send(int ClientID, const char *pLine)
182 | {
183 | if(!m_Ready)
184 | return;
185 |
186 | if(ClientID == -1)
187 | {
188 | for(int i = 0; i < NET_MAX_CLIENTS; i++)
189 | {
190 | if(m_aClients[i].m_State == CClient::STATE_AUTHED)
191 | m_Network.Send(i, pLine);
192 | }
193 | }
194 | else if(ClientID >= 0 && ClientID < NET_MAX_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_AUTHED)
195 | m_Network.Send(ClientID, pLine);
196 | }
197 |
198 | void CServer::Shutdown()
199 | {
200 | if(!m_Ready)
201 | return;
202 |
203 | m_Network.Close();
204 | }
205 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ServerStatus中文版:
2 |
3 | * ServerStatus中文版是一个酷炫高逼格的云探针、云监控、服务器云监控、多服务器探针~。
4 | * 在线演示:https://tz.cloudcpp.com
5 |
6 | [](https://github.com/cppla/ServerStatus)
7 | [](https://github.com/cppla/ServerStatus)
8 | [](https://github.com/cppla/ServerStatus)
9 | [](https://github.com/cppla/ServerStatus)
10 |
11 | 
12 |
13 | `Watchdog触发式告警,interval只是为了防止频繁收到报警,并不是探测间隔。值得注意的是Exprtk使用窄字符类型,中文等Unicode字符无法解析计算。 AI已经能够取代大部分程序员`
14 |
15 |
16 | # 部署:
17 |
18 | 【服务端】:
19 | ```bash
20 |
21 | `Docker`:
22 |
23 | wget --no-check-certificate -qO ~/serverstatus-config.json https://raw.githubusercontent.com/cppla/ServerStatus/master/server/config.json && mkdir ~/serverstatus-monthtraffic
24 | docker run -d --restart=always --name=serverstatus -v ~/serverstatus-config.json:/ServerStatus/server/config.json -v ~/serverstatus-monthtraffic:/usr/share/nginx/html/json -p 80:80 -p 35601:35601 cppla/serverstatus:latest
25 |
26 | `Docker-compose(推荐)`: docker-compose up -d
27 | ```
28 |
29 | 【客户端】:
30 | ```bash
31 | wget --no-check-certificate -qO client-linux.py 'https://raw.githubusercontent.com/cppla/ServerStatus/master/clients/client-linux.py' && nohup python3 client-linux.py SERVER={$SERVER} USER={$USER} PASSWORD={$PASSWORD} >/dev/null 2>&1 &
32 |
33 | eg:
34 | wget --no-check-certificate -qO client-linux.py 'https://raw.githubusercontent.com/cppla/ServerStatus/master/clients/client-linux.py' && nohup python3 client-linux.py SERVER=45.79.67.132 USER=s04 >/dev/null 2>&1 &
35 | ```
36 |
37 |
38 | # 教程:
39 |
40 | **【服务端配置】**
41 |
42 | #### 一、生成服务端程序
43 | ```
44 | `Debian/Ubuntu`: apt-get -y install gcc g++ make libcurl4-openssl-dev
45 | `Centos/Redhat`: yum -y install gcc gcc-c++ make libcurl-devel
46 |
47 | cd ServerStatus/server && make
48 | ./sergate
49 | ```
50 | 如果没错误提示,OK,ctrl+c关闭;如果有错误提示,检查35601端口是否被占用
51 |
52 | #### 二、修改配置文件
53 | ```diff
54 | ! watchdog rule 可以为任何已知字段的表达式。注意Exprtk库默认使用窄字符类型,中文等Unicode字符无法解析计算,等待修复
55 | ! watchdog interval 最小通知间隔
56 | ! watchdog callback 可自定义为Post方法的URL,告警内容将拼接其后并发起回调
57 |
58 | ! Telegram: https://api.telegram.org/bot你自己的密钥/sendMessage?parse_mode=HTML&disable_web_page_preview=true&chat_id=你自己的标识&text=
59 | ! Server酱: https://sctapi.ftqq.com/你自己的密钥.send?title=ServerStatus&desp=
60 | ! PushDeer: https://api2.pushdeer.com/message/push?pushkey=你自己的密钥&text=
61 | ! HttpBasicAuth: https://用户名:密码@你自己的域名/api/push?message=
62 | ```
63 |
64 | ```
65 | {
66 | "servers":
67 | [
68 | {
69 | "username": "s01",
70 | "name": "vps-1",
71 | "type": "kvm",
72 | "host": "chengdu",
73 | "location": "🇨🇳",
74 | "password": "USER_DEFAULT_PASSWORD",
75 | "monthstart": 1
76 | }
77 | ],
78 | "monitors": [
79 | {
80 | "name": "抖音",
81 | "host": "https://www.douyin.com",
82 | "interval": 600,
83 | "type": "https"
84 | },
85 | {
86 | "name": "百度",
87 | "host": "https://www.baidu.com",
88 | "interval": 600,
89 | "type": "https"
90 | }
91 | ],
92 | "sslcerts": [
93 | {
94 | "name": "demo域名",
95 | "domain": "https://demo.example.com",
96 | "port": 443,
97 | "interval": 600,
98 | "callback": "https://yourSMSurl"
99 | }
100 | ],
101 | "watchdog":
102 | [
103 | {
104 | "name": "服务器负载高监控,排除内存大于32G物理机,同时排除node1机器",
105 | "rule": "cpu>90&load_1>4&memory_total<33554432&name!='node1'",
106 | "interval": 600,
107 | "callback": "https://yourSMSurl"
108 | },
109 | {
110 | "name": "服务器内存使用率过高监控,排除小于1G的机器",
111 | "rule": "(memory_used/memory_total)*100>90&memory_total>1048576",
112 | "interval": 600,
113 | "callback": "https://yourSMSurl"
114 | },
115 | {
116 | "name": "服务器宕机告警",
117 | "rule": "online4=0&online6=0",
118 | "interval": 600,
119 | "callback": "https://yourSMSurl"
120 | },
121 | {
122 | "name": "DDOS和CC攻击监控,限制甲骨文机器",
123 | "rule": "tcp_count>600&type='Oracle'",
124 | "interval": 300,
125 | "callback": "https://yourSMSurl"
126 | },
127 | {
128 | "name": "服务器月出口流量999GB告警",
129 | "rule": "(network_out-last_network_out)/1024/1024/1024>999",
130 | "interval": 3600,
131 | "callback": "https://yourSMSurl"
132 | },
133 | {
134 | "name": "阿里云服务器流量18GB告警,限制username为乌兰察布",
135 | "rule": "(network_out-last_network_out)/1024/1024/1024>18&(username='wlcb1'|username='wlcb2'|username='wlcb3'|username='wlcb4')",
136 | "interval": 3600,
137 | "callback": "https://yourSMSurl"
138 | },
139 | {
140 | "name": "重要线路丢包率过高检查",
141 | "rule": "(ping_10010>10|ping_189>10|ping_10086>10)&(host='sgp'|host='qqhk'|host='hk-21-x'|host='hk-31-x')",
142 | "interval": 600,
143 | "callback": "https://yourSMSurl"
144 | },
145 | {
146 | "name": "你可以组合任何已知字段的表达式",
147 | "rule": "(hdd_used/hdd_total)*100>95",
148 | "interval": 1800,
149 | "callback": "https://yourSMSurl"
150 | }
151 | ]
152 | }
153 | ```
154 |
155 | #### 三、拷贝ServerStatus/status到你的网站目录
156 | 例如:
157 | ```
158 | sudo cp -r ServerStatus/web/* /home/wwwroot/default
159 | ```
160 |
161 | #### 四、运行服务端:
162 | web-dir参数为上一步设置的网站根目录,务必修改成自己网站的路径
163 | ```
164 | ./sergate --config=config.json --web-dir=/home/wwwroot/default
165 | ```
166 |
167 | **【客户端配置】**
168 |
169 | #### client-linux.py Linux版
170 | ```bash
171 | # 1、修改 client-linux.py 中的 SERVER、username、password
172 | python3 client-linux.py
173 | # 2、以传参的方式启动
174 | python3 client-linux.py SERVER=127.0.0.1 USER=s01
175 |
176 | ```
177 |
178 | #### client-psutil.py 跨平台版
179 | ```bash
180 | # 安装依赖
181 | # Debian/Ubuntu
182 | apt -y install python3-psutil
183 | # Centos/Redhat
184 | yum -y install python3-pip gcc python3-devel && pip3 install psutil
185 | # Windows: 从 https://pypi.org/project/psutil/ 安装
186 | ```
187 |
188 | #### 后台运行与开机启动
189 | ```bash
190 | # 后台运行
191 | nohup python3 client-linux.py &
192 |
193 | # 开机启动 (crontab -e)
194 | @reboot /usr/bin/python3 /path/to/client-linux.py
195 | ```
196 |
197 | # Make Better
198 |
199 | * BotoX:https://github.com/BotoX/ServerStatus
200 | * mojeda: https://github.com/mojeda
201 | * mojeda's ServerStatus: https://github.com/mojeda/ServerStatus
202 | * BlueVM's project: http://www.lowendtalk.com/discussion/comment/169690#Comment_169690
203 |
--------------------------------------------------------------------------------
/server/include/json.h:
--------------------------------------------------------------------------------
1 |
2 | /* vim: set et ts=3 sw=3 sts=3 ft=c:
3 | *
4 | * Copyright (C) 2012, 2013, 2014 James McLaughlin et al. All rights reserved.
5 | * https://github.com/udp/json-parser
6 | *
7 | * Redistribution and use in source and binary forms, with or without
8 | * modification, are permitted provided that the following conditions
9 | * are met:
10 | *
11 | * 1. Redistributions of source code must retain the above copyright
12 | * notice, this list of conditions and the following disclaimer.
13 | *
14 | * 2. Redistributions in binary form must reproduce the above copyright
15 | * notice, this list of conditions and the following disclaimer in the
16 | * documentation and/or other materials provided with the distribution.
17 | *
18 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 | * SUCH DAMAGE.
29 | */
30 |
31 | #ifndef _JSON_H
32 | #define _JSON_H
33 |
34 | #ifndef json_char
35 | #define json_char char
36 | #endif
37 |
38 | #ifndef json_int_t
39 | #ifndef _MSC_VER
40 | #include
41 | #define json_int_t int64_t
42 | #else
43 | #define json_int_t __int64
44 | #endif
45 | #endif
46 |
47 | #include
48 |
49 | #ifdef __cplusplus
50 |
51 | #include
52 |
53 | extern "C"
54 | {
55 |
56 | #endif
57 |
58 | typedef struct
59 | {
60 | unsigned long max_memory;
61 | int settings;
62 |
63 | /* Custom allocator support (leave null to use malloc/free)
64 | */
65 |
66 | void * (* mem_alloc) (size_t, int zero, void * user_data);
67 | void (* mem_free) (void *, void * user_data);
68 |
69 | void * user_data; /* will be passed to mem_alloc and mem_free */
70 |
71 | } json_settings;
72 |
73 | #define json_enable_comments 0x01
74 |
75 | typedef enum
76 | {
77 | json_none,
78 | json_object,
79 | json_array,
80 | json_integer,
81 | json_double,
82 | json_string,
83 | json_boolean,
84 | json_null
85 |
86 | } json_type;
87 |
88 | extern const struct _json_value json_value_none;
89 |
90 | typedef struct _json_value
91 | {
92 | struct _json_value * parent;
93 |
94 | json_type type;
95 |
96 | union
97 | {
98 | int boolean;
99 | json_int_t integer;
100 | double dbl;
101 |
102 | struct
103 | {
104 | unsigned int length;
105 | json_char * ptr; /* null terminated */
106 |
107 | } string;
108 |
109 | struct
110 | {
111 | unsigned int length;
112 |
113 | struct
114 | {
115 | json_char * name;
116 | unsigned int name_length;
117 |
118 | struct _json_value * value;
119 |
120 | } * values;
121 |
122 | #if defined(__cplusplus) && __cplusplus >= 201103L
123 | decltype(values) begin () const
124 | { return values;
125 | }
126 | decltype(values) end () const
127 | { return values + length;
128 | }
129 | #endif
130 |
131 | } object;
132 |
133 | struct
134 | {
135 | unsigned int length;
136 | struct _json_value ** values;
137 |
138 | #if defined(__cplusplus) && __cplusplus >= 201103L
139 | decltype(values) begin () const
140 | { return values;
141 | }
142 | decltype(values) end () const
143 | { return values + length;
144 | }
145 | #endif
146 |
147 | } array;
148 |
149 | } u;
150 |
151 | union
152 | {
153 | struct _json_value * next_alloc;
154 | void * object_mem;
155 |
156 | } _reserved;
157 |
158 |
159 | /* Some C++ operator sugar */
160 |
161 | #ifdef __cplusplus
162 |
163 | public:
164 |
165 | inline _json_value ()
166 | { memset (this, 0, sizeof (_json_value));
167 | }
168 |
169 | inline const struct _json_value &operator [] (int index) const
170 | {
171 | if (type != json_array || index < 0
172 | || ((unsigned int) index) >= u.array.length)
173 | {
174 | return json_value_none;
175 | }
176 |
177 | return *u.array.values [index];
178 | }
179 |
180 | inline const struct _json_value &operator [] (const char * index) const
181 | {
182 | if (type != json_object)
183 | return json_value_none;
184 |
185 | for (unsigned int i = 0; i < u.object.length; ++ i)
186 | if (!strcmp (u.object.values [i].name, index))
187 | return *u.object.values [i].value;
188 |
189 | return json_value_none;
190 | }
191 |
192 | inline operator const char * () const
193 | {
194 | switch (type)
195 | {
196 | case json_string:
197 | return u.string.ptr;
198 |
199 | default:
200 | return "";
201 | };
202 | }
203 |
204 | inline operator json_int_t () const
205 | {
206 | switch (type)
207 | {
208 | case json_integer:
209 | return u.integer;
210 |
211 | case json_double:
212 | return (json_int_t) u.dbl;
213 |
214 | default:
215 | return 0;
216 | };
217 | }
218 |
219 | inline operator bool () const
220 | {
221 | if (type != json_boolean)
222 | return false;
223 |
224 | return u.boolean != 0;
225 | }
226 |
227 | inline operator double () const
228 | {
229 | switch (type)
230 | {
231 | case json_integer:
232 | return (double) u.integer;
233 |
234 | case json_double:
235 | return u.dbl;
236 |
237 | default:
238 | return 0;
239 | };
240 | }
241 |
242 | #endif
243 |
244 | } json_value;
245 |
246 | json_value * json_parse (const json_char * json,
247 | size_t length);
248 |
249 | #define json_error_max 128
250 | json_value * json_parse_ex (json_settings * settings,
251 | const json_char * json,
252 | size_t length,
253 | char * error);
254 |
255 | void json_value_free (json_value *);
256 |
257 |
258 | /* Not usually necessary, unless you used a custom mem_alloc and now want to
259 | * use a custom mem_free.
260 | */
261 | void json_value_free_ex (json_settings * settings,
262 | json_value *);
263 |
264 |
265 | #ifdef __cplusplus
266 | } /* extern "C" */
267 | #endif
268 |
269 | #endif
270 |
--------------------------------------------------------------------------------
/server/src/argparse.c:
--------------------------------------------------------------------------------
1 | #include "argparse.h"
2 |
3 | #if defined(__cplusplus)
4 | extern "C" {
5 | #endif
6 |
7 | #define OPT_UNSET 1
8 |
9 | static const char *
10 | prefix_skip(const char *str, const char *prefix)
11 | {
12 | size_t len = strlen(prefix);
13 | return strncmp(str, prefix, len) ? NULL : str + len;
14 | }
15 |
16 | int
17 | prefix_cmp(const char *str, const char *prefix)
18 | {
19 | for (;; str++, prefix++)
20 | if (!*prefix)
21 | return 0;
22 | else if (*str != *prefix)
23 | return (unsigned char)*prefix - (unsigned char)*str;
24 | }
25 |
26 | static void
27 | argparse_error(struct argparse *this_, const struct argparse_option *opt,
28 | const char *reason)
29 | {
30 | if (!strncmp(this_->argv[0], "--", 2)) {
31 | fprintf(stderr, "error: option `%s` %s\n", opt->long_name, reason);
32 | exit(-1);
33 | } else {
34 | fprintf(stderr, "error: option `%c` %s\n", opt->short_name, reason);
35 | exit(-1);
36 | }
37 | }
38 |
39 | static int
40 | argparse_getvalue(struct argparse *this_, const struct argparse_option *opt,
41 | int flags)
42 | {
43 | const char *s = NULL;
44 | if (!opt->value)
45 | goto skipped;
46 | switch (opt->type) {
47 | case ARGPARSE_OPT_BOOLEAN:
48 | if (flags & OPT_UNSET) {
49 | *(int *)opt->value = *(int *)opt->value - 1;
50 | } else {
51 | *(int *)opt->value = *(int *)opt->value + 1;
52 | }
53 | if (*(int *)opt->value < 0) {
54 | *(int *)opt->value = 0;
55 | }
56 | break;
57 | case ARGPARSE_OPT_BIT:
58 | if (flags & OPT_UNSET) {
59 | *(int *)opt->value &= ~opt->data;
60 | } else {
61 | *(int *)opt->value |= opt->data;
62 | }
63 | break;
64 | case ARGPARSE_OPT_STRING:
65 | if (this_->optvalue) {
66 | *(const char **)opt->value = this_->optvalue;
67 | this_->optvalue = NULL;
68 | } else if (this_->argc > 1) {
69 | this_->argc--;
70 | *(const char **)opt->value = *++this_->argv;
71 | } else {
72 | argparse_error(this_, opt, "requires a value");
73 | }
74 | break;
75 | case ARGPARSE_OPT_INTEGER:
76 | if (this_->optvalue) {
77 | *(int *)opt->value = strtol(this_->optvalue, (char **)&s, 0);
78 | this_->optvalue = NULL;
79 | } else if (this_->argc > 1) {
80 | this_->argc--;
81 | *(int *)opt->value = strtol(*++this_->argv, (char **)&s, 0);
82 | } else {
83 | argparse_error(this_, opt, "requires a value");
84 | }
85 | if (*s)
86 | argparse_error(this_, opt, "expects a numerical value");
87 | break;
88 | default:
89 | assert(0);
90 | }
91 |
92 | skipped:
93 | if (opt->callback) {
94 | return opt->callback(this_, opt);
95 | }
96 |
97 | return 0;
98 | }
99 |
100 | static void
101 | argparse_options_check(const struct argparse_option *options)
102 | {
103 | for (; options->type != ARGPARSE_OPT_END; options++) {
104 | switch (options->type) {
105 | case ARGPARSE_OPT_END:
106 | case ARGPARSE_OPT_BOOLEAN:
107 | case ARGPARSE_OPT_BIT:
108 | case ARGPARSE_OPT_INTEGER:
109 | case ARGPARSE_OPT_STRING:
110 | continue;
111 | default:
112 | fprintf(stderr, "wrong option type: %d", options->type);
113 | break;
114 | }
115 | }
116 | }
117 |
118 | static int
119 | argparse_short_opt(struct argparse *this_, const struct argparse_option *options)
120 | {
121 | for (; options->type != ARGPARSE_OPT_END; options++) {
122 | if (options->short_name == *this_->optvalue) {
123 | this_->optvalue = this_->optvalue[1] ? this_->optvalue + 1 : NULL;
124 | return argparse_getvalue(this_, options, 0);
125 | }
126 | }
127 | return -2;
128 | }
129 |
130 | static int
131 | argparse_long_opt(struct argparse *this_, const struct argparse_option *options)
132 | {
133 | for (; options->type != ARGPARSE_OPT_END; options++) {
134 | const char *rest;
135 | int opt_flags = 0;
136 | if (!options->long_name)
137 | continue;
138 |
139 | rest = prefix_skip(this_->argv[0] + 2, options->long_name);
140 | if (!rest) {
141 | // Negation allowed?
142 | if (options->flags & OPT_NONEG) {
143 | continue;
144 | }
145 | // Only boolean/bit allow negation.
146 | if (options->type != ARGPARSE_OPT_BOOLEAN && options->type != ARGPARSE_OPT_BIT) {
147 | continue;
148 | }
149 |
150 | if (!prefix_cmp(this_->argv[0] + 2, "no-")) {
151 | rest = prefix_skip(this_->argv[0] + 2 + 3, options->long_name);
152 | if (!rest)
153 | continue;
154 | opt_flags |= OPT_UNSET;
155 | } else {
156 | continue;
157 | }
158 | }
159 | if (*rest) {
160 | if (*rest != '=')
161 | continue;
162 | this_->optvalue = rest + 1;
163 | }
164 | return argparse_getvalue(this_, options, opt_flags);
165 | }
166 | return -2;
167 | }
168 |
169 | int
170 | argparse_init(struct argparse *this_, struct argparse_option *options,
171 | const char *usage, int flags)
172 | {
173 | memset(this_, 0, sizeof(*this_));
174 | this_->options = options;
175 | this_->usage = usage;
176 | this_->flags = flags;
177 | return 0;
178 | }
179 |
180 | int
181 | argparse_parse(struct argparse *this_, int argc, const char **argv)
182 | {
183 | this_->argc = argc - 1;
184 | this_->argv = argv + 1;
185 | this_->out = argv;
186 |
187 | argparse_options_check(this_->options);
188 |
189 | for (; this_->argc; this_->argc--, this_->argv++) {
190 | const char *arg = this_->argv[0];
191 | if (arg[0] != '-' || !arg[1]) {
192 | if (this_->flags & ARGPARSE_STOP_AT_NON_OPTION) {
193 | goto end;
194 | }
195 | // if it's not option or is a single char '-', copy verbatimly
196 | this_->out[this_->cpidx++] = this_->argv[0];
197 | continue;
198 | }
199 | // short option
200 | if (arg[1] != '-') {
201 | this_->optvalue = arg + 1;
202 | switch (argparse_short_opt(this_, this_->options)) {
203 | case -1:
204 | break;
205 | case -2:
206 | goto unknown;
207 | }
208 | while (this_->optvalue) {
209 | switch (argparse_short_opt(this_, this_->options)) {
210 | case -1:
211 | break;
212 | case -2:
213 | goto unknown;
214 | }
215 | }
216 | continue;
217 | }
218 | // if '--' presents
219 | if (!arg[2]) {
220 | this_->argc--;
221 | this_->argv++;
222 | break;
223 | }
224 | // long option
225 | switch (argparse_long_opt(this_, this_->options)) {
226 | case -1:
227 | break;
228 | case -2:
229 | goto unknown;
230 | }
231 | continue;
232 |
233 | unknown:
234 | fprintf(stderr, "error: unknown option `%s`\n", this_->argv[0]);
235 | argparse_usage(this_);
236 | exit(0);
237 | }
238 |
239 | end:
240 | memmove(this_->out + this_->cpidx, this_->argv,
241 | this_->argc * sizeof(*this_->out));
242 | this_->out[this_->cpidx + this_->argc] = NULL;
243 |
244 | return this_->cpidx + this_->argc;
245 | }
246 |
247 | void
248 | argparse_usage(struct argparse *this_)
249 | {
250 | fprintf(stdout, "Usage: %s\n", this_->usage);
251 | fputc('\n', stdout);
252 |
253 | const struct argparse_option *options;
254 |
255 | // figure out best width
256 | size_t usage_opts_width = 0;
257 | size_t len;
258 | options = this_->options;
259 | for (; options->type != ARGPARSE_OPT_END; options++) {
260 | len = 0;
261 | if ((options)->short_name) {
262 | len += 2;
263 | }
264 | if ((options)->short_name && (options)->long_name) {
265 | len += 2; // separator ", "
266 | }
267 | if ((options)->long_name) {
268 | len += strlen((options)->long_name) + 2;
269 | }
270 | if (options->type == ARGPARSE_OPT_INTEGER) {
271 | len += strlen("=");
272 | } else if (options->type == ARGPARSE_OPT_STRING) {
273 | len += strlen("=");
274 | }
275 | len = ceil((float)len / 4) * 4;
276 | if (usage_opts_width < len) {
277 | usage_opts_width = len;
278 | }
279 | }
280 | usage_opts_width += 4; // 4 spaces prefix
281 |
282 | options = this_->options;
283 | for (; options->type != ARGPARSE_OPT_END; options++) {
284 | size_t pos;
285 | int pad;
286 | pos = fprintf(stdout, " ");
287 | if (options->short_name) {
288 | pos += fprintf(stdout, "-%c", options->short_name);
289 | }
290 | if (options->long_name && options->short_name) {
291 | pos += fprintf(stdout, ", ");
292 | }
293 | if (options->long_name) {
294 | pos += fprintf(stdout, "--%s", options->long_name);
295 | }
296 | if (options->type == ARGPARSE_OPT_INTEGER) {
297 | pos += fprintf(stdout, "=");
298 | } else if (options->type == ARGPARSE_OPT_STRING) {
299 | pos += fprintf(stdout, "=");
300 | }
301 | if (pos <= usage_opts_width) {
302 | pad = usage_opts_width - pos;
303 | } else {
304 | fputc('\n', stdout);
305 | pad = usage_opts_width;
306 | }
307 | fprintf(stdout, "%*s%s\n", pad + 2, "", options->help);
308 | }
309 | }
310 |
311 | int
312 | argparse_help_cb(struct argparse *this_, const struct argparse_option *option)
313 | {
314 | (void)option;
315 | argparse_usage(this_);
316 | exit(0);
317 | return 0;
318 | }
319 |
320 | #if defined(__cplusplus)
321 | }
322 | #endif
323 |
--------------------------------------------------------------------------------
/server/src/netban.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "netban.h"
3 |
4 | bool CNetBan::StrAllnum(const char *pStr)
5 | {
6 | while(*pStr)
7 | {
8 | if(!(*pStr >= '0' && *pStr <= '9'))
9 | return false;
10 | pStr++;
11 | }
12 | return true;
13 | }
14 |
15 |
16 | CNetBan::CNetHash::CNetHash(const NETADDR *pAddr)
17 | {
18 | if(pAddr->type==NETTYPE_IPV4)
19 | m_Hash = (pAddr->ip[0]+pAddr->ip[1]+pAddr->ip[2]+pAddr->ip[3])&0xFF;
20 | else
21 | m_Hash = (pAddr->ip[0]+pAddr->ip[1]+pAddr->ip[2]+pAddr->ip[3]+pAddr->ip[4]+pAddr->ip[5]+pAddr->ip[6]+pAddr->ip[7]+
22 | pAddr->ip[8]+pAddr->ip[9]+pAddr->ip[10]+pAddr->ip[11]+pAddr->ip[12]+pAddr->ip[13]+pAddr->ip[14]+pAddr->ip[15])&0xFF;
23 | m_HashIndex = 0;
24 | }
25 |
26 | CNetBan::CNetHash::CNetHash(const CNetRange *pRange)
27 | {
28 | m_Hash = 0;
29 | m_HashIndex = 0;
30 | for(int i = 0; pRange->m_LB.ip[i] == pRange->m_UB.ip[i]; ++i)
31 | {
32 | m_Hash += pRange->m_LB.ip[i];
33 | ++m_HashIndex;
34 | }
35 | m_Hash &= 0xFF;
36 | }
37 |
38 | int CNetBan::CNetHash::MakeHashArray(const NETADDR *pAddr, CNetHash aHash[17])
39 | {
40 | int Length = pAddr->type==NETTYPE_IPV4 ? 4 : 16;
41 | aHash[0].m_Hash = 0;
42 | aHash[0].m_HashIndex = 0;
43 | for(int i = 1, Sum = 0; i <= Length; ++i)
44 | {
45 | Sum += pAddr->ip[i-1];
46 | aHash[i].m_Hash = Sum&0xFF;
47 | aHash[i].m_HashIndex = i%Length;
48 | }
49 | return Length;
50 | }
51 |
52 |
53 | template
54 | typename CNetBan::CBan *CNetBan::CBanPool::Add(const T *pData, const CBanInfo *pInfo, const CNetHash *pNetHash)
55 | {
56 | if(!m_pFirstFree)
57 | return 0;
58 |
59 | // create new ban
60 | CBan *pBan = m_pFirstFree;
61 | pBan->m_Data = *pData;
62 | pBan->m_Info = *pInfo;
63 | pBan->m_NetHash = *pNetHash;
64 | if(pBan->m_pNext)
65 | pBan->m_pNext->m_pPrev = pBan->m_pPrev;
66 | if(pBan->m_pPrev)
67 | pBan->m_pPrev->m_pNext = pBan->m_pNext;
68 | else
69 | m_pFirstFree = pBan->m_pNext;
70 |
71 | // add it to the hash list
72 | if(m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash])
73 | m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]->m_pHashPrev = pBan;
74 | pBan->m_pHashPrev = 0;
75 | pBan->m_pHashNext = m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash];
76 | m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash] = pBan;
77 |
78 | // insert it into the used list
79 | if(m_pFirstUsed)
80 | {
81 | for(CBan *p = m_pFirstUsed; ; p = p->m_pNext)
82 | {
83 | if(p->m_Info.m_Expires == CBanInfo::EXPIRES_NEVER || (pInfo->m_Expires != CBanInfo::EXPIRES_NEVER && pInfo->m_Expires <= p->m_Info.m_Expires))
84 | {
85 | // insert before
86 | pBan->m_pNext = p;
87 | pBan->m_pPrev = p->m_pPrev;
88 | if(p->m_pPrev)
89 | p->m_pPrev->m_pNext = pBan;
90 | else
91 | m_pFirstUsed = pBan;
92 | p->m_pPrev = pBan;
93 | break;
94 | }
95 |
96 | if(!p->m_pNext)
97 | {
98 | // last entry
99 | p->m_pNext = pBan;
100 | pBan->m_pPrev = p;
101 | pBan->m_pNext = 0;
102 | break;
103 | }
104 | }
105 | }
106 | else
107 | {
108 | m_pFirstUsed = pBan;
109 | pBan->m_pNext = pBan->m_pPrev = 0;
110 | }
111 |
112 | // update ban count
113 | ++m_CountUsed;
114 |
115 | return pBan;
116 | }
117 |
118 | template
119 | int CNetBan::CBanPool::Remove(CBan *pBan)
120 | {
121 | if(pBan == 0)
122 | return -1;
123 |
124 | // remove from hash list
125 | if(pBan->m_pHashNext)
126 | pBan->m_pHashNext->m_pHashPrev = pBan->m_pHashPrev;
127 | if(pBan->m_pHashPrev)
128 | pBan->m_pHashPrev->m_pHashNext = pBan->m_pHashNext;
129 | else
130 | m_paaHashList[pBan->m_NetHash.m_HashIndex][pBan->m_NetHash.m_Hash] = pBan->m_pHashNext;
131 | pBan->m_pHashNext = pBan->m_pHashPrev = 0;
132 |
133 | // remove from used list
134 | if(pBan->m_pNext)
135 | pBan->m_pNext->m_pPrev = pBan->m_pPrev;
136 | if(pBan->m_pPrev)
137 | pBan->m_pPrev->m_pNext = pBan->m_pNext;
138 | else
139 | m_pFirstUsed = pBan->m_pNext;
140 |
141 | // add to recycle list
142 | if(m_pFirstFree)
143 | m_pFirstFree->m_pPrev = pBan;
144 | pBan->m_pPrev = 0;
145 | pBan->m_pNext = m_pFirstFree;
146 | m_pFirstFree = pBan;
147 |
148 | // update ban count
149 | --m_CountUsed;
150 |
151 | return 0;
152 | }
153 |
154 | template
155 | void CNetBan::CBanPool::Update(CBan *pBan, const CBanInfo *pInfo)
156 | {
157 | pBan->m_Info = *pInfo;
158 |
159 | // remove from used list
160 | if(pBan->m_pNext)
161 | pBan->m_pNext->m_pPrev = pBan->m_pPrev;
162 | if(pBan->m_pPrev)
163 | pBan->m_pPrev->m_pNext = pBan->m_pNext;
164 | else
165 | m_pFirstUsed = pBan->m_pNext;
166 |
167 | // insert it into the used list
168 | if(m_pFirstUsed)
169 | {
170 | for(CBan *p = m_pFirstUsed; ; p = p->m_pNext)
171 | {
172 | if(p->m_Info.m_Expires == CBanInfo::EXPIRES_NEVER || (pInfo->m_Expires != CBanInfo::EXPIRES_NEVER && pInfo->m_Expires <= p->m_Info.m_Expires))
173 | {
174 | // insert before
175 | pBan->m_pNext = p;
176 | pBan->m_pPrev = p->m_pPrev;
177 | if(p->m_pPrev)
178 | p->m_pPrev->m_pNext = pBan;
179 | else
180 | m_pFirstUsed = pBan;
181 | p->m_pPrev = pBan;
182 | break;
183 | }
184 |
185 | if(!p->m_pNext)
186 | {
187 | // last entry
188 | p->m_pNext = pBan;
189 | pBan->m_pPrev = p;
190 | pBan->m_pNext = 0;
191 | break;
192 | }
193 | }
194 | }
195 | else
196 | {
197 | m_pFirstUsed = pBan;
198 | pBan->m_pNext = pBan->m_pPrev = 0;
199 | }
200 | }
201 |
202 | template
203 | void CNetBan::CBanPool::Reset()
204 | {
205 | mem_zero(m_paaHashList, sizeof(m_paaHashList));
206 | mem_zero(m_aBans, sizeof(m_aBans));
207 | m_pFirstUsed = 0;
208 | m_CountUsed = 0;
209 |
210 | for(int i = 1; i < MAX_BANS-1; ++i)
211 | {
212 | m_aBans[i].m_pNext = &m_aBans[i+1];
213 | m_aBans[i].m_pPrev = &m_aBans[i-1];
214 | }
215 |
216 | m_aBans[0].m_pNext = &m_aBans[1];
217 | m_aBans[MAX_BANS-1].m_pPrev = &m_aBans[MAX_BANS-2];
218 | m_pFirstFree = &m_aBans[0];
219 | }
220 |
221 | template
222 | typename CNetBan::CBan *CNetBan::CBanPool::Get(int Index) const
223 | {
224 | if(Index < 0 || Index >= Num())
225 | return 0;
226 |
227 | for(CNetBan::CBan *pBan = m_pFirstUsed; pBan; pBan = pBan->m_pNext, --Index)
228 | {
229 | if(Index == 0)
230 | return pBan;
231 | }
232 |
233 | return 0;
234 | }
235 |
236 |
237 | template
238 | void CNetBan::MakeBanInfo(const CBan *pBan, char *pBuf, unsigned BuffSize, int Type) const
239 | {
240 | if(pBan == 0 || pBuf == 0)
241 | {
242 | if(BuffSize > 0)
243 | pBuf[0] = 0;
244 | return;
245 | }
246 |
247 | // build type based part
248 | char aBuf[256];
249 | if(Type == MSGTYPE_PLAYER)
250 | str_copy(aBuf, "You have been banned", sizeof(aBuf));
251 | else
252 | {
253 | char aTemp[256];
254 | switch(Type)
255 | {
256 | case MSGTYPE_LIST:
257 | str_format(aBuf, sizeof(aBuf), "%s banned", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break;
258 | case MSGTYPE_BANADD:
259 | str_format(aBuf, sizeof(aBuf), "banned %s", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break;
260 | case MSGTYPE_BANREM:
261 | str_format(aBuf, sizeof(aBuf), "unbanned %s", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break;
262 | default:
263 | aBuf[0] = 0;
264 | }
265 | }
266 |
267 | // add info part
268 | if(pBan->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER)
269 | {
270 | int Mins = ((pBan->m_Info.m_Expires-time_timestamp()) + 59) / 60;
271 | if(Mins <= 1)
272 | str_format(pBuf, BuffSize, "%s for 1 minute (%s)", aBuf, pBan->m_Info.m_aReason);
273 | else
274 | str_format(pBuf, BuffSize, "%s for %d minutes (%s)", aBuf, Mins, pBan->m_Info.m_aReason);
275 | }
276 | else
277 | str_format(pBuf, BuffSize, "%s for life (%s)", aBuf, pBan->m_Info.m_aReason);
278 | }
279 |
280 | template
281 | int CNetBan::Ban(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason)
282 | {
283 | // do not ban localhost
284 | if(NetMatch(pData, &m_LocalhostIPV4) || NetMatch(pData, &m_LocalhostIPV6))
285 | {
286 | dbg_msg("net_ban", "ban failed (localhost)");
287 | return -1;
288 | }
289 |
290 | int Stamp = Seconds > 0 ? time_timestamp()+Seconds : CBanInfo::EXPIRES_NEVER;
291 |
292 | // set up info
293 | CBanInfo Info = {0};
294 | Info.m_Expires = Stamp;
295 | str_copy(Info.m_aReason, pReason, sizeof(Info.m_aReason));
296 |
297 | // check if it already exists
298 | CNetHash NetHash(pData);
299 | CBan *pBan = pBanPool->Find(pData, &NetHash);
300 | if(pBan)
301 | {
302 | // adjust the ban
303 | pBanPool->Update(pBan, &Info);
304 | char aBuf[128];
305 | MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_LIST);
306 | dbg_msg("net_ban", aBuf);
307 | return 1;
308 | }
309 |
310 | // add ban and print result
311 | pBan = pBanPool->Add(pData, &Info, &NetHash);
312 | if(pBan)
313 | {
314 | char aBuf[128];
315 | MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_BANADD);
316 | dbg_msg("net_ban", aBuf);
317 | return 0;
318 | }
319 | else
320 | dbg_msg("net_ban", "ban failed (full banlist)");
321 | return -1;
322 | }
323 |
324 | template
325 | int CNetBan::Unban(T *pBanPool, const typename T::CDataType *pData)
326 | {
327 | CNetHash NetHash(pData);
328 | CBan *pBan = pBanPool->Find(pData, &NetHash);
329 | if(pBan)
330 | {
331 | char aBuf[256];
332 | MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_BANREM);
333 | pBanPool->Remove(pBan);
334 | dbg_msg("net_ban", aBuf);
335 | return 0;
336 | }
337 | else
338 | dbg_msg("net_ban", "unban failed (invalid entry)");
339 | return -1;
340 | }
341 |
342 | void CNetBan::Init()
343 | {
344 | m_BanAddrPool.Reset();
345 | m_BanRangePool.Reset();
346 |
347 | net_host_lookup("localhost", &m_LocalhostIPV4, NETTYPE_IPV4);
348 | net_host_lookup("localhost", &m_LocalhostIPV6, NETTYPE_IPV6);
349 | }
350 |
351 | void CNetBan::Update()
352 | {
353 | int Now = time_timestamp();
354 |
355 | // remove expired bans
356 | char aBuf[256], aNetStr[256];
357 | while(m_BanAddrPool.First() && m_BanAddrPool.First()->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER && m_BanAddrPool.First()->m_Info.m_Expires < Now)
358 | {
359 | str_format(aBuf, sizeof(aBuf), "ban %s expired", NetToString(&m_BanAddrPool.First()->m_Data, aNetStr, sizeof(aNetStr)));
360 | dbg_msg("net_ban", aBuf);
361 | m_BanAddrPool.Remove(m_BanAddrPool.First());
362 | }
363 | while(m_BanRangePool.First() && m_BanRangePool.First()->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER && m_BanRangePool.First()->m_Info.m_Expires < Now)
364 | {
365 | str_format(aBuf, sizeof(aBuf), "ban %s expired", NetToString(&m_BanRangePool.First()->m_Data, aNetStr, sizeof(aNetStr)));
366 | dbg_msg("net_ban", aBuf);
367 | m_BanRangePool.Remove(m_BanRangePool.First());
368 | }
369 | }
370 |
371 | int CNetBan::BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason)
372 | {
373 | return Ban(&m_BanAddrPool, pAddr, Seconds, pReason);
374 | }
375 |
376 | int CNetBan::BanRange(const CNetRange *pRange, int Seconds, const char *pReason)
377 | {
378 | if(pRange->IsValid())
379 | return Ban(&m_BanRangePool, pRange, Seconds, pReason);
380 |
381 | dbg_msg("net_ban", "ban failed (invalid range)");
382 | return -1;
383 | }
384 |
385 | int CNetBan::UnbanByAddr(const NETADDR *pAddr)
386 | {
387 | return Unban(&m_BanAddrPool, pAddr);
388 | }
389 |
390 | int CNetBan::UnbanByRange(const CNetRange *pRange)
391 | {
392 | if(pRange->IsValid())
393 | return Unban(&m_BanRangePool, pRange);
394 |
395 | dbg_msg("net_ban", "ban failed (invalid range)");
396 | return -1;
397 | }
398 |
399 | int CNetBan::UnbanByIndex(int Index)
400 | {
401 | int Result;
402 | char aBuf[256];
403 | CBanAddr *pBan = m_BanAddrPool.Get(Index);
404 | if(pBan)
405 | {
406 | NetToString(&pBan->m_Data, aBuf, sizeof(aBuf));
407 | Result = m_BanAddrPool.Remove(pBan);
408 | }
409 | else
410 | {
411 | CBanRange *pBan = m_BanRangePool.Get(Index-m_BanAddrPool.Num());
412 | if(pBan)
413 | {
414 | NetToString(&pBan->m_Data, aBuf, sizeof(aBuf));
415 | Result = m_BanRangePool.Remove(pBan);
416 | }
417 | else
418 | {
419 | dbg_msg("net_ban", "unban failed (invalid index)");
420 | return -1;
421 | }
422 | }
423 |
424 | char aMsg[256];
425 | str_format(aMsg, sizeof(aMsg), "unbanned index %i (%s)", Index, aBuf);
426 | dbg_msg("net_ban", aMsg);
427 | return Result;
428 | }
429 |
430 | void CNetBan::UnbanAll()
431 | {
432 | m_BanAddrPool.Reset();
433 | m_BanRangePool.Reset();
434 | }
435 |
436 | bool CNetBan::IsBanned(const NETADDR *pAddr, char *pBuf, unsigned BufferSize) const
437 | {
438 | CNetHash aHash[17];
439 | int Length = CNetHash::MakeHashArray(pAddr, aHash);
440 |
441 | // check ban adresses
442 | CBanAddr *pBan = m_BanAddrPool.Find(pAddr, &aHash[Length]);
443 | if(pBan)
444 | {
445 | MakeBanInfo(pBan, pBuf, BufferSize, MSGTYPE_PLAYER);
446 | return true;
447 | }
448 |
449 | // check ban ranges
450 | for(int i = Length-1; i >= 0; --i)
451 | {
452 | for(CBanRange *pBan = m_BanRangePool.First(&aHash[i]); pBan; pBan = pBan->m_pHashNext)
453 | {
454 | if(NetMatch(&pBan->m_Data, pAddr, i, Length))
455 | {
456 | MakeBanInfo(pBan, pBuf, BufferSize, MSGTYPE_PLAYER);
457 | return true;
458 | }
459 | }
460 | }
461 |
462 | return false;
463 | }
464 |
--------------------------------------------------------------------------------
/clients/client-psutil.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # coding: utf-8
3 | # Update by : https://github.com/cppla/ServerStatus, Update date: 20250902
4 | # 依赖于psutil跨平台库
5 | # 版本:1.1.0, 支持Python版本:3.6+
6 | # 支持操作系统: Linux, Windows, OSX, Sun Solaris, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures
7 | # 说明: 默认情况下修改server和user就可以了。丢包率监测方向可以自定义,例如:CU = "www.facebook.com"。
8 |
9 | SERVER = "127.0.0.1"
10 | USER = "s01"
11 |
12 |
13 | PASSWORD = "USER_DEFAULT_PASSWORD"
14 | PORT = 35601
15 | CU = "cu.tz.cloudcpp.com"
16 | CT = "ct.tz.cloudcpp.com"
17 | CM = "cm.tz.cloudcpp.com"
18 | PROBEPORT = 80
19 | PROBE_PROTOCOL_PREFER = "ipv4" # ipv4, ipv6
20 | PING_PACKET_HISTORY_LEN = 100
21 | INTERVAL = 1
22 |
23 | import socket
24 | import time
25 | import timeit
26 | import os
27 | import sys
28 | import json
29 | import errno
30 | import psutil
31 | import threading
32 | import platform
33 | from queue import Queue
34 |
35 | def get_uptime():
36 | return int(time.time() - psutil.boot_time())
37 |
38 | def get_memory():
39 | Mem = psutil.virtual_memory()
40 | return int(Mem.total / 1024.0), int(Mem.used / 1024.0)
41 |
42 | def get_swap():
43 | Mem = psutil.swap_memory()
44 | return int(Mem.total/1024.0), int(Mem.used/1024.0)
45 |
46 | def get_hdd():
47 | if "darwin" in sys.platform:
48 | return int(psutil.disk_usage("/").total/1024.0/1024.0), int((psutil.disk_usage("/").total-psutil.disk_usage("/").free)/1024.0/1024.0)
49 | else:
50 | valid_fs = ["ext4", "ext3", "ext2", "reiserfs", "jfs", "btrfs", "fuseblk", "zfs", "simfs", "ntfs", "fat32",
51 | "exfat", "xfs"]
52 | disks = dict()
53 | size = 0
54 | used = 0
55 | for disk in psutil.disk_partitions():
56 | if not disk.device in disks and disk.fstype.lower() in valid_fs:
57 | disks[disk.device] = disk.mountpoint
58 | for disk in disks.values():
59 | usage = psutil.disk_usage(disk)
60 | size += usage.total
61 | used += usage.used
62 | return int(size/1024.0/1024.0), int(used/1024.0/1024.0)
63 |
64 | def get_cpu():
65 | return psutil.cpu_percent(interval=INTERVAL)
66 |
67 | def liuliang():
68 | NET_IN = 0
69 | NET_OUT = 0
70 | net = psutil.net_io_counters(pernic=True)
71 | for k, v in net.items():
72 | if 'lo' in k or 'tun' in k \
73 | or 'docker' in k or 'veth' in k \
74 | or 'br-' in k or 'vmbr' in k \
75 | or 'vnet' in k or 'kube' in k:
76 | continue
77 | else:
78 | NET_IN += v[1]
79 | NET_OUT += v[0]
80 | return NET_IN, NET_OUT
81 |
82 | def tupd():
83 | '''
84 | tcp, udp, process, thread count: for view ddcc attack , then send warning
85 | :return:
86 | '''
87 | try:
88 | if sys.platform.startswith("linux") is True:
89 | t = int(os.popen('ss -t|wc -l').read()[:-1])-1
90 | u = int(os.popen('ss -u|wc -l').read()[:-1])-1
91 | p = int(os.popen('ps -ef|wc -l').read()[:-1])-2
92 | d = int(os.popen('ps -eLf|wc -l').read()[:-1])-2
93 | elif sys.platform.startswith("darwin") is True:
94 | t = int(os.popen('lsof -nP -iTCP | wc -l').read()[:-1]) - 1
95 | u = int(os.popen('lsof -nP -iUDP | wc -l').read()[:-1]) - 1
96 | p = len(psutil.pids())
97 | d = 0
98 | for k in psutil.pids():
99 | try:
100 | d += psutil.Process(k).num_threads()
101 | except:
102 | pass
103 |
104 | elif sys.platform.startswith("win") is True:
105 | t = int(os.popen('netstat -an|find "TCP" /c').read()[:-1])-1
106 | u = int(os.popen('netstat -an|find "UDP" /c').read()[:-1])-1
107 | p = len(psutil.pids())
108 | # if you find cpu is high, please set d=0
109 | d = sum([psutil.Process(k).num_threads() for k in psutil.pids()])
110 | else:
111 | t,u,p,d = 0,0,0,0
112 | return t,u,p,d
113 | except:
114 | return 0,0,0,0
115 |
116 | def get_network(ip_version):
117 | if(ip_version == 4):
118 | HOST = "ipv4.google.com"
119 | elif(ip_version == 6):
120 | HOST = "ipv6.google.com"
121 | try:
122 | socket.create_connection((HOST, 80), 2).close()
123 | return True
124 | except:
125 | return False
126 |
127 | lostRate = {
128 | '10010': 0.0,
129 | '189': 0.0,
130 | '10086': 0.0
131 | }
132 | pingTime = {
133 | '10010': 0,
134 | '189': 0,
135 | '10086': 0
136 | }
137 | netSpeed = {
138 | 'netrx': 0.0,
139 | 'nettx': 0.0,
140 | 'clock': 0.0,
141 | 'diff': 0.0,
142 | 'avgrx': 0,
143 | 'avgtx': 0
144 | }
145 | diskIO = {
146 | 'read': 0,
147 | 'write': 0
148 | }
149 | monitorServer = {}
150 |
151 | def _ping_thread(host, mark, port):
152 | lostPacket = 0
153 | packet_queue = Queue(maxsize=PING_PACKET_HISTORY_LEN)
154 |
155 | while True:
156 | # flush dns, every time.
157 | IP = host
158 | if host.count(':') < 1: # if not plain ipv6 address, means ipv4 address or hostname
159 | try:
160 | if PROBE_PROTOCOL_PREFER == 'ipv4':
161 | IP = socket.getaddrinfo(host, None, socket.AF_INET)[0][4][0]
162 | else:
163 | IP = socket.getaddrinfo(host, None, socket.AF_INET6)[0][4][0]
164 | except Exception:
165 | pass
166 |
167 | if packet_queue.full():
168 | if packet_queue.get() == 0:
169 | lostPacket -= 1
170 | try:
171 | b = timeit.default_timer()
172 | socket.create_connection((IP, port), timeout=1).close()
173 | pingTime[mark] = int((timeit.default_timer() - b) * 1000)
174 | packet_queue.put(1)
175 | except socket.error as error:
176 | if error.errno == errno.ECONNREFUSED:
177 | pingTime[mark] = int((timeit.default_timer() - b) * 1000)
178 | packet_queue.put(1)
179 | #elif error.errno == errno.ETIMEDOUT:
180 | else:
181 | lostPacket += 1
182 | packet_queue.put(0)
183 |
184 | if packet_queue.qsize() > 30:
185 | lostRate[mark] = float(lostPacket) / packet_queue.qsize()
186 |
187 | time.sleep(INTERVAL)
188 |
189 | def _net_speed():
190 | while True:
191 | avgrx = 0
192 | avgtx = 0
193 | for name, stats in psutil.net_io_counters(pernic=True).items():
194 | if "lo" in name or "tun" in name \
195 | or "docker" in name or "veth" in name \
196 | or "br-" in name or "vmbr" in name \
197 | or "vnet" in name or "kube" in name:
198 | continue
199 | avgrx += stats.bytes_recv
200 | avgtx += stats.bytes_sent
201 | now_clock = time.time()
202 | netSpeed["diff"] = now_clock - netSpeed["clock"]
203 | netSpeed["clock"] = now_clock
204 | netSpeed["netrx"] = int((avgrx - netSpeed["avgrx"]) / netSpeed["diff"])
205 | netSpeed["nettx"] = int((avgtx - netSpeed["avgtx"]) / netSpeed["diff"])
206 | netSpeed["avgrx"] = avgrx
207 | netSpeed["avgtx"] = avgtx
208 | time.sleep(INTERVAL)
209 |
210 | def _disk_io():
211 | """
212 | the code is by: https://github.com/giampaolo/psutil/blob/master/scripts/iotop.py
213 | good luck for opensource! modify: cpp.la
214 | Calculate IO usage by comparing IO statics before and
215 | after the interval.
216 | Return a tuple including all currently running processes
217 | sorted by IO activity and total disks I/O activity.
218 | 磁盘IO:因为IOPS原因,SSD和HDD、包括RAID卡,ZFS等。IO对性能的影响还需要结合自身服务器情况来判断。
219 | 比如我这里是机械硬盘,大量做随机小文件读写,那么很低的读写也就能造成硬盘长时间的等待。
220 | 如果这里做连续性IO,那么普通机械硬盘写入到100Mb/s,那么也能造成硬盘长时间的等待。
221 | 磁盘读写有误差:4k,8k ,https://stackoverflow.com/questions/34413926/psutil-vs-dd-monitoring-disk-i-o
222 | macos/win,暂不处理。
223 | """
224 | if "darwin" in sys.platform or "win" in sys.platform:
225 | diskIO["read"] = 0
226 | diskIO["write"] = 0
227 | else:
228 | while True:
229 | # first get a list of all processes and disk io counters
230 | procs = [p for p in psutil.process_iter()]
231 | for p in procs[:]:
232 | try:
233 | p._before = p.io_counters()
234 | except psutil.Error:
235 | procs.remove(p)
236 | continue
237 | disks_before = psutil.disk_io_counters()
238 |
239 | # sleep some time, only when INTERVAL==1 , io read/write per_sec.
240 | # when INTERVAL > 1, io read/write per_INTERVAL
241 | time.sleep(INTERVAL)
242 |
243 | # then retrieve the same info again
244 | for p in procs[:]:
245 | with p.oneshot():
246 | try:
247 | p._after = p.io_counters()
248 | p._cmdline = ' '.join(p.cmdline())
249 | if not p._cmdline:
250 | p._cmdline = p.name()
251 | p._username = p.username()
252 | except (psutil.NoSuchProcess, psutil.ZombieProcess):
253 | procs.remove(p)
254 | disks_after = psutil.disk_io_counters()
255 |
256 | # finally calculate results by comparing data before and
257 | # after the interval
258 | for p in procs:
259 | p._read_per_sec = p._after.read_bytes - p._before.read_bytes
260 | p._write_per_sec = p._after.write_bytes - p._before.write_bytes
261 | p._total = p._read_per_sec + p._write_per_sec
262 |
263 | diskIO["read"] = disks_after.read_bytes - disks_before.read_bytes
264 | diskIO["write"] = disks_after.write_bytes - disks_before.write_bytes
265 |
266 | def get_realtime_data():
267 | '''
268 | real time get system data
269 | :return:
270 | '''
271 | t1 = threading.Thread(
272 | target=_ping_thread,
273 | kwargs={
274 | 'host': CU,
275 | 'mark': '10010',
276 | 'port': PROBEPORT
277 | }
278 | )
279 | t2 = threading.Thread(
280 | target=_ping_thread,
281 | kwargs={
282 | 'host': CT,
283 | 'mark': '189',
284 | 'port': PROBEPORT
285 | }
286 | )
287 | t3 = threading.Thread(
288 | target=_ping_thread,
289 | kwargs={
290 | 'host': CM,
291 | 'mark': '10086',
292 | 'port': PROBEPORT
293 | }
294 | )
295 | t4 = threading.Thread(
296 | target=_net_speed,
297 | )
298 | t5 = threading.Thread(
299 | target=_disk_io,
300 | )
301 | for ti in [t1, t2, t3, t4, t5]:
302 | ti.daemon = True
303 | ti.start()
304 |
305 | def _monitor_thread(name, host, interval, type):
306 | # 参考 _ping_thread 风格:每轮解析一次目标,按协议族偏好解析 IP,测 TCP 建连耗时
307 | while True:
308 | if name not in monitorServer:
309 | break
310 | try:
311 | # 1) 解析目标 host 与端口
312 | if type == 'http':
313 | addr = str(host).replace('http://','')
314 | addr = addr.split('/',1)[0]
315 | port = 80
316 | if ':' in addr and not addr.startswith('['):
317 | a, p = addr.rsplit(':',1)
318 | if p.isdigit():
319 | addr, port = a, int(p)
320 | elif type == 'https':
321 | addr = str(host).replace('https://','')
322 | addr = addr.split('/',1)[0]
323 | port = 443
324 | if ':' in addr and not addr.startswith('['):
325 | a, p = addr.rsplit(':',1)
326 | if p.isdigit():
327 | addr, port = a, int(p)
328 | elif type == 'tcp':
329 | addr = str(host)
330 | if addr.startswith('[') and ']' in addr:
331 | # [v6]:port
332 | a = addr[1:addr.index(']')]
333 | rest = addr[addr.index(']')+1:]
334 | if rest.startswith(':') and rest[1:].isdigit():
335 | addr, port = a, int(rest[1:])
336 | else:
337 | raise Exception('bad tcp target')
338 | else:
339 | a, p = addr.rsplit(':',1)
340 | addr, port = a, int(p)
341 | else:
342 | time.sleep(interval)
343 | continue
344 |
345 | # 2) 解析 IP(按偏好族),与 _ping_thread 保持一致的判定
346 | IP = addr
347 | if addr.count(':') < 1: # 非纯 IPv6,可能是 IPv4 或域名
348 | try:
349 | if PROBE_PROTOCOL_PREFER == 'ipv4':
350 | IP = socket.getaddrinfo(addr, None, socket.AF_INET)[0][4][0]
351 | else:
352 | IP = socket.getaddrinfo(addr, None, socket.AF_INET6)[0][4][0]
353 | except Exception:
354 | pass
355 |
356 | # 3) 测 TCP 建连耗时(timeout=1s);ECONNREFUSED 也记为耗时
357 | try:
358 | b = timeit.default_timer()
359 | socket.create_connection((IP, port), timeout=1).close()
360 | monitorServer[name]['latency'] = int((timeit.default_timer() - b) * 1000)
361 | except socket.error as error:
362 | if getattr(error, 'errno', None) == errno.ECONNREFUSED:
363 | monitorServer[name]['latency'] = int((timeit.default_timer() - b) * 1000)
364 | else:
365 | monitorServer[name]['latency'] = 0
366 | except Exception:
367 | monitorServer[name]['latency'] = 0
368 | time.sleep(interval)
369 |
370 |
371 | def byte_str(object):
372 | '''
373 | bytes to str, str to bytes
374 | :param object:
375 | :return:
376 | '''
377 | if isinstance(object, str):
378 | return object.encode(encoding="utf-8")
379 | elif isinstance(object, bytes):
380 | return bytes.decode(object)
381 | else:
382 | print(type(object))
383 |
384 | if __name__ == '__main__':
385 | for argc in sys.argv:
386 | if 'SERVER' in argc:
387 | SERVER = argc.split('SERVER=')[-1]
388 | elif 'PORT' in argc:
389 | PORT = int(argc.split('PORT=')[-1])
390 | elif 'USER' in argc:
391 | USER = argc.split('USER=')[-1]
392 | elif 'PASSWORD' in argc:
393 | PASSWORD = argc.split('PASSWORD=')[-1]
394 | elif 'INTERVAL' in argc:
395 | INTERVAL = int(argc.split('INTERVAL=')[-1])
396 | socket.setdefaulttimeout(30)
397 | get_realtime_data()
398 | while 1:
399 | try:
400 | print("Connecting...")
401 | s = socket.create_connection((SERVER, PORT))
402 | data = byte_str(s.recv(1024))
403 | if data.find("Authentication required") > -1:
404 | s.send(byte_str(USER + ':' + PASSWORD + '\n'))
405 | data = byte_str(s.recv(1024))
406 | if data.find("Authentication successful") < 0:
407 | print(data)
408 | raise socket.error
409 | else:
410 | print(data)
411 | raise socket.error
412 |
413 | print(data)
414 | if data.find("You are connecting via") < 0:
415 | data = byte_str(s.recv(1024))
416 | print(data)
417 | for i in data.split('\n'):
418 | if "monitor" in i and "type" in i and "{" in i and "}" in i:
419 | jdata = json.loads(i[i.find("{"):i.find("}")+1])
420 | monitorServer[jdata.get("name")] = {
421 | "type": jdata.get("type"),
422 | "host": jdata.get("host"),
423 | "latency": 0
424 | }
425 | t = threading.Thread(
426 | target=_monitor_thread,
427 | kwargs={
428 | 'name': jdata.get("name"),
429 | 'host': jdata.get("host"),
430 | 'interval': jdata.get("interval"),
431 | 'type': jdata.get("type")
432 | }
433 | )
434 | t.daemon = True
435 | t.start()
436 |
437 | timer = 0
438 | check_ip = 0
439 | if data.find("IPv4") > -1:
440 | check_ip = 6
441 | elif data.find("IPv6") > -1:
442 | check_ip = 4
443 | else:
444 | print(data)
445 | raise socket.error
446 |
447 | while 1:
448 | CPU = get_cpu()
449 | NET_IN, NET_OUT = liuliang()
450 | Uptime = get_uptime()
451 | Load_1, Load_5, Load_15 = os.getloadavg() if 'linux' in sys.platform or 'darwin' in sys.platform else (0.0, 0.0, 0.0)
452 | MemoryTotal, MemoryUsed = get_memory()
453 | SwapTotal, SwapUsed = get_swap()
454 | HDDTotal, HDDUsed = get_hdd()
455 | array = {}
456 | if not timer:
457 | array['online' + str(check_ip)] = get_network(check_ip)
458 | timer = 10
459 | else:
460 | timer -= 1*INTERVAL
461 |
462 | array['uptime'] = Uptime
463 | array['load_1'] = Load_1
464 | array['load_5'] = Load_5
465 | array['load_15'] = Load_15
466 | array['memory_total'] = MemoryTotal
467 | array['memory_used'] = MemoryUsed
468 | array['swap_total'] = SwapTotal
469 | array['swap_used'] = SwapUsed
470 | array['hdd_total'] = HDDTotal
471 | array['hdd_used'] = HDDUsed
472 | array['cpu'] = CPU
473 | array['network_rx'] = netSpeed.get("netrx")
474 | array['network_tx'] = netSpeed.get("nettx")
475 | array['network_in'] = NET_IN
476 | array['network_out'] = NET_OUT
477 | array['ping_10010'] = lostRate.get('10010') * 100
478 | array['ping_189'] = lostRate.get('189') * 100
479 | array['ping_10086'] = lostRate.get('10086') * 100
480 | array['time_10010'] = pingTime.get('10010')
481 | array['time_189'] = pingTime.get('189')
482 | array['time_10086'] = pingTime.get('10086')
483 | array['tcp'], array['udp'], array['process'], array['thread'] = tupd()
484 | array['io_read'] = diskIO.get("read")
485 | array['io_write'] = diskIO.get("write")
486 | # report OS (normalized)
487 | try:
488 | sysname = platform.system().lower()
489 | if sysname.startswith('windows'):
490 | os_name = 'windows'
491 | elif sysname.startswith('darwin') or 'mac' in sysname:
492 | os_name = 'darwin'
493 | elif 'bsd' in sysname:
494 | os_name = 'bsd'
495 | elif sysname.startswith('linux'):
496 | # try distro if available
497 | os_name = 'linux'
498 | try:
499 | import distro # optional
500 | os_name = distro.id() or 'linux'
501 | except Exception:
502 | pass
503 | else:
504 | os_name = sysname or 'unknown'
505 | except Exception:
506 | os_name = 'unknown'
507 | array['os'] = os_name
508 | items = []
509 | for _n, st in monitorServer.items():
510 | key = str(_n)
511 | try:
512 | ms = int(st.get('latency') or 0)
513 | except Exception:
514 | ms = 0
515 | items.append((key, max(0, ms)))
516 | # 稳定顺序:按 key 排序
517 | items.sort(key=lambda x: x[0])
518 | array['custom'] = ';'.join(f"{k}={v}" for k,v in items)
519 | s.send(byte_str("update " + json.dumps(array) + "\n"))
520 | except KeyboardInterrupt:
521 | raise
522 | except socket.error:
523 | monitorServer.clear()
524 | print("Disconnected...")
525 | if 's' in locals().keys():
526 | del s
527 | time.sleep(3)
528 | except Exception as e:
529 | monitorServer.clear()
530 | print("Caught Exception:", e)
531 | if 's' in locals().keys():
532 | del s
533 | time.sleep(3)
534 |
--------------------------------------------------------------------------------
/clients/client-linux.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # coding: utf-8
3 | # Update by : https://github.com/cppla/ServerStatus, Update date: 20250902
4 | # 版本:1.1.0, 支持Python版本:3.6+
5 | # 支持操作系统: Linux, OSX, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures
6 | # 说明: 默认情况下修改server和user就可以了。丢包率监测方向可以自定义,例如:CU = "www.facebook.com"。
7 |
8 | SERVER = "127.0.0.1"
9 | USER = "s01"
10 |
11 |
12 | PASSWORD = "USER_DEFAULT_PASSWORD"
13 | PORT = 35601
14 | CU = "cu.tz.cloudcpp.com"
15 | CT = "ct.tz.cloudcpp.com"
16 | CM = "cm.tz.cloudcpp.com"
17 | PROBEPORT = 80
18 | PROBE_PROTOCOL_PREFER = "ipv4" # ipv4, ipv6
19 | PING_PACKET_HISTORY_LEN = 100
20 | INTERVAL = 1
21 |
22 | import socket
23 | import time
24 | import timeit
25 | import re
26 | import os
27 | import sys
28 | import json
29 | import errno
30 | import subprocess
31 | import threading
32 | import platform
33 | from queue import Queue
34 |
35 | def get_uptime():
36 | with open('/proc/uptime', 'r') as f:
37 | uptime = f.readline().split('.', 2)
38 | return int(uptime[0])
39 |
40 | def get_memory():
41 | re_parser = re.compile(r'^(?P\S*):\s*(?P\d*)\s*kB')
42 | result = dict()
43 | for line in open('/proc/meminfo'):
44 | match = re_parser.match(line)
45 | if not match:
46 | continue
47 | key, value = match.groups(['key', 'value'])
48 | result[key] = int(value)
49 | MemTotal = float(result['MemTotal'])
50 | MemUsed = MemTotal-float(result['MemFree'])-float(result['Buffers'])-float(result['Cached'])-float(result['SReclaimable'])
51 | SwapTotal = float(result['SwapTotal'])
52 | SwapFree = float(result['SwapFree'])
53 | return int(MemTotal), int(MemUsed), int(SwapTotal), int(SwapFree)
54 |
55 | def get_hdd():
56 | p = subprocess.check_output(['df', '-Tlm', '--total', '-t', 'ext4', '-t', 'ext3', '-t', 'ext2', '-t', 'reiserfs', '-t', 'jfs', '-t', 'ntfs', '-t', 'fat32', '-t', 'btrfs', '-t', 'fuseblk', '-t', 'zfs', '-t', 'simfs', '-t', 'xfs']).decode("Utf-8")
57 | total = p.splitlines()[-1]
58 | used = total.split()[3]
59 | size = total.split()[2]
60 | return int(size), int(used)
61 |
62 | def get_time():
63 | with open("/proc/stat", "r") as f:
64 | time_list = f.readline().split(' ')[2:6]
65 | for i in range(len(time_list)) :
66 | time_list[i] = int(time_list[i])
67 | return time_list
68 |
69 | def delta_time():
70 | x = get_time()
71 | time.sleep(INTERVAL)
72 | y = get_time()
73 | for i in range(len(x)):
74 | y[i]-=x[i]
75 | return y
76 |
77 | def get_cpu():
78 | t = delta_time()
79 | st = sum(t)
80 | if st == 0:
81 | st = 1
82 | result = 100-(t[len(t)-1]*100.00/st)
83 | return round(result, 1)
84 |
85 | def liuliang():
86 | NET_IN = 0
87 | NET_OUT = 0
88 | with open('/proc/net/dev') as f:
89 | for line in f.readlines():
90 | netinfo = re.findall(r'([^\s]+):[\s]{0,}(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)', line)
91 | if netinfo:
92 | if netinfo[0][0] == 'lo' or 'tun' in netinfo[0][0] \
93 | or 'docker' in netinfo[0][0] or 'veth' in netinfo[0][0] \
94 | or 'br-' in netinfo[0][0] or 'vmbr' in netinfo[0][0] \
95 | or 'vnet' in netinfo[0][0] or 'kube' in netinfo[0][0] \
96 | or netinfo[0][1]=='0' or netinfo[0][9]=='0':
97 | continue
98 | else:
99 | NET_IN += int(netinfo[0][1])
100 | NET_OUT += int(netinfo[0][9])
101 | return NET_IN, NET_OUT
102 |
103 | def tupd():
104 | '''
105 | tcp, udp, process, thread count: for view ddcc attack , then send warning
106 | :return:
107 | '''
108 | s = subprocess.check_output("ss -t|wc -l", shell=True)
109 | t = int(s[:-1])-1
110 | s = subprocess.check_output("ss -u|wc -l", shell=True)
111 | u = int(s[:-1])-1
112 | s = subprocess.check_output("ps -ef|wc -l", shell=True)
113 | p = int(s[:-1])-2
114 | s = subprocess.check_output("ps -eLf|wc -l", shell=True)
115 | d = int(s[:-1])-2
116 | return t,u,p,d
117 |
118 | def get_network(ip_version):
119 | if(ip_version == 4):
120 | HOST = "ipv4.google.com"
121 | elif(ip_version == 6):
122 | HOST = "ipv6.google.com"
123 | try:
124 | socket.create_connection((HOST, 80), 2).close()
125 | return True
126 | except:
127 | return False
128 |
129 | lostRate = {
130 | '10010': 0.0,
131 | '189': 0.0,
132 | '10086': 0.0
133 | }
134 | pingTime = {
135 | '10010': 0,
136 | '189': 0,
137 | '10086': 0
138 | }
139 | netSpeed = {
140 | 'netrx': 0.0,
141 | 'nettx': 0.0,
142 | 'clock': 0.0,
143 | 'diff': 0.0,
144 | 'avgrx': 0,
145 | 'avgtx': 0
146 | }
147 | diskIO = {
148 | 'read': 0,
149 | 'write': 0
150 | }
151 | monitorServer = {}
152 |
153 | def _ping_thread(host, mark, port):
154 | lostPacket = 0
155 | packet_queue = Queue(maxsize=PING_PACKET_HISTORY_LEN)
156 |
157 | while True:
158 | # flush dns , every time.
159 | IP = host
160 | if host.count(':') < 1: # if not plain ipv6 address, means ipv4 address or hostname
161 | try:
162 | if PROBE_PROTOCOL_PREFER == 'ipv4':
163 | IP = socket.getaddrinfo(host, None, socket.AF_INET)[0][4][0]
164 | else:
165 | IP = socket.getaddrinfo(host, None, socket.AF_INET6)[0][4][0]
166 | except Exception:
167 | pass
168 |
169 | if packet_queue.full():
170 | if packet_queue.get() == 0:
171 | lostPacket -= 1
172 | try:
173 | b = timeit.default_timer()
174 | socket.create_connection((IP, port), timeout=1).close()
175 | pingTime[mark] = int((timeit.default_timer() - b) * 1000)
176 | packet_queue.put(1)
177 | except socket.error as error:
178 | if error.errno == errno.ECONNREFUSED:
179 | pingTime[mark] = int((timeit.default_timer() - b) * 1000)
180 | packet_queue.put(1)
181 | #elif error.errno == errno.ETIMEDOUT:
182 | else:
183 | lostPacket += 1
184 | packet_queue.put(0)
185 |
186 | if packet_queue.qsize() > 30:
187 | lostRate[mark] = float(lostPacket) / packet_queue.qsize()
188 |
189 | time.sleep(INTERVAL)
190 |
191 | def _net_speed():
192 | while True:
193 | with open("/proc/net/dev", "r") as f:
194 | net_dev = f.readlines()
195 | avgrx = 0
196 | avgtx = 0
197 | for dev in net_dev[2:]:
198 | dev = dev.split(':')
199 | if "lo" in dev[0] or "tun" in dev[0] \
200 | or "docker" in dev[0] or "veth" in dev[0] \
201 | or "br-" in dev[0] or "vmbr" in dev[0] \
202 | or "vnet" in dev[0] or "kube" in dev[0]:
203 | continue
204 | dev = dev[1].split()
205 | avgrx += int(dev[0])
206 | avgtx += int(dev[8])
207 | now_clock = time.time()
208 | netSpeed["diff"] = now_clock - netSpeed["clock"]
209 | netSpeed["clock"] = now_clock
210 | netSpeed["netrx"] = int((avgrx - netSpeed["avgrx"]) / netSpeed["diff"])
211 | netSpeed["nettx"] = int((avgtx - netSpeed["avgtx"]) / netSpeed["diff"])
212 | netSpeed["avgrx"] = avgrx
213 | netSpeed["avgtx"] = avgtx
214 | time.sleep(INTERVAL)
215 |
216 | def _disk_io():
217 | '''
218 | good luck for opensource! by: cpp.la
219 | 磁盘IO:因为IOPS原因,SSD和HDD、包括RAID卡,ZFS等阵列技术。IO对性能的影响还需要结合自身服务器情况来判断。
220 | 比如我这里是机械硬盘,大量做随机小文件读写,那么很低的读写也就能造成硬盘长时间的等待。
221 | 如果这里做连续性IO,那么普通机械硬盘写入到100Mb/s,那么也能造成硬盘长时间的等待。
222 | 磁盘读写有误差:4k,8k ,https://stackoverflow.com/questions/34413926/psutil-vs-dd-monitoring-disk-i-o
223 | :return:
224 | '''
225 | while True:
226 | # pre pid snapshot
227 | snapshot_first = {}
228 | # next pid snapshot
229 | snapshot_second = {}
230 | # read count snapshot
231 | snapshot_read = 0
232 | # write count snapshot
233 | snapshot_write = 0
234 | # process snapshot
235 | pid_snapshot = [str(i) for i in os.listdir("/proc") if i.isdigit() is True]
236 | for pid in pid_snapshot:
237 | try:
238 | with open("/proc/{}/io".format(pid)) as f:
239 | pid_io = {}
240 | for line in f.readlines():
241 | if "read_bytes" in line:
242 | pid_io["read"] = int(line.split("read_bytes:")[-1].strip())
243 | elif "write_bytes" in line and "cancelled_write_bytes" not in line:
244 | pid_io["write"] = int(line.split("write_bytes:")[-1].strip())
245 | pid_io["name"] = open("/proc/{}/comm".format(pid), "r").read().strip()
246 | snapshot_first[pid] = pid_io
247 | except:
248 | if pid in snapshot_first:
249 | snapshot_first.pop(pid)
250 |
251 | time.sleep(INTERVAL)
252 |
253 | for pid in pid_snapshot:
254 | try:
255 | with open("/proc/{}/io".format(pid)) as f:
256 | pid_io = {}
257 | for line in f.readlines():
258 | if "read_bytes" in line:
259 | pid_io["read"] = int(line.split("read_bytes:")[-1].strip())
260 | elif "write_bytes" in line and "cancelled_write_bytes" not in line:
261 | pid_io["write"] = int(line.split("write_bytes:")[-1].strip())
262 | pid_io["name"] = open("/proc/{}/comm".format(pid), "r").read().strip()
263 | snapshot_second[pid] = pid_io
264 | except:
265 | if pid in snapshot_first:
266 | snapshot_first.pop(pid)
267 | if pid in snapshot_second:
268 | snapshot_second.pop(pid)
269 |
270 | for k, v in snapshot_first.items():
271 | if snapshot_first[k]["name"] == snapshot_second[k]["name"] and snapshot_first[k]["name"] != "bash":
272 | snapshot_read += (snapshot_second[k]["read"] - snapshot_first[k]["read"])
273 | snapshot_write += (snapshot_second[k]["write"] - snapshot_first[k]["write"])
274 | diskIO["read"] = snapshot_read
275 | diskIO["write"] = snapshot_write
276 |
277 | def get_realtime_data():
278 | '''
279 | real time get system data
280 | :return:
281 | '''
282 | t1 = threading.Thread(
283 | target=_ping_thread,
284 | kwargs={
285 | 'host': CU,
286 | 'mark': '10010',
287 | 'port': PROBEPORT
288 | }
289 | )
290 | t2 = threading.Thread(
291 | target=_ping_thread,
292 | kwargs={
293 | 'host': CT,
294 | 'mark': '189',
295 | 'port': PROBEPORT
296 | }
297 | )
298 | t3 = threading.Thread(
299 | target=_ping_thread,
300 | kwargs={
301 | 'host': CM,
302 | 'mark': '10086',
303 | 'port': PROBEPORT
304 | }
305 | )
306 | t4 = threading.Thread(
307 | target=_net_speed,
308 | )
309 | t5 = threading.Thread(
310 | target=_disk_io,
311 | )
312 | for ti in [t1, t2, t3, t4, t5]:
313 | ti.daemon = True
314 | ti.start()
315 |
316 |
317 | def _monitor_thread(name, host, interval, type):
318 | while True:
319 | if name not in monitorServer.keys():
320 | break
321 | try:
322 | # 1) 解析目标 host 与端口
323 | if type == 'http':
324 | addr = str(host).replace('http://','')
325 | addr = addr.split('/',1)[0]
326 | port = 80
327 | if ':' in addr and not addr.startswith('['):
328 | a, p = addr.rsplit(':',1)
329 | if p.isdigit():
330 | addr, port = a, int(p)
331 | elif type == 'https':
332 | addr = str(host).replace('https://','')
333 | addr = addr.split('/',1)[0]
334 | port = 443
335 | if ':' in addr and not addr.startswith('['):
336 | a, p = addr.rsplit(':',1)
337 | if p.isdigit():
338 | addr, port = a, int(p)
339 | elif type == 'tcp':
340 | addr = str(host)
341 | if addr.startswith('[') and ']' in addr:
342 | a = addr[1:addr.index(']')]
343 | rest = addr[addr.index(']')+1:]
344 | if rest.startswith(':') and rest[1:].isdigit():
345 | addr, port = a, int(rest[1:])
346 | else:
347 | raise Exception('bad tcp target')
348 | else:
349 | a, p = addr.rsplit(':',1)
350 | addr, port = a, int(p)
351 | else:
352 | time.sleep(interval)
353 | continue
354 |
355 | # 2) 解析 IP(按偏好族)
356 | IP = addr
357 | if addr.count(':') < 1: # 非纯 IPv6
358 | try:
359 | if PROBE_PROTOCOL_PREFER == 'ipv4':
360 | IP = socket.getaddrinfo(addr, None, socket.AF_INET)[0][4][0]
361 | else:
362 | IP = socket.getaddrinfo(addr, None, socket.AF_INET6)[0][4][0]
363 | except Exception:
364 | pass
365 |
366 | # 3) 建连耗时(timeout=1s),ECONNREFUSED 也计入
367 | try:
368 | b = timeit.default_timer()
369 | socket.create_connection((IP, port), timeout=1).close()
370 | monitorServer[name]["latency"] = int((timeit.default_timer() - b) * 1000)
371 | except socket.error as error:
372 | if getattr(error, 'errno', None) == errno.ECONNREFUSED:
373 | monitorServer[name]["latency"] = int((timeit.default_timer() - b) * 1000)
374 | else:
375 | monitorServer[name]["latency"] = 0
376 | except Exception:
377 | monitorServer[name]["latency"] = 0
378 | time.sleep(interval)
379 |
380 | def byte_str(object):
381 | '''
382 | bytes to str, str to bytes
383 | :param object:
384 | :return:
385 | '''
386 | if isinstance(object, str):
387 | return object.encode(encoding="utf-8")
388 | elif isinstance(object, bytes):
389 | return bytes.decode(object)
390 | else:
391 | print(type(object))
392 |
393 | if __name__ == '__main__':
394 | for argc in sys.argv:
395 | if 'SERVER' in argc:
396 | SERVER = argc.split('SERVER=')[-1]
397 | elif 'PORT' in argc:
398 | PORT = int(argc.split('PORT=')[-1])
399 | elif 'USER' in argc:
400 | USER = argc.split('USER=')[-1]
401 | elif 'PASSWORD' in argc:
402 | PASSWORD = argc.split('PASSWORD=')[-1]
403 | elif 'INTERVAL' in argc:
404 | INTERVAL = int(argc.split('INTERVAL=')[-1])
405 | socket.setdefaulttimeout(30)
406 | get_realtime_data()
407 | while True:
408 | try:
409 | print("Connecting...")
410 | s = socket.create_connection((SERVER, PORT))
411 | data = byte_str(s.recv(1024))
412 | if data.find("Authentication required") > -1:
413 | s.send(byte_str(USER + ':' + PASSWORD + '\n'))
414 | data = byte_str(s.recv(1024))
415 | if data.find("Authentication successful") < 0:
416 | print(data)
417 | raise socket.error
418 | else:
419 | print(data)
420 | raise socket.error
421 |
422 | print(data)
423 | if data.find("You are connecting via") < 0:
424 | data = byte_str(s.recv(1024))
425 | print(data)
426 | monitorServer.clear()
427 | for i in data.split('\n'):
428 | if "monitor" in i and "type" in i and "{" in i and "}" in i:
429 | jdata = json.loads(i[i.find("{"):i.find("}")+1])
430 | monitorServer[jdata.get("name")] = {
431 | "type": jdata.get("type"),
432 | "host": jdata.get("host"),
433 | "latency": 0
434 | }
435 | t = threading.Thread(
436 | target=_monitor_thread,
437 | kwargs={
438 | 'name': jdata.get("name"),
439 | 'host': jdata.get("host"),
440 | 'interval': jdata.get("interval"),
441 | 'type': jdata.get("type")
442 | }
443 | )
444 | t.daemon = True
445 | t.start()
446 |
447 | timer = 0
448 | check_ip = 0
449 | if data.find("IPv4") > -1:
450 | check_ip = 6
451 | elif data.find("IPv6") > -1:
452 | check_ip = 4
453 | else:
454 | print(data)
455 | raise socket.error
456 |
457 | while True:
458 | CPU = get_cpu()
459 | NET_IN, NET_OUT = liuliang()
460 | Uptime = get_uptime()
461 | Load_1, Load_5, Load_15 = os.getloadavg()
462 | MemoryTotal, MemoryUsed, SwapTotal, SwapFree = get_memory()
463 | HDDTotal, HDDUsed = get_hdd()
464 | array = {}
465 | if not timer:
466 | array['online' + str(check_ip)] = get_network(check_ip)
467 | timer = 10
468 | else:
469 | timer -= 1*INTERVAL
470 |
471 | array['uptime'] = Uptime
472 | array['load_1'] = Load_1
473 | array['load_5'] = Load_5
474 | array['load_15'] = Load_15
475 | array['memory_total'] = MemoryTotal
476 | array['memory_used'] = MemoryUsed
477 | array['swap_total'] = SwapTotal
478 | array['swap_used'] = SwapTotal - SwapFree
479 | array['hdd_total'] = HDDTotal
480 | array['hdd_used'] = HDDUsed
481 | array['cpu'] = CPU
482 | array['network_rx'] = netSpeed.get("netrx")
483 | array['network_tx'] = netSpeed.get("nettx")
484 | array['network_in'] = NET_IN
485 | array['network_out'] = NET_OUT
486 | array['ping_10010'] = lostRate.get('10010') * 100
487 | array['ping_189'] = lostRate.get('189') * 100
488 | array['ping_10086'] = lostRate.get('10086') * 100
489 | array['time_10010'] = pingTime.get('10010')
490 | array['time_189'] = pingTime.get('189')
491 | array['time_10086'] = pingTime.get('10086')
492 | array['tcp'], array['udp'], array['process'], array['thread'] = tupd()
493 | array['io_read'] = diskIO.get("read")
494 | array['io_write'] = diskIO.get("write")
495 | # report OS (normalized)
496 | try:
497 | sysname = platform.system().lower()
498 | if sysname.startswith('linux'):
499 | os_name = 'linux'
500 | # try distro from os-release
501 | try:
502 | with open('/etc/os-release') as f:
503 | for line in f:
504 | if line.startswith('ID='):
505 | val = line.strip().split('=',1)[1].strip().strip('"')
506 | if val: os_name = val
507 | break
508 | except Exception:
509 | pass
510 | elif sysname.startswith('darwin'):
511 | os_name = 'darwin'
512 | elif sysname.startswith('freebsd'):
513 | os_name = 'freebsd'
514 | elif sysname.startswith('openbsd'):
515 | os_name = 'openbsd'
516 | elif sysname.startswith('netbsd'):
517 | os_name = 'netbsd'
518 | else:
519 | os_name = sysname or 'unknown'
520 | except Exception:
521 | os_name = 'unknown'
522 | array['os'] = os_name
523 | items = []
524 | for _n, st in monitorServer.items():
525 | key = str(_n)
526 | try:
527 | ms = int(st.get('latency') or 0)
528 | except Exception:
529 | ms = 0
530 | items.append((key, max(0, ms)))
531 | # 稳定顺序:按 key 排序
532 | items.sort(key=lambda x: x[0])
533 | array['custom'] = ';'.join(f"{k}={v}" for k,v in items)
534 | s.send(byte_str("update " + json.dumps(array) + "\n"))
535 | except KeyboardInterrupt:
536 | raise
537 | except socket.error:
538 | monitorServer.clear()
539 | print("Disconnected...")
540 | if 's' in locals().keys():
541 | del s
542 | time.sleep(3)
543 | except Exception as e:
544 | monitorServer.clear()
545 | print("Caught Exception:", e)
546 | if 's' in locals().keys():
547 | del s
548 | time.sleep(3)
549 |
--------------------------------------------------------------------------------
/web/css/app.css:
--------------------------------------------------------------------------------
1 | :root{--bg:#0f1115;--bg-alt:#171a21;--border:#262a33;--text:#e2e8f0;--text-dim:#7a899d;--accent:#3b82f6;--accent-glow:#60a5fa;--danger:#ef4444;--warn:#f59e0b;--ok:#10b981;--radius:10px;--radius-sm:4px;--shadow:0 4px 12px -2px rgba(0,0,0,.4);--font:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,'Noto Sans SC',sans-serif;--trans:.25s cubic-bezier(.4,0,.2,1);--logo-start:#3b82f6;--logo-end:#2563eb;--logo-accent-grad-start:#5fa8ff;--logo-accent-grad-end:#93c5fd}
2 | body.light{--bg:#f6f7f9;--bg-alt:#ffffff;--border:#e2e8f0;--text:#1e293b;--text-dim:#64748b;--accent:#2563eb;--accent-glow:#3b82f6;--shadow:0 4px 20px -4px rgba(0,0,0,.08);--logo-start:#2563eb;--logo-end:#1d4ed8;--logo-accent-grad-start:#1d4ed8;--logo-accent-grad-end:#60a5fa}
3 | *{box-sizing:border-box}
4 | html,body{height:100%;margin:0;padding:0;font-family:var(--font);background:var(--bg);color:var(--text);-webkit-font-smoothing:antialiased}
5 | html.light{background:var(--bg)}
6 | body,button{font-size:14px;line-height:1.35}
7 | a{color:var(--accent);text-decoration:none}
8 | a:hover{color:var(--accent-glow)}
9 | .topbar{position:sticky;top:0;z-index:20;display:flex;align-items:center;gap:1rem;padding:.75rem 1.25rem;background:var(--bg-alt);border-bottom:1px solid var(--border)}
10 | .brand{font-weight:600;letter-spacing:.5px;font-size:15px}
11 | .nav{display:flex;gap:.5rem}
12 | .nav button{background:transparent;border:1px solid var(--border);color:var(--text-dim);padding:.45rem .9rem;border-radius:var(--radius-sm);cursor:pointer;display:flex;align-items:center;gap:.35rem;transition:var(--trans);font-weight:500}
13 | .nav button.active,.nav button:hover{color:var(--text);background:var(--accent);border-color:var(--accent);box-shadow:0 0 0 1px var(--accent-glow),0 4px 10px -2px rgba(0,0,0,.5)}
14 | .actions{margin-left:auto;display:flex;align-items:center;gap:.75rem}
15 | .actions button{background:var(--bg);border:1px solid var(--border);color:var(--text-dim);height:32px;width:38px;border-radius:8px;cursor:pointer;display:grid;place-items:center;transition:var(--trans)}
16 | .actions button:hover{color:var(--text);border-color:var(--accent);background:var(--accent)}
17 | .wrapper{max-width:1680px;margin:1.2rem auto;padding:0 1.2rem;display:flex;flex-direction:column;gap:1.25rem}
18 | .notice{padding:.9rem 1rem;border:1px solid var(--border);background:linear-gradient(145deg,var(--bg-alt),var(--bg));border-radius:var(--radius);display:flex;align-items:center;gap:.75rem;font-size:13px}
19 | .notice.info:before{content:"";width:8px;height:8px;border-radius:50%;background:var(--accent);box-shadow:0 0 0 4px color-mix(in srgb,var(--accent) 20%,transparent)}
20 | .panel{display:none;flex-direction:column;gap:.75rem;animation:fade .4s ease}
21 | .panel.active{display:flex}
22 | .table-wrap{overflow:auto;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg-alt);box-shadow:var(--shadow)}
23 | table.data{width:100%;border-collapse:separate;border-spacing:0;min-width:960px}
24 | table.data thead th{position:sticky;top:0;background:var(--bg-alt);font-weight:500;text-align:left;font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:var(--text-dim);padding:.7rem .75rem;border-bottom:1px solid var(--border);white-space:nowrap}
25 | table.data tbody td{padding:.55rem .75rem;border-bottom:1px solid var(--border);font-size:13px;vertical-align:middle;white-space:nowrap}
26 | /* 防止数值变化导致列抖动:为月流量(7)/当前网络(8)/总流量(9)设置固定宽度并使用等宽数字 */
27 | table.data th,table.data td{font-variant-numeric:tabular-nums}
28 | /* 月流量(2) 左对齐;当前网络(8)/总流量(9) 居中 */
29 | #serversTable thead th:nth-child(2),#serversTable tbody td:nth-child(2){
30 | /* 月流量列:向左贴近协议(减小左 padding),同时加大右 padding 拉开与节点距离 */
31 | width:128px;min-width:128px;max-width:128px;font-variant-numeric:tabular-nums;letter-spacing:.3px;text-align:center;padding:0 1.05rem 0 .15rem;
32 | }
33 | /* 节点列加宽,避免被月流量胶囊视觉挤压 */
34 | #serversTable thead th:nth-child(3),#serversTable tbody td:nth-child(3){
35 | width:160px;min-width:160px;max-width:160px;
36 | }
37 | /* 协议列继续收紧右侧 padding 与固定宽度 */
38 | #serversTable thead th:nth-child(1),#serversTable tbody td:nth-child(1){
39 | padding-right:.14rem;width:78px;min-width:78px;max-width:78px; /* 扩大协议列并恢复适度间距 */
40 | }
41 | /* 让双色胶囊更靠近协议列 */
42 | #serversTable tbody td:nth-child(2) .caps-traffic.duo{margin-left:-6px;} /* 向协议方向微移,视觉更靠近;右侧 padding 增大避免靠近节点 */
43 | #serversTable thead th:nth-child(8),#serversTable tbody td:nth-child(8),
44 | #serversTable thead th:nth-child(9),#serversTable tbody td:nth-child(9){
45 | width:132px;min-width:132px;max-width:132px;font-variant-numeric:tabular-nums;letter-spacing:.3px;text-align:center;
46 | /* 进一步拉开与 CPU/内存/硬盘 组的视觉距离 */
47 | padding-right:1.95rem;
48 | }
49 | /* CPU / 内存 / 硬盘 列:居中 + 固定宽度 与仪表盘一致 */
50 | #serversTable thead th:nth-child(10),#serversTable tbody td:nth-child(10),
51 | #serversTable thead th:nth-child(11),#serversTable tbody td:nth-child(11),
52 | #serversTable thead th:nth-child(12),#serversTable tbody td:nth-child(12){
53 | width:70px;min-width:70px;max-width:70px;text-align:center;padding-left:1.1rem;padding-right:0; /* 继续右移并贴近右侧列 */
54 | }
55 | /* 月流量胶囊 */
56 | .caps-traffic{display:inline-flex;align-items:center;gap:6px;background:linear-gradient(145deg,var(--bg),var(--bg-alt));border:1px solid var(--border);padding:3px 12px 3px 10px;border-radius:999px;font-size:12px;line-height:1;font-weight:500;position:relative;box-shadow:0 2px 4px -2px rgba(0,0,0,.35),0 0 0 1px rgba(255,255,255,.03);}
57 | .caps-traffic:before{content:"";position:absolute;inset:0;border-radius:inherit;background:radial-gradient(circle at 20% 20%,rgba(255,255,255,.06),transparent 70%);pointer-events:none;}
58 | .caps-traffic .io{display:inline-flex;align-items:center;gap:2px;font-variant-numeric:tabular-nums;letter-spacing:.3px;}
59 | .caps-traffic .io.in{color:var(--ok);}
60 | .caps-traffic .io.out{color:var(--accent);}
61 | .caps-traffic .sep{opacity:.4;font-size:11px;display:none;}
62 | .caps-traffic.sm{padding:2px 8px 2px 7px;font-size:11px;gap:5px;}
63 | .caps-traffic.sm .io{font-size:11px;}
64 | /* 双色胶囊:左红右黄 */
65 | .caps-traffic.duo{background:none;border:0;gap:0;padding:0;box-shadow:none;position:relative;border-radius:999px;overflow:hidden;font-size:12px;}
66 | /* 宽度按内容自适应(不再拉伸占满列),每半边仅为其文本 + padding,可容纳最大 111.1MB */
67 | .caps-traffic.duo .half{flex:0 0 auto;display:flex;align-items:center;justify-content:center;padding:2px 4px;font-variant-numeric:tabular-nums;font-weight:600;line-height:1.25; /* 与 .pill 保持一致高度 */ letter-spacing:.25px;color:#fff;font-size:12px;white-space:nowrap;}
68 | /* 双色胶囊配色:
69 | normal (默认): 左白(#fff) 右蓝(accent)
70 | heavy (>=500GB 任一方向): 左黄(warn) 右红(danger)
71 | */
72 | /* normal 初始:淡绿色(入) + 淡蓝色(出) */
73 | .caps-traffic.duo.normal .half.in{background:#d1fae5;color:#065f46;} /* emerald-100 / text-emerald-800 */
74 | body.light .caps-traffic.duo.normal .half.in{background:#d1fae5;color:#065f46;}
75 | .caps-traffic.duo.normal .half.out{background:#bfdbfe;color:#1e3a8a;} /* blue-200 / text-blue-900 */
76 | body.light .caps-traffic.duo.normal .half.out{background:#bfdbfe;color:#1e3a8a;}
77 |
78 | .caps-traffic.duo.heavy .half.in{background:var(--warn);color:#111;}
79 | body.light .caps-traffic.duo.heavy .half.in{background:var(--warn);color:#111;}
80 | .caps-traffic.duo.heavy .half.out{background:var(--danger);color:#fff;}
81 | body.light .caps-traffic.duo.heavy .half.out{color:#fff;}
82 |
83 | /* 半之间分隔线 */
84 | .caps-traffic.duo .half + .half{border-left:1px solid rgba(0,0,0,.18);}
85 | body.light .caps-traffic.duo .half + .half{border-left:1px solid rgba(0,0,0,.08);}
86 | .caps-traffic.duo.sm .half{padding:1px 4px;font-size:10px;min-width:0;}
87 | table.data tbody tr:last-child td{border-bottom:none}
88 | table.data tbody tr:hover{background:rgba(255,255,255,.04)}
89 | .badge{display:inline-block;padding:2px 6px;font-size:11px;border-radius:12px;font-weight:500;line-height:1.2;background:var(--bg);border:1px solid var(--border);color:var(--text-dim)}
90 | .badge.ok{background:rgba(16,185,129,.15);color:var(--ok);border-color:rgba(16,185,129,.3)}
91 | .badge.warn{background:rgba(245,158,11,.15);color:var(--warn);border-color:rgba(245,158,11,.4)}
92 | .badge.err{background:rgba(239,68,68,.15);color:var(--danger);border-color:rgba(239,68,68,.4)}
93 | .footer{margin:2rem 0 2.5rem;display:flex;align-items:center;justify-content:center;gap:.5rem;font-size:12px;color:var(--text-dim)}
94 | .footer a{color:var(--text-dim)}
95 | .footer a:hover{color:var(--accent)}
96 | .muted{color:var(--text-dim)}
97 | /* 旧状态文字样式已不再使用(采用 pill) */
98 | @media (max-width:1100px){.nav{flex-wrap:wrap}.table-wrap{border-radius:8px}}
99 | @keyframes fade{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}
100 |
101 | /* modal styles */
102 | .modal-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.55);display:flex;align-items:flex-start;justify-content:center;padding:5vh 1rem;z-index:50;backdrop-filter:blur(4px)}
103 | .modal-box{position:relative;width:100%;max-width:560px;background:var(--bg-alt);border:1px solid var(--border);border-radius:16px;box-shadow:0 8px 30px -6px rgba(0,0,0,.6);padding:1.25rem 1.35rem;display:flex;flex-direction:column;gap:.9rem;animation:fade .25s ease}
104 | .modal-box.high-load{border-color:rgba(239,68,68,.6);background:linear-gradient(180deg, rgba(239,68,68,.16), rgba(239,68,68,.08)), var(--bg-alt);box-shadow:0 0 0 1px rgba(239,68,68,.38),0 10px 28px -10px rgba(239,68,68,.28)}
105 | body:not(.light) .modal-box.high-load{background:linear-gradient(180deg, rgba(239,68,68,.24), rgba(239,68,68,.12)), var(--bg-alt)}
106 | .modal-title{margin:0;font-size:16px;font-weight:600;letter-spacing:.5px}
107 | .modal-close{position:absolute;top:10px;right:12px;background:transparent;border:0;color:var(--text-dim);font-size:20px;line-height:1;cursor:pointer;padding:4px;border-radius:8px;transition:var(--trans)}
108 | .modal-close:hover{color:var(--text);background:var(--bg)}
109 | .modal-content{font-size:13px;line-height:1.5;display:grid;gap:.6rem}
110 | .kv{display:flex;justify-content:space-between;gap:1rem;padding:.5rem .75rem;background:linear-gradient(145deg,var(--bg),var(--bg-alt));border:1px solid var(--border);border-radius:10px}
111 | .kv span{white-space:nowrap}
112 | .mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}
113 | /* 资源使用百分比色彩标签 */
114 | /* 回退:移除资源使用百分比彩色标签样式 */
115 |
116 | /* 详情弹窗三列信息行 */
117 |
118 | /* 旧 spark 样式已移除,现使用半圆仪表盘 */
119 | /* 全圆旧样式(保留以便回退) */
120 | .gauge{--p:0;--col:var(--accent);width:74px;height:74px;position:relative;display:grid;place-items:center;font-size:11px;font-family:ui-monospace,monospace;font-weight:600;color:var(--text);}
121 | .gauge:before{content:"";position:absolute;inset:0;border-radius:50%;background:conic-gradient(var(--col) calc(var(--p)*1turn),rgba(255,255,255,0.06) 0);mask:radial-gradient(circle at 50% 50%,transparent 58%,#000 59%);-webkit-mask:radial-gradient(circle at 50% 50%,transparent 58%,#000 59%);border:1px solid var(--border);box-shadow:0 2px 6px -2px rgba(0,0,0,.4),0 0 0 1px rgba(255,255,255,.05);}
122 | .gauge span{position:relative;z-index:1}
123 |
124 | /* 半圆仪表盘 */
125 | .gauge-half{--p:0;width:60px;height:34px;position:relative;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;font-family:ui-monospace,monospace;font-size:10px;font-weight:600;gap:0;color:var(--text);}
126 | .gauge-half svg{width:100%;height:28px;overflow:visible;}
127 | .gauge-half path{fill:none;stroke-linecap:round;}
128 | /* 基础颜色 (暗色/亮色自动过渡) */
129 | .gauge-half path.track{stroke:color-mix(in srgb,var(--text-dim) 18%,transparent);stroke-width:6;}
130 | .gauge-half path.arc{stroke:var(--gauge-base,#3b82f6);stroke-width:8;stroke-dasharray:126;stroke-dashoffset:calc(126*(1 - var(--p)));transition:stroke-dashoffset .8s cubic-bezier(.4,0,.2,1),stroke .35s;filter:drop-shadow(0 1px 2px rgba(0,0,0,.45));}
131 | .gauge-half[data-type=mem] path.arc{--gauge-base:#10b981}
132 | .gauge-half[data-type=hdd] path.arc{--gauge-base:#06b6d4}
133 | /* 阈值颜色:>=50% 警告黄,>=90% 危险红 */
134 | .gauge-half[data-warn] path.arc{stroke:var(--warn)}
135 | .gauge-half[data-bad] path.arc{stroke:var(--danger)}
136 | /* 指针:以中心(50,50)为原点旋转;半圆角度范围 180deg -> 从 180deg (左) 到 0deg(右) */
137 | .gauge-half span{line-height:1;position:relative;top:-6px;font-size:12px;}
138 | /* 亮色模式细化对比度 */
139 | body.light .gauge-half path.track{stroke:color-mix(in srgb,var(--text-dim) 28%,transparent)}
140 | body.light .gauge-half path.arc{filter:none}
141 | body.light .gauge-half .needle{background:linear-gradient(var(--text),var(--text-dim))}
142 |
143 | /* status pill */
144 | .pill{display:inline-block;padding:2px 8px;font-size:12px;font-weight:600;border-radius:999px;letter-spacing:.45px;min-width:48px;text-align:center;line-height:1.25;border:0;box-shadow:0 2px 4px -1px rgba(0,0,0,.4),0 0 0 1px rgba(255,255,255,.04);transition:var(--trans);color:#fff}
145 | .pill.on{background:var(--ok)}
146 | .pill.off{background:var(--danger)}
147 | .pill.on:hover{filter:brightness(1.1)}
148 | .pill.off:hover{filter:brightness(1.1)}
149 |
150 | /* buckets CU/CT/CM (simple version) */
151 | .buckets{display:flex;align-items:flex-end;gap:8px;min-width:140px}
152 | .bucket{position:relative;width:26px;height:34px;background:linear-gradient(145deg,var(--bg),var(--bg-alt));border:1px solid var(--border);border-radius:8px;padding:4px 4px 16px;box-sizing:border-box;display:flex;justify-content:flex-end}
153 | .bucket span{display:block;width:100%;background:var(--accent);border-radius:4px 4px 6px 6px;height:var(--h);align-self:flex-end;transition:height .8s cubic-bezier(.4,0,.2,1),background .3s}
154 | .bucket[data-lv=warn] span{background:var(--warn)}
155 | .bucket[data-lv=bad] span{background:var(--danger)}
156 | .bucket label{position:absolute;left:0;right:0;bottom:2px;font-size:10px;text-align:center;color:var(--text-dim);pointer-events:none}
157 | .bucket:hover label{color:var(--text)}
158 |
159 | /* 居中联通电信移动列 */
160 | #serversTable thead th:last-child, #serversTable tbody td:last-child { text-align:center; }
161 | /* 放大第13列宽度以容纳更宽水桶 */
162 | #serversTable thead th:nth-child(13),#serversTable tbody td:nth-child(13){
163 | width:150px;min-width:150px;max-width:150px;padding-left:0;padding-right:.55rem; /* 去除左 padding 进一步贴近 */
164 | }
165 | /* 调整“总流量”表头(第9列)padding 使标题文字居中,不受正文额外右 padding 影响 */
166 | #serversTable thead th:nth-child(9){padding-left:.75rem;padding-right:.75rem;}
167 | .buckets{justify-content:center}
168 |
169 | /* 响应式隐藏非关键列,保持可读性 */
170 | @media (max-width:1100px){
171 | #serversTable{min-width:100%;}
172 | #serversTable thead th:nth-child(3),
173 | #serversTable tbody td:nth-child(3), /* 节点 */
174 | #serversTable thead th:nth-child(4),
175 | #serversTable tbody td:nth-child(4), /* 虚拟化 */
176 | #serversTable thead th:nth-child(8),
177 | #serversTable tbody td:nth-child(8) /* 当前网络 */ {display:none}
178 | }
179 | @media (max-width:820px){
180 | #serversTable thead th:nth-child(5),
181 | #serversTable tbody td:nth-child(5), /* 在线 */
182 | #serversTable thead th:nth-child(6),
183 | #serversTable tbody td:nth-child(6) /* 负载 */ {display:none}
184 | .buckets{gap:6px;min-width:100px}
185 | }
186 | @media (max-width:640px){
187 | #serversTable thead th:nth-child(4),
188 | #serversTable tbody td:nth-child(4) /* 位置 */ {display:none}
189 | .topbar{flex-wrap:wrap;padding:.6rem .8rem}
190 | .actions{width:100%;justify-content:flex-end;margin-top:.4rem}
191 | .wrapper{padding:0 .7rem}
192 | #panel-servers .table-wrap{display:none;}
193 | #serversCards{display:grid!important;grid-template-columns:1fr;gap:.75rem;margin-top:.5rem;}
194 | /* 服务与证书移动端卡片 */
195 | #panel-monitors .table-wrap, #panel-ssl .table-wrap{display:none;}
196 | #monitorsCards,#sslCards{display:grid!important;grid-template-columns:1fr;gap:.75rem;margin-top:.5rem;}
197 | .modal-box{max-width:100%;border-radius:14px;padding:1rem .95rem;}
198 | .modal-content{max-height:65vh;overflow:auto;}
199 | }
200 |
201 | /* SSL 表(证书)列宽与换行修正,避免复用服务器列宽导致名称与域名重叠 */
202 | #sslTable th,#sslTable td{white-space:nowrap;padding:.55rem .75rem;}
203 | #sslTable th:nth-child(1),#sslTable td:nth-child(1){width:140px;min-width:120px;}
204 | /* 域名允许换行以防过长挤压 */
205 | #sslTable th:nth-child(2),#sslTable td:nth-child(2){white-space:normal;max-width:320px;overflow-wrap:anywhere;}
206 |
207 | /* === 覆盖:要求三个表 (servers / monitors / ssl) 全部改为自动宽度 === */
208 | /* 1. 取消全局 table.data 的 min-width 对这三个表的影响 */
209 | #serversTable,#monitorsTable,#sslTable{min-width:0;}
210 | /* 2. 统一去除之前为 serversTable 设定的列固定宽度,允许浏览器自动分配 */
211 | #serversTable thead th,#serversTable tbody td,
212 | #monitorsTable thead th,#monitorsTable tbody td,
213 | #sslTable thead th,#sslTable tbody td{
214 | width:auto!important;min-width:0!important;max-width:none!important;
215 | padding:.55rem .7rem;
216 | }
217 | /* 3. 允许证书域名不受 max-width 限制(如仍需换行可保留 overflow-wrap) */
218 | #sslTable th:nth-child(2),#sslTable td:nth-child(2){max-width:none;white-space:normal;overflow-wrap:anywhere;}
219 | /* 4. 取消“联通|电信|移动”列固定宽度 */
220 | #serversTable thead th:nth-child(13),#serversTable tbody td:nth-child(13){width:auto!important;min-width:0!important;}
221 | /* 5. 如需稍微限制仪表盘相关列最小可读宽度,可设定一个较小下限 (可选) -- 暂不设置,完全交由自动布局 */
222 |
223 | /* === 新增:为“月流量 / 当前网络 / 总流量”三列设置最小宽度,防止内容被压缩换行或挤压 === */
224 | /* 目标最小宽度按示例 "111.1GB|111.1GB" 设计(13 个字符左右),取 16ch 留余量 */
225 | #serversTable thead th:nth-child(2),#serversTable tbody td:nth-child(2),
226 | #serversTable thead th:nth-child(8),#serversTable tbody td:nth-child(8),
227 | #serversTable thead th:nth-child(9),#serversTable tbody td:nth-child(9){
228 | min-width:22ch !important; /* 保留自动宽度,但不小于此值 */
229 | text-align:center;
230 | font-variant-numeric:tabular-nums;
231 | }
232 | .cards .card{border:1px solid var(--border);border-radius:12px;padding:.75rem .85rem;background:linear-gradient(145deg,var(--bg),var(--bg-alt));display:flex;flex-direction:column;gap:.45rem;position:relative;}
233 | .cards .card.offline{opacity:.6;}
234 | .cards .card.high-load{border-color:rgba(239,68,68,.6);background:linear-gradient(180deg, rgba(239,68,68,.22), rgba(239,68,68,.12)), var(--bg-alt);box-shadow:0 0 0 1px rgba(239,68,68,.48),0 6px 18px -6px rgba(239,68,68,.28);}
235 | table.data tbody tr.high-load{background:rgba(239,68,68,.18) !important;}
236 | table.data tbody tr.high-load:hover{background:rgba(239,68,68,.26) !important;}
237 |
238 | /* SSL 域名告警底色:与高负载相同 */
239 | #sslTable td.alert-domain{background:rgba(239,68,68,.18) !important;}
240 | #sslTable tr:hover td.alert-domain{background:rgba(239,68,68,.26) !important;}
241 |
242 | /* OS 着色(更明显):
243 | 1) 为各 OS 类定义 --os-color 变量
244 | 2) 行左侧使用 inset box-shadow 画 4px 彩条
245 | 3) 行背景叠加轻度渐变以提示 OS
246 | */
247 | table.data tbody tr[class*="os-"]{box-shadow:inset 4px 0 0 0 var(--os-color, transparent);background:linear-gradient(180deg, color-mix(in srgb, var(--os-color, transparent) 10%, transparent), transparent 60%);}
248 | table.data tbody tr[class*="os-"]:hover{background:linear-gradient(180deg, color-mix(in srgb, var(--os-color, transparent) 16%, transparent), transparent 65%);}
249 | .cards .card[class*="os-"]{border-color:color-mix(in srgb, var(--os-color, var(--accent)) 60%, transparent);box-shadow:0 0 0 1px color-mix(in srgb, var(--os-color, var(--accent)) 40%, transparent),0 4px 16px -6px color-mix(in srgb, var(--os-color, #000) 35%, transparent);}
250 | /* 弹窗 OS 着色:左侧彩条 + 渐变与卡片一致 */
251 | /* 取消弹窗背景着色,改为仅在标题展示系统胶囊 */
252 | .os-chip{display:inline-flex;align-items:center;padding:2px 8px;margin-left:.5rem;border-radius:999px;font-size:12px;font-weight:600;line-height:1.2;background:var(--os-color, var(--border));color:#fff;border:0;white-space:nowrap;}
253 |
254 | /* 为常见系统赋色 */
255 | .os-linux{--os-color: rgba(16,185,129,.85);} /* 绿色 (通用 Linux,保持不变) */
256 | .os-ubuntu{--os-color: rgba(221,72,20,.9);} /* Ubuntu 橙 (#dd4814) */
257 | .os-debian{--os-color: rgba(215,10,83,.9);} /* Debian 品红 (#d70a53) */
258 | .os-centos{--os-color: rgba(102,0,153,.9);} /* CentOS 紫 (#660099) */
259 | .os-rocky{--os-color: rgba(1,122,66,.9);} /* Rocky Linux 绿 (#017a42) */
260 | .os-almalinux{--os-color: rgba(0,92,170,.9);} /* AlmaLinux 蓝 (#005caa) */
261 | .os-rhel{--os-color: rgba(204,0,0,.9);} /* Red Hat 红 (#cc0000) */
262 | .os-arch{--os-color: rgba(23,147,209,.9);} /* Arch Linux 蓝 (#1793d1) */
263 | .os-alpine{--os-color: rgba(14,87,123,.9);} /* Alpine Linux 蓝 (#0e577b) */
264 | .os-fedora{--os-color: rgba(60,110,180,.9);} /* Fedora 蓝 (#3c6eb4) */
265 | .os-amazon{--os-color: rgba(255,153,0,.9);} /* Amazon Linux 橙 (#ff9900) */
266 | .os-suse{--os-color: rgba(0,150,0,.9);} /* openSUSE 绿 (#009600) */
267 | .os-freebsd{--os-color: rgba(166,31,47,.9);} /* FreeBSD 红 (#a61f2f) */
268 | .os-openbsd{--os-color: rgba(255,204,0,.9);} /* OpenBSD 黄 (#ffcc00) */
269 | .os-bsd{--os-color: rgba(166,31,47,.9);} /* BSD 系统一般跟 FreeBSD 接近 */
270 | .os-darwin{--os-color: rgba(29,29,31,.95);} /* macOS 深空灰 (#1d1d1f) */
271 | .os-windows{--os-color: rgba(0,120,215,.95);} /* Windows 蓝 (#0078d7) */
272 |
273 |
274 |
275 | /* 旧进度条相关样式已清理 */
276 | .cards .card-header{display:flex;align-items:center;justify-content:space-between;gap:.5rem;}
277 | .cards .card-title{font-weight:600;font-size:.95rem;}
278 | .cards .tag{font-size:.65rem;padding:.15rem .4rem;border-radius:4px;background:var(--border);letter-spacing:.5px;}
279 | .cards .status-pill{font-size:.6rem;padding:.2rem .45rem;border-radius:999px;font-weight:500;}
280 | .cards .status-pill.on{background:var(--ok);color:#fff;}
281 | .cards .status-pill.off{background:var(--danger);color:#fff;}
282 | .cards .kvlist{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.35rem .75rem;font-size:.7rem;line-height:1.25;}
283 | .cards .kvlist div{display:flex;flex-direction:column;}
284 | .cards .kvlist span.key{opacity:.6;}
285 | .cards .buckets{margin-top:.25rem;}
286 | /* 证书卡片:域名告警底色(与高负载卡片风格一致) */
287 | .cards .kvlist .alert-domain{background:rgba(239,68,68,.18);border:1px solid rgba(239,68,68,.35);border-radius:8px;padding:.4rem .5rem;}
288 | .cards .kvlist .alert-domain .key{opacity:.85}
289 | /* 移除移动端卡片展开箭头与展开区域(已按需简化交互) */
290 | /* 旧移动端 latency spark 样式移除 */
291 |
292 | /* 简易信号格,用于服务连通性延迟展示 */
293 | .sig{display:inline-flex;gap:2px;vertical-align:baseline;margin:0 4px 0 6px;align-items:flex-end;line-height:1}
294 | .sig .b{width:3px;background:color-mix(in srgb,var(--text-dim) 35%,transparent);border-radius:2px;display:inline-block}
295 | .sig .b:nth-child(1){height:7px}
296 | .sig .b:nth-child(2){height:9px}
297 | .sig .b:nth-child(3){height:11px}
298 | .sig .b:nth-child(4){height:13px}
299 | .sig .b:nth-child(5){height:15px}
300 | .sig .b.on{background:var(--ok)}
301 | .sig .b.off{opacity:.35}
302 |
303 | /* 服务监测项:不同组竖排,同一组横排;不考虑自动换行 */
304 | .mon-items{display:flex;flex-direction:column;gap:6px;align-items:flex-start}
305 | .mon-item{display:inline-flex;align-items:center;white-space:nowrap;line-height:1}
306 | .mon-item .name{margin-right:6px}
307 | .mon-item .ms{margin-left:6px;font-variant-numeric:tabular-nums}
308 | .mon-item .sig{margin:0 6px;transform:translateY(-1px)}
309 |
310 | /* 新 Logo 样式 */
311 | .brand{display:flex;align-items:center;gap:.55rem;font-weight:600;letter-spacing:.5px;font-size:16px;position:relative}
312 | .brand .logo-mark{display:inline-flex;width:34px;height:34px;border-radius:10px;background:linear-gradient(145deg,var(--logo-start) 0%,var(--logo-end) 90%);color:#fff;align-items:center;justify-content:center;box-shadow:0 4px 12px -2px rgba(0,0,0,.45),0 0 0 1px rgba(255,255,255,.08);transition:var(--trans)}
313 | .brand .logo-mark svg{display:block}
314 | .brand .logo-text{font-size:17px;font-weight:700;line-height:1;display:flex;align-items:baseline;gap:2px;letter-spacing:1px}
315 | .brand .logo-text .logo-accent{background:linear-gradient(90deg,var(--logo-accent-grad-start),var(--logo-accent-grad-end));-webkit-background-clip:text;background-clip:text;color:transparent;filter:drop-shadow(0 2px 4px rgba(0,0,0,.3))}
316 | .brand:hover .logo-mark{transform:translateY(-2px) scale(1.05)}
317 | .brand:hover .logo-text{color:var(--text)}
318 | @media (max-width:640px){.brand .logo-text{font-size:15px}.brand .logo-mark{width:30px;height:30px}}
319 |
--------------------------------------------------------------------------------
/server/src/json.c:
--------------------------------------------------------------------------------
1 | /* vim: set et ts=3 sw=3 sts=3 ft=c:
2 | *
3 | * Copyright (C) 2012, 2013, 2014 James McLaughlin et al. All rights reserved.
4 | * https://github.com/udp/json-parser
5 | *
6 | * Redistribution and use in source and binary forms, with or without
7 | * modification, are permitted provided that the following conditions
8 | * are met:
9 | *
10 | * 1. Redistributions of source code must retain the above copyright
11 | * notice, this list of conditions and the following disclaimer.
12 | *
13 | * 2. Redistributions in binary form must reproduce the above copyright
14 | * notice, this list of conditions and the following disclaimer in the
15 | * documentation and/or other materials provided with the distribution.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 | * SUCH DAMAGE.
28 | */
29 |
30 | #include "json.h"
31 |
32 | #ifdef _MSC_VER
33 | #ifndef _CRT_SECURE_NO_WARNINGS
34 | #define _CRT_SECURE_NO_WARNINGS
35 | #endif
36 | #endif
37 |
38 | #ifdef __cplusplus
39 | const struct _json_value json_value_none; /* zero-d by ctor */
40 | #else
41 | const struct _json_value json_value_none = { 0 };
42 | #endif
43 |
44 | #include
45 | #include
46 | #include
47 | #include
48 |
49 | typedef unsigned short json_uchar;
50 |
51 | static unsigned char hex_value (json_char c)
52 | {
53 | if (isdigit(c))
54 | return c - '0';
55 |
56 | switch (c) {
57 | case 'a': case 'A': return 0x0A;
58 | case 'b': case 'B': return 0x0B;
59 | case 'c': case 'C': return 0x0C;
60 | case 'd': case 'D': return 0x0D;
61 | case 'e': case 'E': return 0x0E;
62 | case 'f': case 'F': return 0x0F;
63 | default: return 0xFF;
64 | }
65 | }
66 |
67 | typedef struct
68 | {
69 | unsigned long used_memory;
70 |
71 | unsigned int uint_max;
72 | unsigned long ulong_max;
73 |
74 | json_settings settings;
75 | int first_pass;
76 |
77 | } json_state;
78 |
79 | static void * default_alloc (size_t size, int zero, void * user_data)
80 | {
81 | return zero ? calloc (1, size) : malloc (size);
82 | }
83 |
84 | static void default_free (void * ptr, void * user_data)
85 | {
86 | free (ptr);
87 | }
88 |
89 | static void * json_alloc (json_state * state, unsigned long size, int zero)
90 | {
91 | if ((state->ulong_max - state->used_memory) < size)
92 | return 0;
93 |
94 | if (state->settings.max_memory
95 | && (state->used_memory += size) > state->settings.max_memory)
96 | {
97 | return 0;
98 | }
99 |
100 | return state->settings.mem_alloc (size, zero, state->settings.user_data);
101 | }
102 |
103 | static int new_value
104 | (json_state * state, json_value ** top, json_value ** root, json_value ** alloc, json_type type)
105 | {
106 | json_value * value;
107 | int values_size;
108 |
109 | if (!state->first_pass)
110 | {
111 | value = *top = *alloc;
112 | *alloc = (*alloc)->_reserved.next_alloc;
113 |
114 | if (!*root)
115 | *root = value;
116 |
117 | switch (value->type)
118 | {
119 | case json_array:
120 |
121 | if (! (value->u.array.values = (json_value **) json_alloc
122 | (state, value->u.array.length * sizeof (json_value *), 0)) )
123 | {
124 | return 0;
125 | }
126 |
127 | value->u.array.length = 0;
128 | break;
129 |
130 | case json_object:
131 |
132 | values_size = sizeof (*value->u.object.values) * value->u.object.length;
133 |
134 | void *tmp_alloc = json_alloc(state, values_size + ((unsigned long) value->u.object.values), 0);
135 | if (!tmp_alloc)
136 | {
137 | return 0;
138 | }
139 | /* 避免违反严格别名:通过中间变量复制 */
140 | memcpy(&value->u.object.values, &tmp_alloc, sizeof(void*));
141 | char *obj_mem = (char*)value->u.object.values + values_size;
142 | memcpy(&value->_reserved.object_mem, &obj_mem, sizeof(char*));
143 |
144 | value->u.object.length = 0;
145 | break;
146 |
147 | case json_string:
148 |
149 | if (! (value->u.string.ptr = (json_char *) json_alloc
150 | (state, (value->u.string.length + 1) * sizeof (json_char), 0)) )
151 | {
152 | return 0;
153 | }
154 |
155 | value->u.string.length = 0;
156 | break;
157 |
158 | default:
159 | break;
160 | };
161 |
162 | return 1;
163 | }
164 |
165 | value = (json_value *) json_alloc (state, sizeof (json_value), 1);
166 |
167 | if (!value)
168 | return 0;
169 |
170 | if (!*root)
171 | *root = value;
172 |
173 | value->type = type;
174 | value->parent = *top;
175 |
176 | if (*alloc)
177 | (*alloc)->_reserved.next_alloc = value;
178 |
179 | *alloc = *top = value;
180 |
181 | return 1;
182 | }
183 |
184 | #define e_off \
185 | ((int) (i - cur_line_begin))
186 |
187 | #define whitespace \
188 | case '\n': ++ cur_line; cur_line_begin = i; \
189 | case ' ': case '\t': case '\r'
190 |
191 | #define string_add(b) \
192 | do { if (!state.first_pass) string [string_length] = b; ++ string_length; } while (0);
193 |
194 | static const long
195 | flag_next = 1 << 0,
196 | flag_reproc = 1 << 1,
197 | flag_need_comma = 1 << 2,
198 | flag_seek_value = 1 << 3,
199 | flag_escaped = 1 << 4,
200 | flag_string = 1 << 5,
201 | flag_need_colon = 1 << 6,
202 | flag_done = 1 << 7,
203 | flag_num_negative = 1 << 8,
204 | flag_num_zero = 1 << 9,
205 | flag_num_e = 1 << 10,
206 | flag_num_e_got_sign = 1 << 11,
207 | flag_num_e_negative = 1 << 12,
208 | flag_line_comment = 1 << 13,
209 | flag_block_comment = 1 << 14;
210 |
211 | json_value * json_parse_ex (json_settings * settings,
212 | const json_char * json,
213 | size_t length,
214 | char * error_buf)
215 | {
216 | json_char error [json_error_max];
217 | unsigned int cur_line;
218 | const json_char * cur_line_begin, * i, * end;
219 | json_value * top, * root, * alloc = 0;
220 | json_state state = { 0 };
221 | long flags;
222 | long num_digits = 0, num_e = 0;
223 | json_int_t num_fraction = 0;
224 |
225 | /* Skip UTF-8 BOM
226 | */
227 | if (length >= 3 && ((unsigned char) json [0]) == 0xEF
228 | && ((unsigned char) json [1]) == 0xBB
229 | && ((unsigned char) json [2]) == 0xBF)
230 | {
231 | json += 3;
232 | length -= 3;
233 | }
234 |
235 | error[0] = '\0';
236 | end = (json + length);
237 |
238 | memcpy (&state.settings, settings, sizeof (json_settings));
239 |
240 | if (!state.settings.mem_alloc)
241 | state.settings.mem_alloc = default_alloc;
242 |
243 | if (!state.settings.mem_free)
244 | state.settings.mem_free = default_free;
245 |
246 | memset (&state.uint_max, 0xFF, sizeof (state.uint_max));
247 | memset (&state.ulong_max, 0xFF, sizeof (state.ulong_max));
248 |
249 | state.uint_max -= 8; /* limit of how much can be added before next check */
250 | state.ulong_max -= 8;
251 |
252 | for (state.first_pass = 1; state.first_pass >= 0; -- state.first_pass)
253 | {
254 | json_uchar uchar;
255 | unsigned char uc_b1, uc_b2, uc_b3, uc_b4;
256 | json_char * string = 0;
257 | unsigned int string_length = 0;
258 |
259 | top = root = 0;
260 | flags = flag_seek_value;
261 |
262 | cur_line = 1;
263 | cur_line_begin = json;
264 |
265 | for (i = json ;; ++ i)
266 | {
267 | json_char b = (i == end ? 0 : *i);
268 |
269 | if (flags & flag_string)
270 | {
271 | if (!b)
272 | { sprintf (error, "Unexpected EOF in string (at %d:%d)", cur_line, e_off);
273 | goto e_failed;
274 | }
275 |
276 | if (string_length > state.uint_max)
277 | goto e_overflow;
278 |
279 | if (flags & flag_escaped)
280 | {
281 | flags &= ~ flag_escaped;
282 |
283 | switch (b)
284 | {
285 | case 'b': string_add ('\b'); break;
286 | case 'f': string_add ('\f'); break;
287 | case 'n': string_add ('\n'); break;
288 | case 'r': string_add ('\r'); break;
289 | case 't': string_add ('\t'); break;
290 | case 'u':
291 |
292 | if (end - i < 4 ||
293 | (uc_b1 = hex_value (*++ i)) == 0xFF || (uc_b2 = hex_value (*++ i)) == 0xFF
294 | || (uc_b3 = hex_value (*++ i)) == 0xFF || (uc_b4 = hex_value (*++ i)) == 0xFF)
295 | {
296 | sprintf (error, "Invalid character value `%c` (at %d:%d)", b, cur_line, e_off);
297 | goto e_failed;
298 | }
299 |
300 | uc_b1 = uc_b1 * 16 + uc_b2;
301 | uc_b2 = uc_b3 * 16 + uc_b4;
302 |
303 | uchar = ((json_char) uc_b1) * 256 + uc_b2;
304 |
305 | if (sizeof (json_char) >= sizeof (json_uchar) || (uc_b1 == 0 && uc_b2 <= 0x7F))
306 | {
307 | string_add ((json_char) uchar);
308 | break;
309 | }
310 |
311 | if (uchar <= 0x7FF)
312 | {
313 | if (state.first_pass)
314 | string_length += 2;
315 | else
316 | { string [string_length ++] = 0xC0 | ((uc_b2 & 0xC0) >> 6) | ((uc_b1 & 0x7) << 2);
317 | string [string_length ++] = 0x80 | (uc_b2 & 0x3F);
318 | }
319 |
320 | break;
321 | }
322 |
323 | if (state.first_pass)
324 | string_length += 3;
325 | else
326 | { string [string_length ++] = 0xE0 | ((uc_b1 & 0xF0) >> 4);
327 | string [string_length ++] = 0x80 | ((uc_b1 & 0xF) << 2) | ((uc_b2 & 0xC0) >> 6);
328 | string [string_length ++] = 0x80 | (uc_b2 & 0x3F);
329 | }
330 |
331 | break;
332 |
333 | default:
334 | string_add (b);
335 | };
336 |
337 | continue;
338 | }
339 |
340 | if (b == '\\')
341 | {
342 | flags |= flag_escaped;
343 | continue;
344 | }
345 |
346 | if (b == '"')
347 | {
348 | if (!state.first_pass)
349 | string [string_length] = 0;
350 |
351 | flags &= ~ flag_string;
352 | string = 0;
353 |
354 | switch (top->type)
355 | {
356 | case json_string:
357 |
358 | top->u.string.length = string_length;
359 | flags |= flag_next;
360 |
361 | break;
362 |
363 | case json_object:
364 |
365 | if (state.first_pass)
366 | {
367 | json_char *adv = (json_char*)top->u.object.values;
368 | adv += string_length + 1;
369 | memcpy(&top->u.object.values, &adv, sizeof(json_char*));
370 | }
371 | else
372 | {
373 | top->u.object.values[top->u.object.length].name = (json_char *)top->_reserved.object_mem;
374 | top->u.object.values[top->u.object.length].name_length = string_length;
375 | json_char *adv2 = (json_char*)top->_reserved.object_mem;
376 | adv2 += string_length + 1;
377 | memcpy(&top->_reserved.object_mem, &adv2, sizeof(json_char*));
378 | }
379 |
380 | flags |= flag_seek_value | flag_need_colon;
381 | continue;
382 |
383 | default:
384 | break;
385 | };
386 | }
387 | else
388 | {
389 | string_add (b);
390 | continue;
391 | }
392 | }
393 |
394 | if (state.settings.settings & json_enable_comments)
395 | {
396 | if (flags & (flag_line_comment | flag_block_comment))
397 | {
398 | if (flags & flag_line_comment)
399 | {
400 | if (b == '\r' || b == '\n' || !b)
401 | {
402 | flags &= ~ flag_line_comment;
403 | -- i; /* so null can be reproc'd */
404 | }
405 |
406 | continue;
407 | }
408 |
409 | if (flags & flag_block_comment)
410 | {
411 | if (!b)
412 | { sprintf (error, "%d:%d: Unexpected EOF in block comment", cur_line, e_off);
413 | goto e_failed;
414 | }
415 |
416 | if (b == '*' && i < (end - 1) && i [1] == '/')
417 | {
418 | flags &= ~ flag_block_comment;
419 | ++ i; /* skip closing sequence */
420 | }
421 |
422 | continue;
423 | }
424 | }
425 | else if (b == '/')
426 | {
427 | if (! (flags & (flag_seek_value | flag_done)) && top->type != json_object)
428 | {
429 | sprintf (error, "%d:%d: Comment not allowed here", cur_line, e_off);
430 | goto e_failed;
431 | }
432 |
433 | if (++ i == end)
434 | { sprintf (error, "%d:%d: EOF unexpected", cur_line, e_off);
435 | goto e_failed;
436 | }
437 |
438 | switch (b = *i)
439 | {
440 | case '/':
441 | flags |= flag_line_comment;
442 | continue;
443 |
444 | case '*':
445 | flags |= flag_block_comment;
446 | continue;
447 |
448 | default:
449 | sprintf (error, "%d:%d: Unexpected `%c` in comment opening sequence", cur_line, e_off, b);
450 | goto e_failed;
451 | };
452 | }
453 | }
454 |
455 | if (flags & flag_done)
456 | {
457 | if (!b)
458 | break;
459 |
460 | switch (b)
461 | {
462 | whitespace:
463 | continue;
464 |
465 | default:
466 | sprintf (error, "%d:%d: Trailing garbage: `%c`", cur_line, e_off, b);
467 | goto e_failed;
468 | };
469 | }
470 |
471 | if (flags & flag_seek_value)
472 | {
473 | switch (b)
474 | {
475 | whitespace:
476 | continue;
477 |
478 | case ']':
479 |
480 | if (top->type == json_array)
481 | flags = (flags & ~ (flag_need_comma | flag_seek_value)) | flag_next;
482 | else
483 | { sprintf (error, "%d:%d: Unexpected ]", cur_line, e_off);
484 | goto e_failed;
485 | }
486 |
487 | break;
488 |
489 | default:
490 |
491 | if (flags & flag_need_comma)
492 | {
493 | if (b == ',')
494 | { flags &= ~ flag_need_comma;
495 | continue;
496 | }
497 | else
498 | { sprintf (error, "%d:%d: Expected , before %c", cur_line, e_off, b);
499 | goto e_failed;
500 | }
501 | }
502 |
503 | if (flags & flag_need_colon)
504 | {
505 | if (b == ':')
506 | { flags &= ~ flag_need_colon;
507 | continue;
508 | }
509 | else
510 | { sprintf (error, "%d:%d: Expected : before %c", cur_line, e_off, b);
511 | goto e_failed;
512 | }
513 | }
514 |
515 | flags &= ~ flag_seek_value;
516 |
517 | switch (b)
518 | {
519 | case '{':
520 |
521 | if (!new_value (&state, &top, &root, &alloc, json_object))
522 | goto e_alloc_failure;
523 |
524 | continue;
525 |
526 | case '[':
527 |
528 | if (!new_value (&state, &top, &root, &alloc, json_array))
529 | goto e_alloc_failure;
530 |
531 | flags |= flag_seek_value;
532 | continue;
533 |
534 | case '"':
535 |
536 | if (!new_value (&state, &top, &root, &alloc, json_string))
537 | goto e_alloc_failure;
538 |
539 | flags |= flag_string;
540 |
541 | string = top->u.string.ptr;
542 | string_length = 0;
543 |
544 | continue;
545 |
546 | case 't':
547 |
548 | if ((end - i) < 3 || *(++ i) != 'r' || *(++ i) != 'u' || *(++ i) != 'e')
549 | goto e_unknown_value;
550 |
551 | if (!new_value (&state, &top, &root, &alloc, json_boolean))
552 | goto e_alloc_failure;
553 |
554 | top->u.boolean = 1;
555 |
556 | flags |= flag_next;
557 | break;
558 |
559 | case 'f':
560 |
561 | if ((end - i) < 4 || *(++ i) != 'a' || *(++ i) != 'l' || *(++ i) != 's' || *(++ i) != 'e')
562 | goto e_unknown_value;
563 |
564 | if (!new_value (&state, &top, &root, &alloc, json_boolean))
565 | goto e_alloc_failure;
566 |
567 | flags |= flag_next;
568 | break;
569 |
570 | case 'n':
571 |
572 | if ((end - i) < 3 || *(++ i) != 'u' || *(++ i) != 'l' || *(++ i) != 'l')
573 | goto e_unknown_value;
574 |
575 | if (!new_value (&state, &top, &root, &alloc, json_null))
576 | goto e_alloc_failure;
577 |
578 | flags |= flag_next;
579 | break;
580 |
581 | default:
582 |
583 | if (isdigit (b) || b == '-')
584 | {
585 | if (!new_value (&state, &top, &root, &alloc, json_integer))
586 | goto e_alloc_failure;
587 |
588 | if (!state.first_pass)
589 | {
590 | while (isdigit (b) || b == '+' || b == '-'
591 | || b == 'e' || b == 'E' || b == '.')
592 | {
593 | if ( (++ i) == end)
594 | {
595 | b = 0;
596 | break;
597 | }
598 |
599 | b = *i;
600 | }
601 |
602 | flags |= flag_next | flag_reproc;
603 | break;
604 | }
605 |
606 | flags &= ~ (flag_num_negative | flag_num_e |
607 | flag_num_e_got_sign | flag_num_e_negative |
608 | flag_num_zero);
609 |
610 | num_digits = 0;
611 | num_fraction = 0;
612 | num_e = 0;
613 |
614 | if (b != '-')
615 | {
616 | flags |= flag_reproc;
617 | break;
618 | }
619 |
620 | flags |= flag_num_negative;
621 | continue;
622 | }
623 | else
624 | { sprintf (error, "%d:%d: Unexpected %c when seeking value", cur_line, e_off, b);
625 | goto e_failed;
626 | }
627 | };
628 | };
629 | }
630 | else
631 | {
632 | switch (top->type)
633 | {
634 | case json_object:
635 |
636 | switch (b)
637 | {
638 | whitespace:
639 | continue;
640 |
641 | case '"':
642 |
643 | if (flags & flag_need_comma)
644 | {
645 | sprintf (error, "%d:%d: Expected , before \"", cur_line, e_off);
646 | goto e_failed;
647 | }
648 |
649 | flags |= flag_string;
650 |
651 | string = (json_char *) top->_reserved.object_mem;
652 | string_length = 0;
653 |
654 | break;
655 |
656 | case '}':
657 |
658 | flags = (flags & ~ flag_need_comma) | flag_next;
659 | break;
660 |
661 | case ',':
662 |
663 | if (flags & flag_need_comma)
664 | {
665 | flags &= ~ flag_need_comma;
666 | break;
667 | }
668 |
669 | default:
670 |
671 | sprintf (error, "%d:%d: Unexpected `%c` in object", cur_line, e_off, b);
672 | goto e_failed;
673 | };
674 |
675 | break;
676 |
677 | case json_integer:
678 | case json_double:
679 |
680 | if (isdigit (b))
681 | {
682 | ++ num_digits;
683 |
684 | if (top->type == json_integer || flags & flag_num_e)
685 | {
686 | if (! (flags & flag_num_e))
687 | {
688 | if (flags & flag_num_zero)
689 | { sprintf (error, "%d:%d: Unexpected `0` before `%c`", cur_line, e_off, b);
690 | goto e_failed;
691 | }
692 |
693 | if (num_digits == 1 && b == '0')
694 | flags |= flag_num_zero;
695 | }
696 | else
697 | {
698 | flags |= flag_num_e_got_sign;
699 | num_e = (num_e * 10) + (b - '0');
700 | continue;
701 | }
702 |
703 | top->u.integer = (top->u.integer * 10) + (b - '0');
704 | continue;
705 | }
706 |
707 | num_fraction = (num_fraction * 10) + (b - '0');
708 | continue;
709 | }
710 |
711 | if (b == '+' || b == '-')
712 | {
713 | if ( (flags & flag_num_e) && !(flags & flag_num_e_got_sign))
714 | {
715 | flags |= flag_num_e_got_sign;
716 |
717 | if (b == '-')
718 | flags |= flag_num_e_negative;
719 |
720 | continue;
721 | }
722 | }
723 | else if (b == '.' && top->type == json_integer)
724 | {
725 | if (!num_digits)
726 | { sprintf (error, "%d:%d: Expected digit before `.`", cur_line, e_off);
727 | goto e_failed;
728 | }
729 |
730 | top->type = json_double;
731 | top->u.dbl = (double) top->u.integer;
732 |
733 | num_digits = 0;
734 | continue;
735 | }
736 |
737 | if (! (flags & flag_num_e))
738 | {
739 | if (top->type == json_double)
740 | {
741 | if (!num_digits)
742 | { sprintf (error, "%d:%d: Expected digit after `.`", cur_line, e_off);
743 | goto e_failed;
744 | }
745 |
746 | top->u.dbl += ((double) num_fraction) / (pow (10, (double) num_digits));
747 | }
748 |
749 | if (b == 'e' || b == 'E')
750 | {
751 | flags |= flag_num_e;
752 |
753 | if (top->type == json_integer)
754 | {
755 | top->type = json_double;
756 | top->u.dbl = (double) top->u.integer;
757 | }
758 |
759 | num_digits = 0;
760 | flags &= ~ flag_num_zero;
761 |
762 | continue;
763 | }
764 | }
765 | else
766 | {
767 | if (!num_digits)
768 | { sprintf (error, "%d:%d: Expected digit after `e`", cur_line, e_off);
769 | goto e_failed;
770 | }
771 |
772 | top->u.dbl *= pow (10, (double) (flags & flag_num_e_negative ? - num_e : num_e));
773 | }
774 |
775 | if (flags & flag_num_negative)
776 | {
777 | if (top->type == json_integer)
778 | top->u.integer = - top->u.integer;
779 | else
780 | top->u.dbl = - top->u.dbl;
781 | }
782 |
783 | flags |= flag_next | flag_reproc;
784 | break;
785 |
786 | default:
787 | break;
788 | };
789 | }
790 |
791 | if (flags & flag_reproc)
792 | {
793 | flags &= ~ flag_reproc;
794 | -- i;
795 | }
796 |
797 | if (flags & flag_next)
798 | {
799 | flags = (flags & ~ flag_next) | flag_need_comma;
800 |
801 | if (!top->parent)
802 | {
803 | /* root value done */
804 |
805 | flags |= flag_done;
806 | continue;
807 | }
808 |
809 | if (top->parent->type == json_array)
810 | flags |= flag_seek_value;
811 |
812 | if (!state.first_pass)
813 | {
814 | json_value * parent = top->parent;
815 |
816 | switch (parent->type)
817 | {
818 | case json_object:
819 |
820 | parent->u.object.values
821 | [parent->u.object.length].value = top;
822 |
823 | break;
824 |
825 | case json_array:
826 |
827 | parent->u.array.values
828 | [parent->u.array.length] = top;
829 |
830 | break;
831 |
832 | default:
833 | break;
834 | };
835 | }
836 |
837 | if ( (++ top->parent->u.array.length) > state.uint_max)
838 | goto e_overflow;
839 |
840 | top = top->parent;
841 |
842 | continue;
843 | }
844 | }
845 |
846 | alloc = root;
847 | }
848 |
849 | return root;
850 |
851 | e_unknown_value:
852 |
853 | sprintf (error, "%d:%d: Unknown value", cur_line, e_off);
854 | goto e_failed;
855 |
856 | e_alloc_failure:
857 |
858 | strcpy (error, "Memory allocation failure");
859 | goto e_failed;
860 |
861 | e_overflow:
862 |
863 | sprintf (error, "%d:%d: Too long (caught overflow)", cur_line, e_off);
864 | goto e_failed;
865 |
866 | e_failed:
867 |
868 | if (error_buf)
869 | {
870 | if (*error)
871 | strcpy (error_buf, error);
872 | else
873 | strcpy (error_buf, "Unknown error");
874 | }
875 |
876 | if (state.first_pass)
877 | alloc = root;
878 |
879 | while (alloc)
880 | {
881 | top = alloc->_reserved.next_alloc;
882 | state.settings.mem_free (alloc, state.settings.user_data);
883 | alloc = top;
884 | }
885 |
886 | if (!state.first_pass)
887 | json_value_free_ex (&state.settings, root);
888 |
889 | return 0;
890 | }
891 |
892 | json_value * json_parse (const json_char * json, size_t length)
893 | {
894 | json_settings settings = { 0 };
895 | return json_parse_ex (&settings, json, length, 0);
896 | }
897 |
898 | void json_value_free_ex (json_settings * settings, json_value * value)
899 | {
900 | json_value * cur_value;
901 |
902 | if (!value)
903 | return;
904 |
905 | value->parent = 0;
906 |
907 | while (value)
908 | {
909 | switch (value->type)
910 | {
911 | case json_array:
912 |
913 | if (!value->u.array.length)
914 | {
915 | settings->mem_free (value->u.array.values, settings->user_data);
916 | break;
917 | }
918 |
919 | value = value->u.array.values [-- value->u.array.length];
920 | continue;
921 |
922 | case json_object:
923 |
924 | if (!value->u.object.length)
925 | {
926 | settings->mem_free (value->u.object.values, settings->user_data);
927 | break;
928 | }
929 |
930 | value = value->u.object.values [-- value->u.object.length].value;
931 | continue;
932 |
933 | case json_string:
934 |
935 | settings->mem_free (value->u.string.ptr, settings->user_data);
936 | break;
937 |
938 | default:
939 | break;
940 | };
941 |
942 | cur_value = value;
943 | value = value->parent;
944 | settings->mem_free (cur_value, settings->user_data);
945 | }
946 | }
947 |
948 | void json_value_free (json_value * value)
949 | {
950 | json_settings settings = { 0 };
951 | settings.mem_free = default_free;
952 | json_value_free_ex (&settings, value);
953 | }
954 |
--------------------------------------------------------------------------------
/server/include/system.h:
--------------------------------------------------------------------------------
1 | /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2 | /* If you are missing that file, acquire a complete release at teeworlds.com. */
3 |
4 | /*
5 | Title: OS Abstraction
6 | */
7 |
8 | #ifndef BASE_SYSTEM_H
9 | #define BASE_SYSTEM_H
10 |
11 | #include "detect.h"
12 |
13 | #ifdef __cplusplus
14 | extern "C" {
15 | #endif
16 |
17 | /* Group: Debug */
18 | /*
19 | Function: dbg_assert
20 | Breaks into the debugger based on a test.
21 |
22 | Parameters:
23 | test - Result of the test.
24 | msg - Message that should be printed if the test fails.
25 |
26 | Remarks:
27 | Does nothing in release version of the library.
28 |
29 | See Also:
30 |
31 | */
32 | void dbg_assert(int test, const char *msg);
33 | #define dbg_assert(test,msg) dbg_assert_imp(__FILE__, __LINE__, test, msg)
34 | void dbg_assert_imp(const char *filename, int line, int test, const char *msg);
35 |
36 |
37 | #ifdef __clang_analyzer__
38 | #include
39 | #undef dbg_assert
40 | #define dbg_assert(test,msg) assert(test)
41 | #endif
42 |
43 | /*
44 | Function: dbg_break
45 | Breaks into the debugger.
46 |
47 | Remarks:
48 | Does nothing in release version of the library.
49 |
50 | See Also:
51 |
52 | */
53 | void dbg_break();
54 |
55 | /*
56 | Function: dbg_msg
57 |
58 | Prints a debug message.
59 |
60 | Parameters:
61 | sys - A string that describes what system the message belongs to
62 | fmt - A printf styled format string.
63 |
64 | Remarks:
65 | Does nothing in release version of the library.
66 |
67 | See Also:
68 |
69 | */
70 | void dbg_msg(const char *sys, const char *fmt, ...);
71 |
72 | /* Group: Memory */
73 |
74 | /*
75 | Function: mem_alloc
76 | Allocates memory.
77 |
78 | Parameters:
79 | size - Size of the needed block.
80 | alignment - Alignment for the block.
81 |
82 | Returns:
83 | Returns a pointer to the newly allocated block. Returns a
84 | null pointer if the memory couldn't be allocated.
85 |
86 | Remarks:
87 | - Passing 0 to size will allocated the smallest amount possible
88 | and return a unique pointer.
89 |
90 | See Also:
91 |
92 | */
93 | void *mem_alloc_debug(const char *filename, int line, unsigned size, unsigned alignment);
94 | #define mem_alloc(s,a) mem_alloc_debug(__FILE__, __LINE__, (s), (a))
95 |
96 | /*
97 | Function: mem_free
98 | Frees a block allocated through .
99 |
100 | Remarks:
101 | - In the debug version of the library the function will assert if
102 | a non-valid block is passed, like a null pointer or a block that
103 | isn't allocated.
104 |
105 | See Also:
106 |
107 | */
108 | void mem_free(void *block);
109 |
110 | /*
111 | Function: mem_copy
112 | Copies a a memory block.
113 |
114 | Parameters:
115 | dest - Destination.
116 | source - Source to copy.
117 | size - Size of the block to copy.
118 |
119 | Remarks:
120 | - This functions DOES NOT handles cases where source and
121 | destination is overlapping.
122 |
123 | See Also:
124 |
125 | */
126 | void mem_copy(void *dest, const void *source, unsigned size);
127 |
128 | /*
129 | Function: mem_move
130 | Copies a a memory block
131 |
132 | Parameters:
133 | dest - Destination
134 | source - Source to copy
135 | size - Size of the block to copy
136 |
137 | Remarks:
138 | - This functions handles cases where source and destination
139 | is overlapping
140 |
141 | See Also:
142 |
143 | */
144 | void mem_move(void *dest, const void *source, unsigned size);
145 |
146 | /*
147 | Function: mem_zero
148 | Sets a complete memory block to 0
149 |
150 | Parameters:
151 | block - Pointer to the block to zero out
152 | size - Size of the block
153 | */
154 | void mem_zero(void *block, unsigned size);
155 |
156 | /*
157 | Function: mem_comp
158 | Compares two blocks of memory
159 |
160 | Parameters:
161 | a - First block of data
162 | b - Second block of data
163 | size - Size of the data to compare
164 |
165 | Returns:
166 | <0 - Block a is lesser then block b
167 | 0 - Block a is equal to block b
168 | >0 - Block a is greater then block b
169 | */
170 | int mem_comp(const void *a, const void *b, int size);
171 |
172 | /*
173 | Function: mem_check
174 | Validates the heap
175 | Will trigger a assert if memory has failed.
176 | */
177 | int mem_check_imp();
178 | #define mem_check() dbg_assert_imp(__FILE__, __LINE__, mem_check_imp(), "Memory check failed")
179 |
180 | /* Group: File IO */
181 | enum {
182 | IOFLAG_READ = 1,
183 | IOFLAG_WRITE = 2,
184 | IOFLAG_RANDOM = 4,
185 |
186 | IOSEEK_START = 0,
187 | IOSEEK_CUR = 1,
188 | IOSEEK_END = 2
189 | };
190 |
191 | typedef struct IOINTERNAL *IOHANDLE;
192 |
193 | /*
194 | Function: io_open
195 | Opens a file.
196 |
197 | Parameters:
198 | filename - File to open.
199 | flags - A set of flags. IOFLAG_READ, IOFLAG_WRITE, IOFLAG_RANDOM.
200 |
201 | Returns:
202 | Returns a handle to the file on success and 0 on failure.
203 |
204 | */
205 | IOHANDLE io_open(const char *filename, int flags);
206 |
207 | /*
208 | Function: io_read
209 | Reads data into a buffer from a file.
210 |
211 | Parameters:
212 | io - Handle to the file to read data from.
213 | buffer - Pointer to the buffer that will recive the data.
214 | size - Number of bytes to read from the file.
215 |
216 | Returns:
217 | Number of bytes read.
218 |
219 | */
220 | unsigned io_read(IOHANDLE io, void *buffer, unsigned size);
221 |
222 | /*
223 | Function: io_skip
224 | Skips data in a file.
225 |
226 | Parameters:
227 | io - Handle to the file.
228 | size - Number of bytes to skip.
229 |
230 | Returns:
231 | Number of bytes skipped.
232 | */
233 | unsigned io_skip(IOHANDLE io, int size);
234 |
235 | /*
236 | Function: io_write
237 | Writes data from a buffer to file.
238 |
239 | Parameters:
240 | io - Handle to the file.
241 | buffer - Pointer to the data that should be written.
242 | size - Number of bytes to write.
243 |
244 | Returns:
245 | Number of bytes written.
246 | */
247 | unsigned io_write(IOHANDLE io, const void *buffer, unsigned size);
248 |
249 | /*
250 | Function: io_write_newline
251 | Writes newline to file.
252 |
253 | Parameters:
254 | io - Handle to the file.
255 |
256 | Returns:
257 | Number of bytes written.
258 | */
259 | unsigned io_write_newline(IOHANDLE io);
260 |
261 | /*
262 | Function: io_seek
263 | Seeks to a specified offset in the file.
264 |
265 | Parameters:
266 | io - Handle to the file.
267 | offset - Offset from pos to stop.
268 | origin - Position to start searching from.
269 |
270 | Returns:
271 | Returns 0 on success.
272 | */
273 | int io_seek(IOHANDLE io, int offset, int origin);
274 |
275 | /*
276 | Function: io_tell
277 | Gets the current position in the file.
278 |
279 | Parameters:
280 | io - Handle to the file.
281 |
282 | Returns:
283 | Returns the current position. -1L if an error occured.
284 | */
285 | long int io_tell(IOHANDLE io);
286 |
287 | /*
288 | Function: io_length
289 | Gets the total length of the file. Resetting cursor to the beginning
290 |
291 | Parameters:
292 | io - Handle to the file.
293 |
294 | Returns:
295 | Returns the total size. -1L if an error occured.
296 | */
297 | long int io_length(IOHANDLE io);
298 |
299 | /*
300 | Function: io_close
301 | Closes a file.
302 |
303 | Parameters:
304 | io - Handle to the file.
305 |
306 | Returns:
307 | Returns 0 on success.
308 | */
309 | int io_close(IOHANDLE io);
310 |
311 | /*
312 | Function: io_flush
313 | Empties all buffers and writes all pending data.
314 |
315 | Parameters:
316 | io - Handle to the file.
317 |
318 | Returns:
319 | Returns 0 on success.
320 | */
321 | int io_flush(IOHANDLE io);
322 |
323 |
324 | /*
325 | Function: io_stdin
326 | Returns an to the standard input.
327 | */
328 | IOHANDLE io_stdin();
329 |
330 | /*
331 | Function: io_stdout
332 | Returns an to the standard output.
333 | */
334 | IOHANDLE io_stdout();
335 |
336 | /*
337 | Function: io_stderr
338 | Returns an to the standard error.
339 | */
340 | IOHANDLE io_stderr();
341 |
342 |
343 | /* Group: Threads */
344 |
345 | /*
346 | Function: thread_sleep
347 | Suspends the current thread for a given period.
348 |
349 | Parameters:
350 | milliseconds - Number of milliseconds to sleep.
351 | */
352 | void thread_sleep(int milliseconds);
353 |
354 | /*
355 | Function: thread_create
356 | Creates a new thread.
357 |
358 | Parameters:
359 | threadfunc - Entry point for the new thread.
360 | user - Pointer to pass to the thread.
361 |
362 | */
363 | void *thread_create(void (*threadfunc)(void *), void *user);
364 |
365 | /*
366 | Function: thread_wait
367 | Waits for a thread to be done or destroyed.
368 |
369 | Parameters:
370 | thread - Thread to wait for.
371 | */
372 | void thread_wait(void *thread);
373 |
374 | /*
375 | Function: thread_destroy
376 | Destroys a thread.
377 |
378 | Parameters:
379 | thread - Thread to destroy.
380 | */
381 | void thread_destroy(void *thread);
382 |
383 | /*
384 | Function: thread_yeild
385 | Yeild the current threads execution slice.
386 | */
387 | void thread_yield();
388 |
389 | /*
390 | Function: thread_detach
391 | Puts the thread in the detached thread, guaranteeing that
392 | resources of the thread will be freed immediately when the
393 | thread terminates.
394 |
395 | Parameters:
396 | thread - Thread to detach
397 | */
398 | void thread_detach(void *thread);
399 |
400 | /* Group: Locks */
401 | typedef void* LOCK;
402 |
403 | LOCK lock_create();
404 | void lock_destroy(LOCK lock);
405 |
406 | int lock_try(LOCK lock);
407 | void lock_wait(LOCK lock);
408 | void lock_release(LOCK lock);
409 |
410 |
411 | /* Group: Semaphores */
412 |
413 | #if !defined(CONF_PLATFORM_MACOSX)
414 | #if defined(CONF_FAMILY_UNIX)
415 | #include
416 | typedef sem_t SEMAPHORE;
417 | #elif defined(CONF_FAMILY_WINDOWS)
418 | typedef void* SEMAPHORE;
419 | #else
420 | #error missing sempahore implementation
421 | #endif
422 |
423 | void semaphore_init(SEMAPHORE *sem);
424 | void semaphore_wait(SEMAPHORE *sem);
425 | void semaphore_signal(SEMAPHORE *sem);
426 | void semaphore_destroy(SEMAPHORE *sem);
427 | #endif
428 |
429 | /* Group: Timer */
430 | #ifdef __GNUC__
431 | /* if compiled with -pedantic-errors it will complain about long
432 | not being a C90 thing.
433 | */
434 | __extension__ typedef long long int64;
435 | #else
436 | typedef long long int64;
437 | #endif
438 | /*
439 | Function: time_get
440 | Fetches a sample from a high resolution timer.
441 |
442 | Returns:
443 | Current value of the timer.
444 |
445 | Remarks:
446 | To know how fast the timer is ticking, see .
447 | */
448 | int64 time_get();
449 |
450 | /*
451 | Function: time_freq
452 | Returns the frequency of the high resolution timer.
453 |
454 | Returns:
455 | Returns the frequency of the high resolution timer.
456 | */
457 | int64 time_freq();
458 |
459 | /*
460 | Function: time_timestamp
461 | Retrives the current time as a UNIX timestamp
462 |
463 | Returns:
464 | The time as a UNIX timestamp
465 | */
466 | int time_timestamp();
467 |
468 | /* Group: Network General */
469 | typedef struct
470 | {
471 | int type;
472 | int ipv4sock;
473 | int ipv6sock;
474 | } NETSOCKET;
475 |
476 | enum
477 | {
478 | NETADDR_MAXSTRSIZE = 1+(8*4+7)+1+1+5+1, // [XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX]:XXXXX
479 |
480 | NETTYPE_INVALID = 0,
481 | NETTYPE_IPV4 = 1,
482 | NETTYPE_IPV6 = 2,
483 | NETTYPE_LINK_BROADCAST = 4,
484 | NETTYPE_ALL = NETTYPE_IPV4|NETTYPE_IPV6
485 | };
486 |
487 | typedef struct
488 | {
489 | unsigned int type;
490 | unsigned char ip[16];
491 | unsigned short port;
492 | } NETADDR;
493 |
494 | /*
495 | Function: net_init
496 | Initiates network functionallity.
497 |
498 | Returns:
499 | Returns 0 on success,
500 |
501 | Remarks:
502 | You must call this function before using any other network
503 | functions.
504 | */
505 | int net_init();
506 |
507 | /*
508 | Function: net_host_lookup
509 | Does a hostname lookup by name and fills out the passed
510 | NETADDR struct with the recieved details.
511 |
512 | Returns:
513 | 0 on success.
514 | */
515 | int net_host_lookup(const char *hostname, NETADDR *addr, int types);
516 |
517 | /*
518 | Function: net_addr_comp
519 | Compares two network addresses.
520 |
521 | Parameters:
522 | a - Address to compare
523 | b - Address to compare to.
524 |
525 | Returns:
526 | <0 - Address a is lesser then address b
527 | 0 - Address a is equal to address b
528 | >0 - Address a is greater then address b
529 | */
530 | int net_addr_comp(const NETADDR *a, const NETADDR *b);
531 |
532 | /*
533 | Function: net_addr_str
534 | Turns a network address into a representive string.
535 |
536 | Parameters:
537 | addr - Address to turn into a string.
538 | string - Buffer to fill with the string.
539 | max_length - Maximum size of the string.
540 | add_port - add port to string or not
541 |
542 | Remarks:
543 | - The string will always be zero terminated
544 |
545 | */
546 | void net_addr_str(const NETADDR *addr, char *string, int max_length, int add_port);
547 |
548 | /*
549 | Function: net_addr_from_str
550 | Turns string into a network address.
551 |
552 | Returns:
553 | 0 on success
554 |
555 | Parameters:
556 | addr - Address to fill in.
557 | string - String to parse.
558 | */
559 | int net_addr_from_str(NETADDR *addr, const char *string);
560 |
561 | /* Group: Network UDP */
562 |
563 | /*
564 | Function: net_udp_create
565 | Creates a UDP socket and binds it to a port.
566 |
567 | Parameters:
568 | bindaddr - Address to bind the socket to.
569 |
570 | Returns:
571 | On success it returns an handle to the socket. On failure it
572 | returns NETSOCKET_INVALID.
573 | */
574 | NETSOCKET net_udp_create(NETADDR bindaddr);
575 |
576 | /*
577 | Function: net_udp_send
578 | Sends a packet over an UDP socket.
579 |
580 | Parameters:
581 | sock - Socket to use.
582 | addr - Where to send the packet.
583 | data - Pointer to the packet data to send.
584 | size - Size of the packet.
585 |
586 | Returns:
587 | On success it returns the number of bytes sent. Returns -1
588 | on error.
589 | */
590 | int net_udp_send(NETSOCKET sock, const NETADDR *addr, const void *data, int size);
591 |
592 | /*
593 | Function: net_udp_recv
594 | Recives a packet over an UDP socket.
595 |
596 | Parameters:
597 | sock - Socket to use.
598 | addr - Pointer to an NETADDR that will recive the address.
599 | data - Pointer to a buffer that will recive the data.
600 | maxsize - Maximum size to recive.
601 |
602 | Returns:
603 | On success it returns the number of bytes recived. Returns -1
604 | on error.
605 | */
606 | int net_udp_recv(NETSOCKET sock, NETADDR *addr, void *data, int maxsize);
607 |
608 | /*
609 | Function: net_udp_close
610 | Closes an UDP socket.
611 |
612 | Parameters:
613 | sock - Socket to close.
614 |
615 | Returns:
616 | Returns 0 on success. -1 on error.
617 | */
618 | int net_udp_close(NETSOCKET sock);
619 |
620 |
621 | /* Group: Network TCP */
622 |
623 | /*
624 | Function: net_tcp_create
625 | Creates a TCP socket.
626 |
627 | Parameters:
628 | bindaddr - Address to bind the socket to.
629 |
630 | Returns:
631 | On success it returns an handle to the socket. On failure it returns NETSOCKET_INVALID.
632 | */
633 | NETSOCKET net_tcp_create(NETADDR bindaddr);
634 |
635 | /*
636 | Function: net_tcp_listen
637 | Makes the socket start listening for new connections.
638 |
639 | Parameters:
640 | sock - Socket to start listen to.
641 | backlog - Size of the queue of incomming connections to keep.
642 |
643 | Returns:
644 | Returns 0 on success.
645 | */
646 | int net_tcp_listen(NETSOCKET sock, int backlog);
647 |
648 | /*
649 | Function: net_tcp_accept
650 | Polls a listning socket for a new connection.
651 |
652 | Parameters:
653 | sock - Listning socket to poll.
654 | new_sock - Pointer to a socket to fill in with the new socket.
655 | addr - Pointer to an address that will be filled in the remote address (optional, can be NULL).
656 |
657 | Returns:
658 | Returns a non-negative integer on success. Negative integer on failure.
659 | */
660 | int net_tcp_accept(NETSOCKET sock, NETSOCKET *new_sock, NETADDR *addr);
661 |
662 | /*
663 | Function: net_tcp_connect
664 | Connects one socket to another.
665 |
666 | Parameters:
667 | sock - Socket to connect.
668 | addr - Address to connect to.
669 |
670 | Returns:
671 | Returns 0 on success.
672 |
673 | */
674 | int net_tcp_connect(NETSOCKET sock, const NETADDR *addr);
675 |
676 | /*
677 | Function: net_tcp_send
678 | Sends data to a TCP stream.
679 |
680 | Parameters:
681 | sock - Socket to send data to.
682 | data - Pointer to the data to send.
683 | size - Size of the data to send.
684 |
685 | Returns:
686 | Number of bytes sent. Negative value on failure.
687 | */
688 | int net_tcp_send(NETSOCKET sock, const void *data, int size);
689 |
690 | /*
691 | Function: net_tcp_recv
692 | Recvives data from a TCP stream.
693 |
694 | Parameters:
695 | sock - Socket to recvive data from.
696 | data - Pointer to a buffer to write the data to
697 | max_size - Maximum of data to write to the buffer.
698 |
699 | Returns:
700 | Number of bytes recvived. Negative value on failure. When in
701 | non-blocking mode, it returns 0 when there is no more data to
702 | be fetched.
703 | */
704 | int net_tcp_recv(NETSOCKET sock, void *data, int maxsize);
705 |
706 | /*
707 | Function: net_tcp_close
708 | Closes a TCP socket.
709 |
710 | Parameters:
711 | sock - Socket to close.
712 |
713 | Returns:
714 | Returns 0 on success. Negative value on failure.
715 | */
716 | int net_tcp_close(NETSOCKET sock);
717 |
718 | /* Group: Strings */
719 |
720 | /*
721 | Function: str_append
722 | Appends a string to another.
723 |
724 | Parameters:
725 | dst - Pointer to a buffer that contains a string.
726 | src - String to append.
727 | dst_size - Size of the buffer of the dst string.
728 |
729 | Remarks:
730 | - The strings are treated as zero-termineted strings.
731 | - Garantees that dst string will contain zero-termination.
732 | */
733 | void str_append(char *dst, const char *src, int dst_size);
734 |
735 | /*
736 | Function: str_copy
737 | Copies a string to another.
738 |
739 | Parameters:
740 | dst - Pointer to a buffer that shall recive the string.
741 | src - String to be copied.
742 | dst_size - Size of the buffer dst.
743 |
744 | Remarks:
745 | - The strings are treated as zero-termineted strings.
746 | - Garantees that dst string will contain zero-termination.
747 | */
748 | void str_copy(char *dst, const char *src, int dst_size);
749 |
750 | /*
751 | Function: str_length
752 | Returns the length of a zero terminated string.
753 |
754 | Parameters:
755 | str - Pointer to the string.
756 |
757 | Returns:
758 | Length of string in bytes excluding the zero termination.
759 | */
760 | int str_length(const char *str);
761 |
762 | /*
763 | Function: str_format
764 | Performs printf formating into a buffer.
765 |
766 | Parameters:
767 | buffer - Pointer to the buffer to recive the formated string.
768 | buffer_size - Size of the buffer.
769 | format - printf formating string.
770 | ... - Parameters for the formating.
771 |
772 | Remarks:
773 | - See the C manual for syntax for the printf formating string.
774 | - The strings are treated as zero-termineted strings.
775 | - Garantees that dst string will contain zero-termination.
776 | */
777 | void str_format(char *buffer, int buffer_size, const char *format, ...);
778 |
779 | /*
780 | Function: str_sanitize_strong
781 | Replaces all characters below 32 and above 127 with whitespace.
782 |
783 | Parameters:
784 | str - String to sanitize.
785 |
786 | Remarks:
787 | - The strings are treated as zero-termineted strings.
788 | */
789 | void str_sanitize_strong(char *str);
790 |
791 | /*
792 | Function: str_sanitize_cc
793 | Replaces all characters below 32 with whitespace.
794 |
795 | Parameters:
796 | str - String to sanitize.
797 |
798 | Remarks:
799 | - The strings are treated as zero-termineted strings.
800 | */
801 | void str_sanitize_cc(char *str);
802 |
803 | /*
804 | Function: str_sanitize
805 | Replaces all characters below 32 with whitespace with
806 | exception to \t, \n and \r.
807 |
808 | Parameters:
809 | str - String to sanitize.
810 |
811 | Remarks:
812 | - The strings are treated as zero-termineted strings.
813 | */
814 | void str_sanitize(char *str);
815 |
816 | /*
817 | Function: str_skip_to_whitespace
818 | Skips leading non-whitespace characters(all but ' ', '\t', '\n', '\r').
819 |
820 | Parameters:
821 | str - Pointer to the string.
822 |
823 | Returns:
824 | Pointer to the first whitespace character found
825 | within the string.
826 |
827 | Remarks:
828 | - The strings are treated as zero-termineted strings.
829 | */
830 | char *str_skip_to_whitespace(char *str);
831 |
832 | /*
833 | Function: str_skip_whitespaces
834 | Skips leading whitespace characters(' ', '\t', '\n', '\r').
835 |
836 | Parameters:
837 | str - Pointer to the string.
838 |
839 | Returns:
840 | Pointer to the first non-whitespace character found
841 | within the string.
842 |
843 | Remarks:
844 | - The strings are treated as zero-termineted strings.
845 | */
846 | char *str_skip_whitespaces(char *str);
847 |
848 | /*
849 | Function: str_comp_nocase
850 | Compares to strings case insensitive.
851 |
852 | Parameters:
853 | a - String to compare.
854 | b - String to compare.
855 |
856 | Returns:
857 | <0 - String a is lesser then string b
858 | 0 - String a is equal to string b
859 | >0 - String a is greater then string b
860 |
861 | Remarks:
862 | - Only garanted to work with a-z/A-Z.
863 | - The strings are treated as zero-termineted strings.
864 | */
865 | int str_comp_nocase(const char *a, const char *b);
866 |
867 | /*
868 | Function: str_comp_nocase_num
869 | Compares up to num characters of two strings case insensitive.
870 |
871 | Parameters:
872 | a - String to compare.
873 | b - String to compare.
874 | num - Maximum characters to compare
875 |
876 | Returns:
877 | <0 - String a is lesser than string b
878 | 0 - String a is equal to string b
879 | >0 - String a is greater than string b
880 |
881 | Remarks:
882 | - Only garanted to work with a-z/A-Z.
883 | - The strings are treated as zero-termineted strings.
884 | */
885 | int str_comp_nocase_num(const char *a, const char *b, const int num);
886 |
887 | /*
888 | Function: str_comp
889 | Compares to strings case sensitive.
890 |
891 | Parameters:
892 | a - String to compare.
893 | b - String to compare.
894 |
895 | Returns:
896 | <0 - String a is lesser then string b
897 | 0 - String a is equal to string b
898 | >0 - String a is greater then string b
899 |
900 | Remarks:
901 | - The strings are treated as zero-termineted strings.
902 | */
903 | int str_comp(const char *a, const char *b);
904 |
905 | /*
906 | Function: str_comp_num
907 | Compares up to num characters of two strings case sensitive.
908 |
909 | Parameters:
910 | a - String to compare.
911 | b - String to compare.
912 | num - Maximum characters to compare
913 |
914 | Returns:
915 | <0 - String a is lesser then string b
916 | 0 - String a is equal to string b
917 | >0 - String a is greater then string b
918 |
919 | Remarks:
920 | - The strings are treated as zero-termineted strings.
921 | */
922 | int str_comp_num(const char *a, const char *b, const int num);
923 |
924 | /*
925 | Function: str_comp_filenames
926 | Compares two strings case sensitive, digit chars will be compared as numbers.
927 |
928 | Parameters:
929 | a - String to compare.
930 | b - String to compare.
931 |
932 | Returns:
933 | <0 - String a is lesser then string b
934 | 0 - String a is equal to string b
935 | >0 - String a is greater then string b
936 |
937 | Remarks:
938 | - The strings are treated as zero-termineted strings.
939 | */
940 | int str_comp_filenames(const char *a, const char *b);
941 |
942 | /*
943 | Function: str_find_nocase
944 | Finds a string inside another string case insensitive.
945 |
946 | Parameters:
947 | haystack - String to search in
948 | needle - String to search for
949 |
950 | Returns:
951 | A pointer into haystack where the needle was found.
952 | Returns NULL of needle could not be found.
953 |
954 | Remarks:
955 | - Only garanted to work with a-z/A-Z.
956 | - The strings are treated as zero-termineted strings.
957 | */
958 | const char *str_find_nocase(const char *haystack, const char *needle);
959 |
960 | /*
961 | Function: str_find
962 | Finds a string inside another string case sensitive.
963 |
964 | Parameters:
965 | haystack - String to search in
966 | needle - String to search for
967 |
968 | Returns:
969 | A pointer into haystack where the needle was found.
970 | Returns NULL of needle could not be found.
971 |
972 | Remarks:
973 | - The strings are treated as zero-termineted strings.
974 | */
975 | const char *str_find(const char *haystack, const char *needle);
976 |
977 | /*
978 | Function: str_hex
979 | Takes a datablock and generates a hexstring of it.
980 |
981 | Parameters:
982 | dst - Buffer to fill with hex data
983 | dst_size - size of the buffer
984 | data - Data to turn into hex
985 | data - Size of the data
986 |
987 | Remarks:
988 | - The desination buffer will be zero-terminated
989 | */
990 | void str_hex(char *dst, int dst_size, const void *data, int data_size);
991 |
992 | /*
993 | Function: str_timestamp
994 | Copies a time stamp in the format year-month-day_hour-minute-second to the string.
995 |
996 | Parameters:
997 | buffer - Pointer to a buffer that shall receive the time stamp string.
998 | buffer_size - Size of the buffer.
999 |
1000 | Remarks:
1001 | - Guarantees that buffer string will contain zero-termination.
1002 | */
1003 | void str_timestamp(char *buffer, int buffer_size);
1004 |
1005 | /* Group: Filesystem */
1006 |
1007 | /*
1008 | Function: fs_listdir
1009 | Lists the files in a directory
1010 |
1011 | Parameters:
1012 | dir - Directory to list
1013 | cb - Callback function to call for each entry
1014 | type - Type of the directory
1015 | user - Pointer to give to the callback
1016 |
1017 | Returns:
1018 | Always returns 0.
1019 | */
1020 | typedef int (*FS_LISTDIR_CALLBACK)(const char *name, int is_dir, int dir_type, void *user);
1021 | int fs_listdir(const char *dir, FS_LISTDIR_CALLBACK cb, int type, void *user);
1022 |
1023 | /*
1024 | Function: fs_makedir
1025 | Creates a directory
1026 |
1027 | Parameters:
1028 | path - Directory to create
1029 |
1030 | Returns:
1031 | Returns 0 on success. Negative value on failure.
1032 |
1033 | Remarks:
1034 | Does not create several directories if needed. "a/b/c" will result
1035 | in a failure if b or a does not exist.
1036 | */
1037 | int fs_makedir(const char *path);
1038 |
1039 | /*
1040 | Function: fs_storage_path
1041 | Fetches per user configuration directory.
1042 |
1043 | Returns:
1044 | Returns 0 on success. Negative value on failure.
1045 |
1046 | Remarks:
1047 | - Returns ~/.appname on UNIX based systems
1048 | - Returns ~/Library/Applications Support/appname on Mac OS X
1049 | - Returns %APPDATA%/Appname on Windows based systems
1050 | */
1051 | int fs_storage_path(const char *appname, char *path, int max);
1052 |
1053 | /*
1054 | Function: fs_is_dir
1055 | Checks if directory exists
1056 |
1057 | Returns:
1058 | Returns 1 on success, 0 on failure.
1059 | */
1060 | int fs_is_dir(const char *path);
1061 |
1062 | /*
1063 | Function: fs_chdir
1064 | Changes current working directory
1065 |
1066 | Returns:
1067 | Returns 0 on success, 1 on failure.
1068 | */
1069 | int fs_chdir(const char *path);
1070 |
1071 | /*
1072 | Function: fs_getcwd
1073 | Gets the current working directory.
1074 |
1075 | Returns:
1076 | Returns a pointer to the buffer on success, 0 on failure.
1077 | */
1078 | char *fs_getcwd(char *buffer, int buffer_size);
1079 |
1080 | /*
1081 | Function: fs_parent_dir
1082 | Get the parent directory of a directory
1083 |
1084 | Parameters:
1085 | path - The directory string
1086 |
1087 | Returns:
1088 | Returns 0 on success, 1 on failure.
1089 |
1090 | Remarks:
1091 | - The string is treated as zero-termineted string.
1092 | */
1093 | int fs_parent_dir(char *path);
1094 |
1095 | /*
1096 | Function: fs_remove
1097 | Deletes the file with the specified name.
1098 |
1099 | Parameters:
1100 | filename - The file to delete
1101 |
1102 | Returns:
1103 | Returns 0 on success, 1 on failure.
1104 |
1105 | Remarks:
1106 | - The strings are treated as zero-terminated strings.
1107 | */
1108 | int fs_remove(const char *filename);
1109 |
1110 | /*
1111 | Function: fs_rename
1112 | Renames the file or directory. If the paths differ the file will be moved.
1113 |
1114 | Parameters:
1115 | oldname - The actual name
1116 | newname - The new name
1117 |
1118 | Returns:
1119 | Returns 0 on success, 1 on failure.
1120 |
1121 | Remarks:
1122 | - The strings are treated as zero-terminated strings.
1123 | */
1124 | int fs_rename(const char *oldname, const char *newname);
1125 |
1126 | /*
1127 | Group: Undocumented
1128 | */
1129 |
1130 |
1131 | /*
1132 | Function: net_tcp_connect_non_blocking
1133 |
1134 | DOCTODO: serp
1135 | */
1136 | int net_tcp_connect_non_blocking(NETSOCKET sock, NETADDR bindaddr);
1137 |
1138 | /*
1139 | Function: net_set_non_blocking
1140 |
1141 | DOCTODO: serp
1142 | */
1143 | int net_set_non_blocking(NETSOCKET sock);
1144 |
1145 | /*
1146 | Function: net_set_non_blocking
1147 |
1148 | DOCTODO: serp
1149 | */
1150 | int net_set_blocking(NETSOCKET sock);
1151 |
1152 | /*
1153 | Function: net_errno
1154 |
1155 | DOCTODO: serp
1156 | */
1157 | int net_errno();
1158 |
1159 | /*
1160 | Function: net_would_block
1161 |
1162 | DOCTODO: serp
1163 | */
1164 | int net_would_block();
1165 |
1166 | int net_socket_read_wait(NETSOCKET sock, int time);
1167 |
1168 | void mem_debug_dump(IOHANDLE file);
1169 |
1170 | void swap_endian(void *data, unsigned elem_size, unsigned num);
1171 |
1172 |
1173 | typedef void (*DBG_LOGGER)(const char *line);
1174 | void dbg_logger(DBG_LOGGER logger);
1175 |
1176 | void dbg_logger_stdout();
1177 | void dbg_logger_debugger();
1178 | void dbg_logger_file(const char *filename);
1179 |
1180 | typedef struct
1181 | {
1182 | int allocated;
1183 | int active_allocations;
1184 | int total_allocations;
1185 | } MEMSTATS;
1186 |
1187 | const MEMSTATS *mem_stats();
1188 |
1189 | typedef struct
1190 | {
1191 | int sent_packets;
1192 | int sent_bytes;
1193 | int recv_packets;
1194 | int recv_bytes;
1195 | } NETSTATS;
1196 |
1197 |
1198 | void net_stats(NETSTATS *stats);
1199 |
1200 | int str_toint(const char *str);
1201 | float str_tofloat(const char *str);
1202 | int str_isspace(char c);
1203 | char str_uppercase(char c);
1204 | unsigned str_quickhash(const char *str);
1205 |
1206 | /*
1207 | Function: gui_messagebox
1208 | Display plain OS-dependent message box
1209 |
1210 | Parameters:
1211 | title - title of the message box
1212 | message - text to display
1213 | */
1214 | void gui_messagebox(const char *title, const char *message);
1215 |
1216 |
1217 | /*
1218 | Function: str_utf8_rewind
1219 | Moves a cursor backwards in an utf8 string
1220 |
1221 | Parameters:
1222 | str - utf8 string
1223 | cursor - position in the string
1224 |
1225 | Returns:
1226 | New cursor position.
1227 |
1228 | Remarks:
1229 | - Won't move the cursor less then 0
1230 | */
1231 | int str_utf8_rewind(const char *str, int cursor);
1232 |
1233 | /*
1234 | Function: str_utf8_forward
1235 | Moves a cursor forwards in an utf8 string
1236 |
1237 | Parameters:
1238 | str - utf8 string
1239 | cursor - position in the string
1240 |
1241 | Returns:
1242 | New cursor position.
1243 |
1244 | Remarks:
1245 | - Won't move the cursor beyond the zero termination marker
1246 | */
1247 | int str_utf8_forward(const char *str, int cursor);
1248 |
1249 | /*
1250 | Function: str_utf8_decode
1251 | Decodes an utf8 character
1252 |
1253 | Parameters:
1254 | ptr - pointer to an utf8 string. this pointer will be moved forward
1255 |
1256 | Returns:
1257 | Unicode value for the character. -1 for invalid characters and 0 for end of string.
1258 |
1259 | Remarks:
1260 | - This function will also move the pointer forward.
1261 | */
1262 | int str_utf8_decode(const char **ptr);
1263 |
1264 | /*
1265 | Function: str_utf8_encode
1266 | Encode an utf8 character
1267 |
1268 | Parameters:
1269 | ptr - Pointer to a buffer that should recive the data. Should be able to hold at least 4 bytes.
1270 |
1271 | Returns:
1272 | Number of bytes put into the buffer.
1273 |
1274 | Remarks:
1275 | - Does not do zero termination of the string.
1276 | */
1277 | int str_utf8_encode(char *ptr, int chr);
1278 |
1279 | /*
1280 | Function: str_utf8_check
1281 | Checks if a strings contains just valid utf8 characters.
1282 |
1283 | Parameters:
1284 | str - Pointer to a possible utf8 string.
1285 |
1286 | Returns:
1287 | 0 - invalid characters found.
1288 | 1 - only valid characters found.
1289 |
1290 | Remarks:
1291 | - The string is treated as zero-terminated utf8 string.
1292 | */
1293 | int str_utf8_check(const char *str);
1294 |
1295 | #ifdef __cplusplus
1296 | }
1297 | #endif
1298 |
1299 | #endif
1300 |
--------------------------------------------------------------------------------