├── 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 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 | 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 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
协议月流量 ↓|↑节点虚拟化位置在线负载当前网络 ↓|↑总流量 ↓|↑CPU内存硬盘联通|电信|移动
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 | 93 | 94 | 95 | 96 | 97 |
名称域名端口剩余(天)到期(UTC)状态
98 |
99 | 100 | 101 |
102 |
103 | 104 | 105 | 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 | [![Python Support](https://img.shields.io/badge/python-3.6%2B%20-blue.svg)](https://github.com/cppla/ServerStatus) 7 | [![C++ Compiler](http://img.shields.io/badge/C++-GNU-blue.svg?style=flat&logo=cplusplus)](https://github.com/cppla/ServerStatus) 8 | [![License](https://img.shields.io/badge/license-MIT-4EB1BA.svg?style=flat-square)](https://github.com/cppla/ServerStatus) 9 | [![Version](https://img.shields.io/badge/Version-Build%201.1.7-red)](https://github.com/cppla/ServerStatus) 10 | 11 | ![Latest Host Version](https://dl.cpp.la/Archive/serverstatus_1_1_7.png) 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 | --------------------------------------------------------------------------------