mirror of
https://github.com//cppla/ServerStatus
synced 2025-05-16 13:29:49 +08:00
MMP, Cancel fork, because i do not know how to merge to the original foreign author
This commit is contained in:
parent
1c0e49a137
commit
97635546ab
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
default.sublime-workspace
|
109
README.md
Normal file
109
README.md
Normal file
@ -0,0 +1,109 @@
|
||||
# ServerStatus中文版:
|
||||
|
||||
* ServerStatus中文版是一个酷炫高逼格的云探针、云监控、服务器云监控、多服务器探针~。
|
||||
* 在线演示:https://tz.cloudcpp.com
|
||||
|
||||
# 目录介绍:
|
||||
|
||||
* clients 客户端文件
|
||||
* server 服务端文件
|
||||
* web 网站文件
|
||||
|
||||
# 更新说明:
|
||||
|
||||
* 20180314, 调整前端,置默认密码为,设置ip和user即可上线
|
||||
* 20180312, 加入失联(被照顾)检测【正常:MH361, 屏蔽:MH370】,校准虚拟化(container)流量统计异常
|
||||
* 20170807, 更新平均1,5,15负载
|
||||
* 20170607, 去掉无用的IPV6信息,增加服务器总流量监控
|
||||
|
||||
# 安装教程:
|
||||
|
||||
【克隆代码】:
|
||||
```
|
||||
git clone https://github.com/cppla/ServerStatus.git
|
||||
```
|
||||
|
||||
【服务端配置】(服务端程序在ServerStatus/web下):
|
||||
|
||||
一、生成服务端程序
|
||||
```
|
||||
cd ServerStatus/server
|
||||
make
|
||||
./sergate
|
||||
```
|
||||
如果没错误提示,OK,ctrl+c关闭;如果有错误提示,检查35601端口是否被占用
|
||||
|
||||
二、修改配置文件
|
||||
修改config.json文件,注意username, password的值需要和客户端对应一致
|
||||
```
|
||||
{"servers":
|
||||
[
|
||||
{
|
||||
"username": "s01",
|
||||
"name": "Mainserver 1",
|
||||
"type": "Dedicated Server",
|
||||
"host": "GenericServerHost123",
|
||||
"location": "Austria",
|
||||
"password": "some-hard-to-guess-copy-paste-password"
|
||||
},
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
三、拷贝ServerStatus/status到你的网站目录
|
||||
例如:
|
||||
```
|
||||
sudo cp -r ServerStatus/web/* /home/wwwroot/default
|
||||
```
|
||||
|
||||
四、运行服务端:
|
||||
web-dir参数为上一步设置的网站根目录,务必修改成自己网站的路径
|
||||
```
|
||||
./sergate --config=config.json --web-dir=/home/wwwroot/default
|
||||
```
|
||||
|
||||
【客户端配置】(客户端程序在ServerStatus/clients下):
|
||||
客户端有两个版本,client-linux为普通linux,client-psutil为跨平台版,普通版不成功,换成跨平台版即可。
|
||||
|
||||
一、client-linux版配置:
|
||||
1、vim client-linux.py, 修改SERVER地址,username帐号, password密码
|
||||
2、python client-linux.py 运行即可。
|
||||
|
||||
二、client-psutil版配置:
|
||||
1、安装psutil跨平台依赖库
|
||||
2、vim client-psutil.py, 修改SERVER地址,username帐号, password密码
|
||||
3、python client-psutil.py 运行即可。
|
||||
```
|
||||
### for Centos:
|
||||
sudo yum -y install epel-release
|
||||
sudo yum -y install python-pip
|
||||
sudo yum clean all
|
||||
sudo yum -y install gcc
|
||||
sudo yum -y install python-devel
|
||||
sudo pip install psutil
|
||||
### for Ubuntu/Debian:
|
||||
sudo root
|
||||
apt-get -y install python-setuptools python-dev build-essential
|
||||
apt-get -y install python-pip
|
||||
pip install psutil
|
||||
### for Windows:
|
||||
打开网址:https://pypi.python.org/pypi?:action=display&name=psutil#downloads
|
||||
下载psutil for windows程序包
|
||||
安装即可
|
||||
```
|
||||
|
||||
打开云探针页面,就可以正常的监控。接下来把服务器和客户端脚本自行加入开机启动,或者进程守护,或以后台方式运行即可!例如: nohup python client-linux.py &
|
||||
|
||||
# 为什么会有ServerStatus中文版:
|
||||
|
||||
* 有些功能确实没用
|
||||
* 原版本部署,英文说明复杂
|
||||
* 不符合中文版的习惯
|
||||
* 没有一次又一次的轮子,哪来如此优秀的云探针
|
||||
|
||||
# 相关开源项目,感谢:
|
||||
|
||||
* ServerStatus:https://github.com/BotoX/ServerStatus
|
||||
* mojeda: https://github.com/mojeda
|
||||
* mojeda's ServerStatus: https://github.com/mojeda/ServerStatus
|
||||
* BlueVM's project: http://www.lowendtalk.com/discussion/comment/169690#Comment_169690
|
240
clients/client-linux.py
Executable file
240
clients/client-linux.py
Executable file
@ -0,0 +1,240 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Update by : https://github.com/cppla/ServerStatus
|
||||
# 支持Python版本:2.7 to 3.5
|
||||
# 支持操作系统: Linux, OSX, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures
|
||||
# 时间: 20180312
|
||||
|
||||
|
||||
SERVER = "127.0.0.1"
|
||||
PORT = 35601
|
||||
USER = "s01"
|
||||
PASSWORD = "USER_DEFAULT_PASSWORD"
|
||||
INTERVAL = 1 #更新间隔
|
||||
|
||||
|
||||
import socket
|
||||
import time
|
||||
import string
|
||||
import math
|
||||
import re
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import collections
|
||||
|
||||
def get_uptime():
|
||||
f = open('/proc/uptime', 'r')
|
||||
uptime = f.readline()
|
||||
f.close()
|
||||
uptime = uptime.split('.', 2)
|
||||
time = int(uptime[0])
|
||||
return int(time)
|
||||
|
||||
def get_memory():
|
||||
re_parser = re.compile(r'^(?P<key>\S*):\s*(?P<value>\d*)\s*kB')
|
||||
result = dict()
|
||||
for line in open('/proc/meminfo'):
|
||||
match = re_parser.match(line)
|
||||
if not match:
|
||||
continue;
|
||||
key, value = match.groups(['key', 'value'])
|
||||
result[key] = int(value)
|
||||
|
||||
MemTotal = float(result['MemTotal'])
|
||||
MemFree = float(result['MemFree'])
|
||||
Cached = float(result['Cached'])
|
||||
MemUsed = MemTotal - (Cached + MemFree)
|
||||
SwapTotal = float(result['SwapTotal'])
|
||||
SwapFree = float(result['SwapFree'])
|
||||
return int(MemTotal), int(MemUsed), int(SwapTotal), int(SwapFree)
|
||||
|
||||
def get_hdd():
|
||||
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")
|
||||
total = p.splitlines()[-1]
|
||||
used = total.split()[3]
|
||||
size = total.split()[2]
|
||||
return int(size), int(used)
|
||||
|
||||
def get_time():
|
||||
stat_file = file("/proc/stat", "r")
|
||||
time_list = stat_file.readline().split(' ')[2:6]
|
||||
stat_file.close()
|
||||
for i in range(len(time_list)) :
|
||||
time_list[i] = int(time_list[i])
|
||||
return time_list
|
||||
def delta_time():
|
||||
x = get_time()
|
||||
time.sleep(INTERVAL)
|
||||
y = get_time()
|
||||
for i in range(len(x)):
|
||||
y[i]-=x[i]
|
||||
return y
|
||||
def get_cpu():
|
||||
t = delta_time()
|
||||
st = sum(t)
|
||||
if st == 0:
|
||||
st = 1
|
||||
result = 100-(t[len(t)-1]*100.00/st)
|
||||
return round(result)
|
||||
|
||||
class Traffic:
|
||||
def __init__(self):
|
||||
self.rx = collections.deque(maxlen=10)
|
||||
self.tx = collections.deque(maxlen=10)
|
||||
def get(self):
|
||||
f = open('/proc/net/dev', 'r')
|
||||
net_dev = f.readlines()
|
||||
f.close()
|
||||
avgrx = 0; avgtx = 0
|
||||
|
||||
for dev in net_dev[2:]:
|
||||
dev = dev.split(':')
|
||||
if dev[0].strip() == "lo" or dev[0].find("tun") > -1 \
|
||||
or dev[0].find("docker") > -1 or dev[0].find("veth") > -1 \
|
||||
or dev[0].find("br-") > -1:
|
||||
continue
|
||||
dev = dev[1].split()
|
||||
avgrx += int(dev[0])
|
||||
avgtx += int(dev[8])
|
||||
|
||||
self.rx.append(avgrx)
|
||||
self.tx.append(avgtx)
|
||||
avgrx = 0; avgtx = 0
|
||||
|
||||
l = len(self.rx)
|
||||
for x in range(l - 1):
|
||||
avgrx += self.rx[x+1] - self.rx[x]
|
||||
avgtx += self.tx[x+1] - self.tx[x]
|
||||
|
||||
avgrx = int(avgrx / l / INTERVAL)
|
||||
avgtx = int(avgtx / l / INTERVAL)
|
||||
|
||||
return avgrx, avgtx
|
||||
|
||||
def liuliang():
|
||||
NET_IN = 0
|
||||
NET_OUT = 0
|
||||
with open('/proc/net/dev') as f:
|
||||
for line in f.readlines():
|
||||
netinfo = re.findall('([^\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)
|
||||
if netinfo:
|
||||
if netinfo[0][0] == 'lo' or 'tun' in netinfo[0][0] \
|
||||
or 'docker' in netinfo[0][0] or 'veth' in netinfo[0][0] \
|
||||
or 'br-' in netinfo[0][0] \
|
||||
or netinfo[0][1]=='0' or netinfo[0][9]=='0':
|
||||
continue
|
||||
else:
|
||||
NET_IN += int(netinfo[0][1])
|
||||
NET_OUT += int(netinfo[0][9])
|
||||
return NET_IN, NET_OUT
|
||||
|
||||
# todo: 不确定是否要用多线程or多进程: 效率? 资源?
|
||||
def ip_status():
|
||||
object_check = ['www.10010.com', 'www.189.cn', 'www.10086.cn']
|
||||
ip_check = 0
|
||||
for i in object_check:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(1)
|
||||
try:
|
||||
s.connect((i, 80))
|
||||
except:
|
||||
ip_check += 1
|
||||
s.close()
|
||||
del s
|
||||
if ip_check >= 2:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def get_network(ip_version):
|
||||
if(ip_version == 4):
|
||||
HOST = "ipv4.google.com"
|
||||
elif(ip_version == 6):
|
||||
HOST = "ipv6.google.com"
|
||||
try:
|
||||
s = socket.create_connection((HOST, 80), 2)
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
if __name__ == '__main__':
|
||||
socket.setdefaulttimeout(30)
|
||||
while 1:
|
||||
try:
|
||||
print("Connecting...")
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect((SERVER, PORT))
|
||||
data = s.recv(1024)
|
||||
if data.find("Authentication required") > -1:
|
||||
s.send(USER + ':' + PASSWORD + '\n')
|
||||
data = s.recv(1024)
|
||||
if data.find("Authentication successful") < 0:
|
||||
print(data)
|
||||
raise socket.error
|
||||
else:
|
||||
print(data)
|
||||
raise socket.error
|
||||
|
||||
print(data)
|
||||
data = s.recv(1024)
|
||||
print(data)
|
||||
|
||||
timer = 0
|
||||
check_ip = 0
|
||||
if data.find("IPv4") > -1:
|
||||
check_ip = 6
|
||||
elif data.find("IPv6") > -1:
|
||||
check_ip = 4
|
||||
else:
|
||||
print(data)
|
||||
raise socket.error
|
||||
|
||||
traffic = Traffic()
|
||||
traffic.get()
|
||||
while 1:
|
||||
CPU = get_cpu()
|
||||
NetRx, NetTx = traffic.get()
|
||||
NET_IN, NET_OUT = liuliang()
|
||||
Uptime = get_uptime()
|
||||
Load_1, Load_5, Load_15 = os.getloadavg()
|
||||
MemoryTotal, MemoryUsed, SwapTotal, SwapFree = get_memory()
|
||||
HDDTotal, HDDUsed = get_hdd()
|
||||
IP_STATUS = ip_status()
|
||||
|
||||
array = {}
|
||||
if not timer:
|
||||
array['online' + str(check_ip)] = get_network(check_ip)
|
||||
timer = 10
|
||||
else:
|
||||
timer -= 1*INTERVAL
|
||||
|
||||
array['uptime'] = Uptime
|
||||
array['load_1'] = Load_1
|
||||
array['load_5'] = Load_5
|
||||
array['load_15'] = Load_15
|
||||
array['memory_total'] = MemoryTotal
|
||||
array['memory_used'] = MemoryUsed
|
||||
array['swap_total'] = SwapTotal
|
||||
array['swap_used'] = SwapTotal - SwapFree
|
||||
array['hdd_total'] = HDDTotal
|
||||
array['hdd_used'] = HDDUsed
|
||||
array['cpu'] = CPU
|
||||
array['network_rx'] = NetRx
|
||||
array['network_tx'] = NetTx
|
||||
array['network_in'] = NET_IN
|
||||
array['network_out'] = NET_OUT
|
||||
array['ip_status'] = IP_STATUS
|
||||
|
||||
s.send("update " + json.dumps(array) + "\n")
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except socket.error:
|
||||
print("Disconnected...")
|
||||
# keep on trying after a disconnect
|
||||
s.close()
|
||||
time.sleep(3)
|
||||
except Exception as e:
|
||||
print("Caught Exception:", e)
|
||||
s.close()
|
||||
time.sleep(3)
|
209
clients/client-psutil.py
Executable file
209
clients/client-psutil.py
Executable file
@ -0,0 +1,209 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Update by : https://github.com/cppla/ServerStatus
|
||||
# 依赖于psutil跨平台库:
|
||||
# 支持Python版本:2.6 to 3.5 (users of Python 2.4 and 2.5 may use 2.1.3 version)
|
||||
# 支持操作系统: Linux, Windows, OSX, Sun Solaris, FreeBSD, OpenBSD and NetBSD, both 32-bit and 64-bit architectures
|
||||
# 时间: 20180312
|
||||
|
||||
SERVER = "127.0.0.1"
|
||||
PORT = 35601
|
||||
USER = "s01"
|
||||
PASSWORD = "USER_DEFAULT_PASSWORD"
|
||||
INTERVAL = 1 # 更新间隔
|
||||
|
||||
|
||||
import socket
|
||||
import time
|
||||
import string
|
||||
import math
|
||||
import os
|
||||
import json
|
||||
import collections
|
||||
import psutil
|
||||
import sys
|
||||
|
||||
def get_uptime():
|
||||
return int(time.time() - psutil.boot_time())
|
||||
|
||||
def get_memory():
|
||||
Mem = psutil.virtual_memory()
|
||||
try:
|
||||
MemUsed = Mem.total - (Mem.cached + Mem.free)
|
||||
except:
|
||||
MemUsed = Mem.total - Mem.free
|
||||
return int(Mem.total/1024.0), int(MemUsed/1024.0)
|
||||
|
||||
def get_swap():
|
||||
Mem = psutil.swap_memory()
|
||||
return int(Mem.total/1024.0), int(Mem.used/1024.0)
|
||||
|
||||
def get_hdd():
|
||||
valid_fs = [ "ext4", "ext3", "ext2", "reiserfs", "jfs", "btrfs", "fuseblk", "zfs", "simfs", "ntfs", "fat32", "exfat", "xfs" ]
|
||||
disks = dict()
|
||||
size = 0
|
||||
used = 0
|
||||
for disk in psutil.disk_partitions():
|
||||
if not disk.device in disks and disk.fstype.lower() in valid_fs:
|
||||
disks[disk.device] = disk.mountpoint
|
||||
for disk in disks.itervalues():
|
||||
usage = psutil.disk_usage(disk)
|
||||
size += usage.total
|
||||
used += usage.used
|
||||
return int(size/1024.0/1024.0), int(used/1024.0/1024.0)
|
||||
|
||||
def get_cpu():
|
||||
return psutil.cpu_percent(interval=INTERVAL)
|
||||
|
||||
class Traffic:
|
||||
def __init__(self):
|
||||
self.rx = collections.deque(maxlen=10)
|
||||
self.tx = collections.deque(maxlen=10)
|
||||
def get(self):
|
||||
avgrx = 0; avgtx = 0
|
||||
for name, stats in psutil.net_io_counters(pernic=True).iteritems():
|
||||
if name == "lo" or name.find("tun") > -1 \
|
||||
or name.find("docker") > -1 or name.find("veth") > -1 \
|
||||
or name.find("br-") > -1:
|
||||
continue
|
||||
avgrx += stats.bytes_recv
|
||||
avgtx += stats.bytes_sent
|
||||
|
||||
self.rx.append(avgrx)
|
||||
self.tx.append(avgtx)
|
||||
avgrx = 0; avgtx = 0
|
||||
|
||||
l = len(self.rx)
|
||||
for x in range(l - 1):
|
||||
avgrx += self.rx[x+1] - self.rx[x]
|
||||
avgtx += self.tx[x+1] - self.tx[x]
|
||||
|
||||
avgrx = int(avgrx / l / INTERVAL)
|
||||
avgtx = int(avgtx / l / INTERVAL)
|
||||
|
||||
return avgrx, avgtx
|
||||
|
||||
def liuliang():
|
||||
NET_IN = 0
|
||||
NET_OUT = 0
|
||||
net = psutil.net_io_counters(pernic=True)
|
||||
for k, v in net.items():
|
||||
if k == 'lo' or 'tun' in k \
|
||||
or 'br-' in k \
|
||||
or 'docker' in k or 'veth' in k:
|
||||
continue
|
||||
else:
|
||||
NET_IN += v[1]
|
||||
NET_OUT += v[0]
|
||||
return NET_IN, NET_OUT
|
||||
|
||||
# todo: 不确定是否要用多线程or多进程: 效率? 资源?
|
||||
def ip_status():
|
||||
object_check = ['www.10010.com', 'www.189.cn', 'www.10086.cn']
|
||||
ip_check = 0
|
||||
for i in object_check:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(1)
|
||||
try:
|
||||
s.connect((i, 80))
|
||||
except:
|
||||
ip_check += 1
|
||||
s.close()
|
||||
del s
|
||||
if ip_check >= 2:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def get_network(ip_version):
|
||||
if(ip_version == 4):
|
||||
HOST = "ipv4.google.com"
|
||||
elif(ip_version == 6):
|
||||
HOST = "ipv6.google.com"
|
||||
try:
|
||||
s = socket.create_connection((HOST, 80), 2)
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
if __name__ == '__main__':
|
||||
socket.setdefaulttimeout(30)
|
||||
while 1:
|
||||
try:
|
||||
print("Connecting...")
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect((SERVER, PORT))
|
||||
data = s.recv(1024)
|
||||
if data.find("Authentication required") > -1:
|
||||
s.send(USER + ':' + PASSWORD + '\n')
|
||||
data = s.recv(1024)
|
||||
if data.find("Authentication successful") < 0:
|
||||
print(data)
|
||||
raise socket.error
|
||||
else:
|
||||
print(data)
|
||||
raise socket.error
|
||||
|
||||
print(data)
|
||||
data = s.recv(1024)
|
||||
print(data)
|
||||
|
||||
timer = 0
|
||||
check_ip = 0
|
||||
if data.find("IPv4") > -1:
|
||||
check_ip = 6
|
||||
elif data.find("IPv6") > -1:
|
||||
check_ip = 4
|
||||
else:
|
||||
print(data)
|
||||
raise socket.error
|
||||
|
||||
traffic = Traffic()
|
||||
traffic.get()
|
||||
while 1:
|
||||
CPU = get_cpu()
|
||||
NetRx, NetTx = traffic.get()
|
||||
NET_IN, NET_OUT = liuliang()
|
||||
Uptime = get_uptime()
|
||||
Load_1, Load_5, Load_15 = os.getloadavg() if 'linux' in sys.platform else (0.0, 0.0, 0.0)
|
||||
MemoryTotal, MemoryUsed = get_memory()
|
||||
SwapTotal, SwapUsed = get_swap()
|
||||
HDDTotal, HDDUsed = get_hdd()
|
||||
IP_STATUS = ip_status()
|
||||
|
||||
array = {}
|
||||
if not timer:
|
||||
array['online' + str(check_ip)] = get_network(check_ip)
|
||||
timer = 10
|
||||
else:
|
||||
timer -= 1*INTERVAL
|
||||
|
||||
array['uptime'] = Uptime
|
||||
array['load_1'] = Load_1
|
||||
array['load_5'] = Load_5
|
||||
array['load_15'] = Load_15
|
||||
array['memory_total'] = MemoryTotal
|
||||
array['memory_used'] = MemoryUsed
|
||||
array['swap_total'] = SwapTotal
|
||||
array['swap_used'] = SwapUsed
|
||||
array['hdd_total'] = HDDTotal
|
||||
array['hdd_used'] = HDDUsed
|
||||
array['cpu'] = CPU
|
||||
array['network_rx'] = NetRx
|
||||
array['network_tx'] = NetTx
|
||||
array['network_in'] = NET_IN
|
||||
array['network_out'] = NET_OUT
|
||||
array['ip_status'] = IP_STATUS
|
||||
|
||||
s.send("update " + json.dumps(array) + "\n")
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except socket.error:
|
||||
print("Disconnected...")
|
||||
# keep on trying after a disconnect
|
||||
s.close()
|
||||
time.sleep(3)
|
||||
except Exception as e:
|
||||
print("Caught Exception:", e)
|
||||
s.close()
|
||||
time.sleep(3)
|
2
server/.gitignore
vendored
Normal file
2
server/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
sergate
|
||||
.tags*
|
34
server/Makefile
Normal file
34
server/Makefile
Normal file
@ -0,0 +1,34 @@
|
||||
OUT = sergate
|
||||
|
||||
#CC = clang
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -O2
|
||||
|
||||
#CXX = clang++
|
||||
CXX = g++
|
||||
CXXFLAGS = -Wall -O2
|
||||
|
||||
ODIR = obj
|
||||
SDIR = src
|
||||
LIBS = -pthread -lm
|
||||
INC = -Iinclude
|
||||
|
||||
C_SRCS := $(wildcard $(SDIR)/*.c)
|
||||
CXX_SRCS := $(wildcard $(SDIR)/*.cpp)
|
||||
C_OBJS := $(patsubst $(SDIR)/%.c,$(ODIR)/%.o,$(C_SRCS))
|
||||
CXX_OBJS := $(patsubst $(SDIR)/%.cpp,$(ODIR)/%.o,$(CXX_SRCS))
|
||||
OBJS := $(C_OBJS) $(CXX_OBJS)
|
||||
|
||||
$(ODIR)/%.o: $(SDIR)/%.c
|
||||
$(CC) -c $(INC) $(CFLAGS) $< -o $@
|
||||
|
||||
$(ODIR)/%.o: $(SDIR)/%.cpp
|
||||
$(CXX) -c $(INC) $(CXXFLAGS) $< -o $@
|
||||
|
||||
$(OUT): $(OBJS)
|
||||
$(CXX) $(LIBS) $^ -o $(OUT)
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
clean:
|
||||
rm -f $(ODIR)/*.o $(OUT)
|
37
server/config.json
Normal file
37
server/config.json
Normal file
@ -0,0 +1,37 @@
|
||||
{"servers":
|
||||
[
|
||||
{
|
||||
"username": "s01",
|
||||
"name": "node1",
|
||||
"type": "xen",
|
||||
"host": "host1",
|
||||
"location": "cn",
|
||||
"password": "USER_DEFAULT_PASSWORD"
|
||||
},
|
||||
{
|
||||
"username": "s02",
|
||||
"name": "node2",
|
||||
"type": "vmware",
|
||||
"host": "host2",
|
||||
"location": "jp",
|
||||
"password": "USER_DEFAULT_PASSWORD"
|
||||
},
|
||||
{
|
||||
"disabled": true,
|
||||
"username": "s03",
|
||||
"name": "node3",
|
||||
"type": "Nothing",
|
||||
"host": "host3",
|
||||
"location": "fr",
|
||||
"password": "USER_DEFAULT_PASSWORD"
|
||||
},
|
||||
{
|
||||
"username": "s04",
|
||||
"name": "node4",
|
||||
"type": "kvm",
|
||||
"host": "host4",
|
||||
"location": "kr",
|
||||
"password": "USER_DEFAULT_PASSWORD"
|
||||
}
|
||||
]
|
||||
}
|
139
server/include/argparse.h
Normal file
139
server/include/argparse.h
Normal file
@ -0,0 +1,139 @@
|
||||
#ifndef ARGPARSE_H
|
||||
#define ARGPARSE_H
|
||||
|
||||
/**
|
||||
* Command-line arguments parsing library.
|
||||
*
|
||||
* This module is inspired by parse-options.c (git) and python's argparse
|
||||
* module.
|
||||
*
|
||||
* Arguments parsing is common task in cli program, but traditional `getopt`
|
||||
* libraries are not easy to use. This library provides high-level arguments
|
||||
* parsing solutions.
|
||||
*
|
||||
* The program defines what arguments it requires, and `argparse` will figure
|
||||
* out how to parse those out of `argc` and `argv`, it also automatically
|
||||
* generates help and usage messages and issues errors when users give the
|
||||
* program invalid arguments.
|
||||
*
|
||||
* Reserved namespaces:
|
||||
* argparse
|
||||
* OPT
|
||||
* Author: Yecheng Fu <cofyc.jackson@gmail.com>
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct argparse;
|
||||
struct argparse_option;
|
||||
|
||||
typedef int argparse_callback(struct argparse *this_,
|
||||
const struct argparse_option *option);
|
||||
|
||||
enum argparse_flag {
|
||||
ARGPARSE_STOP_AT_NON_OPTION = 1,
|
||||
};
|
||||
|
||||
enum argparse_option_type {
|
||||
/* special */
|
||||
ARGPARSE_OPT_END,
|
||||
/* options with no arguments */
|
||||
ARGPARSE_OPT_BOOLEAN,
|
||||
ARGPARSE_OPT_BIT,
|
||||
/* options with arguments (optional or required) */
|
||||
ARGPARSE_OPT_INTEGER,
|
||||
ARGPARSE_OPT_STRING,
|
||||
};
|
||||
|
||||
enum argparse_option_flags {
|
||||
OPT_NONEG = 1, /* Negation disabled. */
|
||||
};
|
||||
|
||||
/*
|
||||
* Argparse option struct.
|
||||
*
|
||||
* `type`:
|
||||
* holds the type of the option, you must have an ARGPARSE_OPT_END last in your
|
||||
* array.
|
||||
*
|
||||
* `short_name`:
|
||||
* the character to use as a short option name, '\0' if none.
|
||||
*
|
||||
* `long_name`:
|
||||
* the long option name, without the leading dash, NULL if none.
|
||||
*
|
||||
* `value`:
|
||||
* stores pointer to the value to be filled.
|
||||
*
|
||||
* `help`:
|
||||
* the short help message associated to what the option does.
|
||||
* Must never be NULL (except for ARGPARSE_OPT_END).
|
||||
*
|
||||
* `callback`:
|
||||
* function is called when corresponding argument is parsed.
|
||||
*
|
||||
* `data`:
|
||||
* associated data. Callbacks can use it like they want.
|
||||
*
|
||||
* `flags`:
|
||||
* option flags.
|
||||
*
|
||||
*/
|
||||
struct argparse_option {
|
||||
enum argparse_option_type type;
|
||||
const char short_name;
|
||||
const char *long_name;
|
||||
void *value;
|
||||
const char *help;
|
||||
argparse_callback *callback;
|
||||
intptr_t data;
|
||||
int flags;
|
||||
};
|
||||
|
||||
/*
|
||||
* argpparse
|
||||
*/
|
||||
struct argparse {
|
||||
// user supplied
|
||||
const struct argparse_option *options;
|
||||
const char *usage;
|
||||
int flags;
|
||||
// internal context
|
||||
int argc;
|
||||
const char **argv;
|
||||
const char **out;
|
||||
int cpidx;
|
||||
const char *optvalue; // current option value
|
||||
};
|
||||
|
||||
// builtin callbacks
|
||||
int argparse_help_cb(struct argparse *this_,
|
||||
const struct argparse_option *option);
|
||||
|
||||
// builtin option macros
|
||||
#define OPT_END() { ARGPARSE_OPT_END, 0 }
|
||||
#define OPT_BOOLEAN(...) { ARGPARSE_OPT_BOOLEAN, __VA_ARGS__ }
|
||||
#define OPT_BIT(...) { ARGPARSE_OPT_BIT, __VA_ARGS__ }
|
||||
#define OPT_INTEGER(...) { ARGPARSE_OPT_INTEGER, __VA_ARGS__ }
|
||||
#define OPT_STRING(...) { ARGPARSE_OPT_STRING, __VA_ARGS__ }
|
||||
#define OPT_HELP() OPT_BOOLEAN('h', "help", 0, "Show this help message and exit", argparse_help_cb)
|
||||
|
||||
int argparse_init(struct argparse *this_, struct argparse_option *options,
|
||||
const char *usage, int flags);
|
||||
int argparse_parse(struct argparse *this_, int argc, const char **argv);
|
||||
void argparse_usage(struct argparse *this_);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
149
server/include/detect.h
Normal file
149
server/include/detect.h
Normal file
@ -0,0 +1,149 @@
|
||||
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
|
||||
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
||||
#ifndef BASE_DETECT_H
|
||||
#define BASE_DETECT_H
|
||||
|
||||
/*
|
||||
this file detected the family, platform and architecture
|
||||
to compile for.
|
||||
*/
|
||||
|
||||
/* platforms */
|
||||
|
||||
/* windows Family */
|
||||
#if defined(WIN64) || defined(_WIN64)
|
||||
/* Hmm, is this IA64 or x86-64? */
|
||||
#define CONF_FAMILY_WINDOWS 1
|
||||
#define CONF_FAMILY_STRING "windows"
|
||||
#define CONF_PLATFORM_WIN64 1
|
||||
#define CONF_PLATFORM_STRING "win64"
|
||||
#elif defined(WIN32) || defined(_WIN32) || defined(__CYGWIN32__) || defined(__MINGW32__)
|
||||
#define CONF_FAMILY_WINDOWS 1
|
||||
#define CONF_FAMILY_STRING "windows"
|
||||
#define CONF_PLATFORM_WIN32 1
|
||||
#define CONF_PLATFORM_STRING "win32"
|
||||
#endif
|
||||
|
||||
/* unix family */
|
||||
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
|
||||
#define CONF_FAMILY_UNIX 1
|
||||
#define CONF_FAMILY_STRING "unix"
|
||||
#define CONF_PLATFORM_FREEBSD 1
|
||||
#define CONF_PLATFORM_STRING "freebsd"
|
||||
#endif
|
||||
|
||||
#if defined(__OpenBSD__)
|
||||
#define CONF_FAMILY_UNIX 1
|
||||
#define CONF_FAMILY_STRING "unix"
|
||||
#define CONF_PLATFORM_OPENBSD 1
|
||||
#define CONF_PLATFORM_STRING "openbsd"
|
||||
#endif
|
||||
|
||||
#if defined(__LINUX__) || defined(__linux__)
|
||||
#define CONF_FAMILY_UNIX 1
|
||||
#define CONF_FAMILY_STRING "unix"
|
||||
#define CONF_PLATFORM_LINUX 1
|
||||
#define CONF_PLATFORM_STRING "linux"
|
||||
#endif
|
||||
|
||||
#if defined(__GNU__) || defined(__gnu__)
|
||||
#define CONF_FAMILY_UNIX 1
|
||||
#define CONF_FAMILY_STRING "unix"
|
||||
#define CONF_PLATFORM_HURD 1
|
||||
#define CONF_PLATFORM_STRING "gnu"
|
||||
#endif
|
||||
|
||||
#if defined(MACOSX) || defined(__APPLE__) || defined(__DARWIN__)
|
||||
#define CONF_FAMILY_UNIX 1
|
||||
#define CONF_FAMILY_STRING "unix"
|
||||
#define CONF_PLATFORM_MACOSX 1
|
||||
#define CONF_PLATFORM_STRING "macosx"
|
||||
#endif
|
||||
|
||||
#if defined(__sun)
|
||||
#define CONF_FAMILY_UNIX 1
|
||||
#define CONF_FAMILY_STRING "unix"
|
||||
#define CONF_PLATFORM_SOLARIS 1
|
||||
#define CONF_PLATFORM_STRING "solaris"
|
||||
#endif
|
||||
|
||||
/* beos family */
|
||||
#if defined(__BeOS) || defined(__BEOS__)
|
||||
#define CONF_FAMILY_BEOS 1
|
||||
#define CONF_FAMILY_STRING "beos"
|
||||
#define CONF_PLATFORM_BEOS 1
|
||||
#define CONF_PLATFORM_STRING "beos"
|
||||
#endif
|
||||
|
||||
|
||||
/* use gcc endianness definitions when available */
|
||||
#if defined(__GNUC__) && !defined(__APPLE__) && !defined(__MINGW32__) && !defined(__sun)
|
||||
#if defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||
#include <sys/endian.h>
|
||||
#else
|
||||
#include <endian.h>
|
||||
#endif
|
||||
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#define CONF_ARCH_ENDIAN_LITTLE 1
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
#define CONF_ARCH_ENDIAN_BIG 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
/* architectures */
|
||||
#if defined(i386) || defined(__i386__) || defined(__x86__) || defined(CONF_PLATFORM_WIN32)
|
||||
#define CONF_ARCH_IA32 1
|
||||
#define CONF_ARCH_STRING "ia32"
|
||||
#if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG)
|
||||
#define CONF_ARCH_ENDIAN_LITTLE 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__ia64__) || defined(_M_IA64)
|
||||
#define CONF_ARCH_IA64 1
|
||||
#define CONF_ARCH_STRING "ia64"
|
||||
#if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG)
|
||||
#define CONF_ARCH_ENDIAN_LITTLE 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__amd64__) || defined(__x86_64__) || defined(_M_X64)
|
||||
#define CONF_ARCH_AMD64 1
|
||||
#define CONF_ARCH_STRING "amd64"
|
||||
#if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG)
|
||||
#define CONF_ARCH_ENDIAN_LITTLE 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__powerpc__) || defined(__ppc__)
|
||||
#define CONF_ARCH_PPC 1
|
||||
#define CONF_ARCH_STRING "ppc"
|
||||
#if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG)
|
||||
#define CONF_ARCH_ENDIAN_BIG 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__sparc__)
|
||||
#define CONF_ARCH_SPARC 1
|
||||
#define CONF_ARCH_STRING "sparc"
|
||||
#if !defined(CONF_ARCH_ENDIAN_LITTLE) && !defined(CONF_ARCH_ENDIAN_BIG)
|
||||
#define CONF_ARCH_ENDIAN_BIG 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef CONF_FAMILY_STRING
|
||||
#define CONF_FAMILY_STRING "unknown"
|
||||
#endif
|
||||
|
||||
#ifndef CONF_PLATFORM_STRING
|
||||
#define CONF_PLATFORM_STRING "unknown"
|
||||
#endif
|
||||
|
||||
#ifndef CONF_ARCH_STRING
|
||||
#define CONF_ARCH_STRING "unknown"
|
||||
#endif
|
||||
|
||||
#endif
|
269
server/include/json.h
Normal file
269
server/include/json.h
Normal file
@ -0,0 +1,269 @@
|
||||
|
||||
/* vim: set et ts=3 sw=3 sts=3 ft=c:
|
||||
*
|
||||
* Copyright (C) 2012, 2013, 2014 James McLaughlin et al. All rights reserved.
|
||||
* https://github.com/udp/json-parser
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _JSON_H
|
||||
#define _JSON_H
|
||||
|
||||
#ifndef json_char
|
||||
#define json_char char
|
||||
#endif
|
||||
|
||||
#ifndef json_int_t
|
||||
#ifndef _MSC_VER
|
||||
#include <inttypes.h>
|
||||
#define json_int_t int64_t
|
||||
#else
|
||||
#define json_int_t __int64
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include <string.h>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned long max_memory;
|
||||
int settings;
|
||||
|
||||
/* Custom allocator support (leave null to use malloc/free)
|
||||
*/
|
||||
|
||||
void * (* mem_alloc) (size_t, int zero, void * user_data);
|
||||
void (* mem_free) (void *, void * user_data);
|
||||
|
||||
void * user_data; /* will be passed to mem_alloc and mem_free */
|
||||
|
||||
} json_settings;
|
||||
|
||||
#define json_enable_comments 0x01
|
||||
|
||||
typedef enum
|
||||
{
|
||||
json_none,
|
||||
json_object,
|
||||
json_array,
|
||||
json_integer,
|
||||
json_double,
|
||||
json_string,
|
||||
json_boolean,
|
||||
json_null
|
||||
|
||||
} json_type;
|
||||
|
||||
extern const struct _json_value json_value_none;
|
||||
|
||||
typedef struct _json_value
|
||||
{
|
||||
struct _json_value * parent;
|
||||
|
||||
json_type type;
|
||||
|
||||
union
|
||||
{
|
||||
int boolean;
|
||||
json_int_t integer;
|
||||
double dbl;
|
||||
|
||||
struct
|
||||
{
|
||||
unsigned int length;
|
||||
json_char * ptr; /* null terminated */
|
||||
|
||||
} string;
|
||||
|
||||
struct
|
||||
{
|
||||
unsigned int length;
|
||||
|
||||
struct
|
||||
{
|
||||
json_char * name;
|
||||
unsigned int name_length;
|
||||
|
||||
struct _json_value * value;
|
||||
|
||||
} * values;
|
||||
|
||||
#if defined(__cplusplus) && __cplusplus >= 201103L
|
||||
decltype(values) begin () const
|
||||
{ return values;
|
||||
}
|
||||
decltype(values) end () const
|
||||
{ return values + length;
|
||||
}
|
||||
#endif
|
||||
|
||||
} object;
|
||||
|
||||
struct
|
||||
{
|
||||
unsigned int length;
|
||||
struct _json_value ** values;
|
||||
|
||||
#if defined(__cplusplus) && __cplusplus >= 201103L
|
||||
decltype(values) begin () const
|
||||
{ return values;
|
||||
}
|
||||
decltype(values) end () const
|
||||
{ return values + length;
|
||||
}
|
||||
#endif
|
||||
|
||||
} array;
|
||||
|
||||
} u;
|
||||
|
||||
union
|
||||
{
|
||||
struct _json_value * next_alloc;
|
||||
void * object_mem;
|
||||
|
||||
} _reserved;
|
||||
|
||||
|
||||
/* Some C++ operator sugar */
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
public:
|
||||
|
||||
inline _json_value ()
|
||||
{ memset (this, 0, sizeof (_json_value));
|
||||
}
|
||||
|
||||
inline const struct _json_value &operator [] (int index) const
|
||||
{
|
||||
if (type != json_array || index < 0
|
||||
|| ((unsigned int) index) >= u.array.length)
|
||||
{
|
||||
return json_value_none;
|
||||
}
|
||||
|
||||
return *u.array.values [index];
|
||||
}
|
||||
|
||||
inline const struct _json_value &operator [] (const char * index) const
|
||||
{
|
||||
if (type != json_object)
|
||||
return json_value_none;
|
||||
|
||||
for (unsigned int i = 0; i < u.object.length; ++ i)
|
||||
if (!strcmp (u.object.values [i].name, index))
|
||||
return *u.object.values [i].value;
|
||||
|
||||
return json_value_none;
|
||||
}
|
||||
|
||||
inline operator const char * () const
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case json_string:
|
||||
return u.string.ptr;
|
||||
|
||||
default:
|
||||
return "";
|
||||
};
|
||||
}
|
||||
|
||||
inline operator json_int_t () const
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case json_integer:
|
||||
return u.integer;
|
||||
|
||||
case json_double:
|
||||
return (json_int_t) u.dbl;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
inline operator bool () const
|
||||
{
|
||||
if (type != json_boolean)
|
||||
return false;
|
||||
|
||||
return u.boolean != 0;
|
||||
}
|
||||
|
||||
inline operator double () const
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case json_integer:
|
||||
return (double) u.integer;
|
||||
|
||||
case json_double:
|
||||
return u.dbl;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} json_value;
|
||||
|
||||
json_value * json_parse (const json_char * json,
|
||||
size_t length);
|
||||
|
||||
#define json_error_max 128
|
||||
json_value * json_parse_ex (json_settings * settings,
|
||||
const json_char * json,
|
||||
size_t length,
|
||||
char * error);
|
||||
|
||||
void json_value_free (json_value *);
|
||||
|
||||
|
||||
/* Not usually necessary, unless you used a custom mem_alloc and now want to
|
||||
* use a custom mem_free.
|
||||
*/
|
||||
void json_value_free_ex (json_settings * settings,
|
||||
json_value *);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif
|
1299
server/include/system.h
Normal file
1299
server/include/system.h
Normal file
File diff suppressed because it is too large
Load Diff
1
server/obj/.gitignore
vendored
Normal file
1
server/obj/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.o
|
322
server/src/argparse.c
Normal file
322
server/src/argparse.c
Normal file
@ -0,0 +1,322 @@
|
||||
#include "argparse.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define OPT_UNSET 1
|
||||
|
||||
static const char *
|
||||
prefix_skip(const char *str, const char *prefix)
|
||||
{
|
||||
size_t len = strlen(prefix);
|
||||
return strncmp(str, prefix, len) ? NULL : str + len;
|
||||
}
|
||||
|
||||
int
|
||||
prefix_cmp(const char *str, const char *prefix)
|
||||
{
|
||||
for (;; str++, prefix++)
|
||||
if (!*prefix)
|
||||
return 0;
|
||||
else if (*str != *prefix)
|
||||
return (unsigned char)*prefix - (unsigned char)*str;
|
||||
}
|
||||
|
||||
static void
|
||||
argparse_error(struct argparse *this_, const struct argparse_option *opt,
|
||||
const char *reason)
|
||||
{
|
||||
if (!strncmp(this_->argv[0], "--", 2)) {
|
||||
fprintf(stderr, "error: option `%s` %s\n", opt->long_name, reason);
|
||||
exit(-1);
|
||||
} else {
|
||||
fprintf(stderr, "error: option `%c` %s\n", opt->short_name, reason);
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
argparse_getvalue(struct argparse *this_, const struct argparse_option *opt,
|
||||
int flags)
|
||||
{
|
||||
const char *s = NULL;
|
||||
if (!opt->value)
|
||||
goto skipped;
|
||||
switch (opt->type) {
|
||||
case ARGPARSE_OPT_BOOLEAN:
|
||||
if (flags & OPT_UNSET) {
|
||||
*(int *)opt->value = *(int *)opt->value - 1;
|
||||
} else {
|
||||
*(int *)opt->value = *(int *)opt->value + 1;
|
||||
}
|
||||
if (*(int *)opt->value < 0) {
|
||||
*(int *)opt->value = 0;
|
||||
}
|
||||
break;
|
||||
case ARGPARSE_OPT_BIT:
|
||||
if (flags & OPT_UNSET) {
|
||||
*(int *)opt->value &= ~opt->data;
|
||||
} else {
|
||||
*(int *)opt->value |= opt->data;
|
||||
}
|
||||
break;
|
||||
case ARGPARSE_OPT_STRING:
|
||||
if (this_->optvalue) {
|
||||
*(const char **)opt->value = this_->optvalue;
|
||||
this_->optvalue = NULL;
|
||||
} else if (this_->argc > 1) {
|
||||
this_->argc--;
|
||||
*(const char **)opt->value = *++this_->argv;
|
||||
} else {
|
||||
argparse_error(this_, opt, "requires a value");
|
||||
}
|
||||
break;
|
||||
case ARGPARSE_OPT_INTEGER:
|
||||
if (this_->optvalue) {
|
||||
*(int *)opt->value = strtol(this_->optvalue, (char **)&s, 0);
|
||||
this_->optvalue = NULL;
|
||||
} else if (this_->argc > 1) {
|
||||
this_->argc--;
|
||||
*(int *)opt->value = strtol(*++this_->argv, (char **)&s, 0);
|
||||
} else {
|
||||
argparse_error(this_, opt, "requires a value");
|
||||
}
|
||||
if (*s)
|
||||
argparse_error(this_, opt, "expects a numerical value");
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
skipped:
|
||||
if (opt->callback) {
|
||||
return opt->callback(this_, opt);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
argparse_options_check(const struct argparse_option *options)
|
||||
{
|
||||
for (; options->type != ARGPARSE_OPT_END; options++) {
|
||||
switch (options->type) {
|
||||
case ARGPARSE_OPT_END:
|
||||
case ARGPARSE_OPT_BOOLEAN:
|
||||
case ARGPARSE_OPT_BIT:
|
||||
case ARGPARSE_OPT_INTEGER:
|
||||
case ARGPARSE_OPT_STRING:
|
||||
continue;
|
||||
default:
|
||||
fprintf(stderr, "wrong option type: %d", options->type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
argparse_short_opt(struct argparse *this_, const struct argparse_option *options)
|
||||
{
|
||||
for (; options->type != ARGPARSE_OPT_END; options++) {
|
||||
if (options->short_name == *this_->optvalue) {
|
||||
this_->optvalue = this_->optvalue[1] ? this_->optvalue + 1 : NULL;
|
||||
return argparse_getvalue(this_, options, 0);
|
||||
}
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
|
||||
static int
|
||||
argparse_long_opt(struct argparse *this_, const struct argparse_option *options)
|
||||
{
|
||||
for (; options->type != ARGPARSE_OPT_END; options++) {
|
||||
const char *rest;
|
||||
int opt_flags = 0;
|
||||
if (!options->long_name)
|
||||
continue;
|
||||
|
||||
rest = prefix_skip(this_->argv[0] + 2, options->long_name);
|
||||
if (!rest) {
|
||||
// Negation allowed?
|
||||
if (options->flags & OPT_NONEG) {
|
||||
continue;
|
||||
}
|
||||
// Only boolean/bit allow negation.
|
||||
if (options->type != ARGPARSE_OPT_BOOLEAN && options->type != ARGPARSE_OPT_BIT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!prefix_cmp(this_->argv[0] + 2, "no-")) {
|
||||
rest = prefix_skip(this_->argv[0] + 2 + 3, options->long_name);
|
||||
if (!rest)
|
||||
continue;
|
||||
opt_flags |= OPT_UNSET;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (*rest) {
|
||||
if (*rest != '=')
|
||||
continue;
|
||||
this_->optvalue = rest + 1;
|
||||
}
|
||||
return argparse_getvalue(this_, options, opt_flags);
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
|
||||
int
|
||||
argparse_init(struct argparse *this_, struct argparse_option *options,
|
||||
const char *usage, int flags)
|
||||
{
|
||||
memset(this_, 0, sizeof(*this_));
|
||||
this_->options = options;
|
||||
this_->usage = usage;
|
||||
this_->flags = flags;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
argparse_parse(struct argparse *this_, int argc, const char **argv)
|
||||
{
|
||||
this_->argc = argc - 1;
|
||||
this_->argv = argv + 1;
|
||||
this_->out = argv;
|
||||
|
||||
argparse_options_check(this_->options);
|
||||
|
||||
for (; this_->argc; this_->argc--, this_->argv++) {
|
||||
const char *arg = this_->argv[0];
|
||||
if (arg[0] != '-' || !arg[1]) {
|
||||
if (this_->flags & ARGPARSE_STOP_AT_NON_OPTION) {
|
||||
goto end;
|
||||
}
|
||||
// if it's not option or is a single char '-', copy verbatimly
|
||||
this_->out[this_->cpidx++] = this_->argv[0];
|
||||
continue;
|
||||
}
|
||||
// short option
|
||||
if (arg[1] != '-') {
|
||||
this_->optvalue = arg + 1;
|
||||
switch (argparse_short_opt(this_, this_->options)) {
|
||||
case -1:
|
||||
break;
|
||||
case -2:
|
||||
goto unknown;
|
||||
}
|
||||
while (this_->optvalue) {
|
||||
switch (argparse_short_opt(this_, this_->options)) {
|
||||
case -1:
|
||||
break;
|
||||
case -2:
|
||||
goto unknown;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// if '--' presents
|
||||
if (!arg[2]) {
|
||||
this_->argc--;
|
||||
this_->argv++;
|
||||
break;
|
||||
}
|
||||
// long option
|
||||
switch (argparse_long_opt(this_, this_->options)) {
|
||||
case -1:
|
||||
break;
|
||||
case -2:
|
||||
goto unknown;
|
||||
}
|
||||
continue;
|
||||
|
||||
unknown:
|
||||
fprintf(stderr, "error: unknown option `%s`\n", this_->argv[0]);
|
||||
argparse_usage(this_);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
end:
|
||||
memmove(this_->out + this_->cpidx, this_->argv,
|
||||
this_->argc * sizeof(*this_->out));
|
||||
this_->out[this_->cpidx + this_->argc] = NULL;
|
||||
|
||||
return this_->cpidx + this_->argc;
|
||||
}
|
||||
|
||||
void
|
||||
argparse_usage(struct argparse *this_)
|
||||
{
|
||||
fprintf(stdout, "Usage: %s\n", this_->usage);
|
||||
fputc('\n', stdout);
|
||||
|
||||
const struct argparse_option *options;
|
||||
|
||||
// figure out best width
|
||||
size_t usage_opts_width = 0;
|
||||
size_t len;
|
||||
options = this_->options;
|
||||
for (; options->type != ARGPARSE_OPT_END; options++) {
|
||||
len = 0;
|
||||
if ((options)->short_name) {
|
||||
len += 2;
|
||||
}
|
||||
if ((options)->short_name && (options)->long_name) {
|
||||
len += 2; // separator ", "
|
||||
}
|
||||
if ((options)->long_name) {
|
||||
len += strlen((options)->long_name) + 2;
|
||||
}
|
||||
if (options->type == ARGPARSE_OPT_INTEGER) {
|
||||
len += strlen("=<int>");
|
||||
} else if (options->type == ARGPARSE_OPT_STRING) {
|
||||
len += strlen("=<str>");
|
||||
}
|
||||
len = ceil((float)len / 4) * 4;
|
||||
if (usage_opts_width < len) {
|
||||
usage_opts_width = len;
|
||||
}
|
||||
}
|
||||
usage_opts_width += 4; // 4 spaces prefix
|
||||
|
||||
options = this_->options;
|
||||
for (; options->type != ARGPARSE_OPT_END; options++) {
|
||||
size_t pos;
|
||||
int pad;
|
||||
pos = fprintf(stdout, " ");
|
||||
if (options->short_name) {
|
||||
pos += fprintf(stdout, "-%c", options->short_name);
|
||||
}
|
||||
if (options->long_name && options->short_name) {
|
||||
pos += fprintf(stdout, ", ");
|
||||
}
|
||||
if (options->long_name) {
|
||||
pos += fprintf(stdout, "--%s", options->long_name);
|
||||
}
|
||||
if (options->type == ARGPARSE_OPT_INTEGER) {
|
||||
pos += fprintf(stdout, "=<int>");
|
||||
} else if (options->type == ARGPARSE_OPT_STRING) {
|
||||
pos += fprintf(stdout, "=<str>");
|
||||
}
|
||||
if (pos <= usage_opts_width) {
|
||||
pad = usage_opts_width - pos;
|
||||
} else {
|
||||
fputc('\n', stdout);
|
||||
pad = usage_opts_width;
|
||||
}
|
||||
fprintf(stdout, "%*s%s\n", pad + 2, "", options->help);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
argparse_help_cb(struct argparse *this_, const struct argparse_option *option)
|
||||
{
|
||||
(void)option;
|
||||
argparse_usage(this_);
|
||||
exit(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
949
server/src/json.c
Normal file
949
server/src/json.c
Normal file
@ -0,0 +1,949 @@
|
||||
/* vim: set et ts=3 sw=3 sts=3 ft=c:
|
||||
*
|
||||
* Copyright (C) 2012, 2013, 2014 James McLaughlin et al. All rights reserved.
|
||||
* https://github.com/udp/json-parser
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "json.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#ifndef _CRT_SECURE_NO_WARNINGS
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
const struct _json_value json_value_none; /* zero-d by ctor */
|
||||
#else
|
||||
const struct _json_value json_value_none = { 0 };
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
|
||||
typedef unsigned short json_uchar;
|
||||
|
||||
static unsigned char hex_value (json_char c)
|
||||
{
|
||||
if (isdigit(c))
|
||||
return c - '0';
|
||||
|
||||
switch (c) {
|
||||
case 'a': case 'A': return 0x0A;
|
||||
case 'b': case 'B': return 0x0B;
|
||||
case 'c': case 'C': return 0x0C;
|
||||
case 'd': case 'D': return 0x0D;
|
||||
case 'e': case 'E': return 0x0E;
|
||||
case 'f': case 'F': return 0x0F;
|
||||
default: return 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned long used_memory;
|
||||
|
||||
unsigned int uint_max;
|
||||
unsigned long ulong_max;
|
||||
|
||||
json_settings settings;
|
||||
int first_pass;
|
||||
|
||||
} json_state;
|
||||
|
||||
static void * default_alloc (size_t size, int zero, void * user_data)
|
||||
{
|
||||
return zero ? calloc (1, size) : malloc (size);
|
||||
}
|
||||
|
||||
static void default_free (void * ptr, void * user_data)
|
||||
{
|
||||
free (ptr);
|
||||
}
|
||||
|
||||
static void * json_alloc (json_state * state, unsigned long size, int zero)
|
||||
{
|
||||
if ((state->ulong_max - state->used_memory) < size)
|
||||
return 0;
|
||||
|
||||
if (state->settings.max_memory
|
||||
&& (state->used_memory += size) > state->settings.max_memory)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return state->settings.mem_alloc (size, zero, state->settings.user_data);
|
||||
}
|
||||
|
||||
static int new_value
|
||||
(json_state * state, json_value ** top, json_value ** root, json_value ** alloc, json_type type)
|
||||
{
|
||||
json_value * value;
|
||||
int values_size;
|
||||
|
||||
if (!state->first_pass)
|
||||
{
|
||||
value = *top = *alloc;
|
||||
*alloc = (*alloc)->_reserved.next_alloc;
|
||||
|
||||
if (!*root)
|
||||
*root = value;
|
||||
|
||||
switch (value->type)
|
||||
{
|
||||
case json_array:
|
||||
|
||||
if (! (value->u.array.values = (json_value **) json_alloc
|
||||
(state, value->u.array.length * sizeof (json_value *), 0)) )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
value->u.array.length = 0;
|
||||
break;
|
||||
|
||||
case json_object:
|
||||
|
||||
values_size = sizeof (*value->u.object.values) * value->u.object.length;
|
||||
|
||||
if (! ((*(void **) &value->u.object.values) = json_alloc
|
||||
(state, values_size + ((unsigned long) value->u.object.values), 0)) )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
value->_reserved.object_mem = (*(char **) &value->u.object.values) + values_size;
|
||||
|
||||
value->u.object.length = 0;
|
||||
break;
|
||||
|
||||
case json_string:
|
||||
|
||||
if (! (value->u.string.ptr = (json_char *) json_alloc
|
||||
(state, (value->u.string.length + 1) * sizeof (json_char), 0)) )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
value->u.string.length = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
value = (json_value *) json_alloc (state, sizeof (json_value), 1);
|
||||
|
||||
if (!value)
|
||||
return 0;
|
||||
|
||||
if (!*root)
|
||||
*root = value;
|
||||
|
||||
value->type = type;
|
||||
value->parent = *top;
|
||||
|
||||
if (*alloc)
|
||||
(*alloc)->_reserved.next_alloc = value;
|
||||
|
||||
*alloc = *top = value;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define e_off \
|
||||
((int) (i - cur_line_begin))
|
||||
|
||||
#define whitespace \
|
||||
case '\n': ++ cur_line; cur_line_begin = i; \
|
||||
case ' ': case '\t': case '\r'
|
||||
|
||||
#define string_add(b) \
|
||||
do { if (!state.first_pass) string [string_length] = b; ++ string_length; } while (0);
|
||||
|
||||
static const long
|
||||
flag_next = 1 << 0,
|
||||
flag_reproc = 1 << 1,
|
||||
flag_need_comma = 1 << 2,
|
||||
flag_seek_value = 1 << 3,
|
||||
flag_escaped = 1 << 4,
|
||||
flag_string = 1 << 5,
|
||||
flag_need_colon = 1 << 6,
|
||||
flag_done = 1 << 7,
|
||||
flag_num_negative = 1 << 8,
|
||||
flag_num_zero = 1 << 9,
|
||||
flag_num_e = 1 << 10,
|
||||
flag_num_e_got_sign = 1 << 11,
|
||||
flag_num_e_negative = 1 << 12,
|
||||
flag_line_comment = 1 << 13,
|
||||
flag_block_comment = 1 << 14;
|
||||
|
||||
json_value * json_parse_ex (json_settings * settings,
|
||||
const json_char * json,
|
||||
size_t length,
|
||||
char * error_buf)
|
||||
{
|
||||
json_char error [json_error_max];
|
||||
unsigned int cur_line;
|
||||
const json_char * cur_line_begin, * i, * end;
|
||||
json_value * top, * root, * alloc = 0;
|
||||
json_state state = { 0 };
|
||||
long flags;
|
||||
long num_digits = 0, num_e = 0;
|
||||
json_int_t num_fraction = 0;
|
||||
|
||||
/* Skip UTF-8 BOM
|
||||
*/
|
||||
if (length >= 3 && ((unsigned char) json [0]) == 0xEF
|
||||
&& ((unsigned char) json [1]) == 0xBB
|
||||
&& ((unsigned char) json [2]) == 0xBF)
|
||||
{
|
||||
json += 3;
|
||||
length -= 3;
|
||||
}
|
||||
|
||||
error[0] = '\0';
|
||||
end = (json + length);
|
||||
|
||||
memcpy (&state.settings, settings, sizeof (json_settings));
|
||||
|
||||
if (!state.settings.mem_alloc)
|
||||
state.settings.mem_alloc = default_alloc;
|
||||
|
||||
if (!state.settings.mem_free)
|
||||
state.settings.mem_free = default_free;
|
||||
|
||||
memset (&state.uint_max, 0xFF, sizeof (state.uint_max));
|
||||
memset (&state.ulong_max, 0xFF, sizeof (state.ulong_max));
|
||||
|
||||
state.uint_max -= 8; /* limit of how much can be added before next check */
|
||||
state.ulong_max -= 8;
|
||||
|
||||
for (state.first_pass = 1; state.first_pass >= 0; -- state.first_pass)
|
||||
{
|
||||
json_uchar uchar;
|
||||
unsigned char uc_b1, uc_b2, uc_b3, uc_b4;
|
||||
json_char * string = 0;
|
||||
unsigned int string_length = 0;
|
||||
|
||||
top = root = 0;
|
||||
flags = flag_seek_value;
|
||||
|
||||
cur_line = 1;
|
||||
cur_line_begin = json;
|
||||
|
||||
for (i = json ;; ++ i)
|
||||
{
|
||||
json_char b = (i == end ? 0 : *i);
|
||||
|
||||
if (flags & flag_string)
|
||||
{
|
||||
if (!b)
|
||||
{ sprintf (error, "Unexpected EOF in string (at %d:%d)", cur_line, e_off);
|
||||
goto e_failed;
|
||||
}
|
||||
|
||||
if (string_length > state.uint_max)
|
||||
goto e_overflow;
|
||||
|
||||
if (flags & flag_escaped)
|
||||
{
|
||||
flags &= ~ flag_escaped;
|
||||
|
||||
switch (b)
|
||||
{
|
||||
case 'b': string_add ('\b'); break;
|
||||
case 'f': string_add ('\f'); break;
|
||||
case 'n': string_add ('\n'); break;
|
||||
case 'r': string_add ('\r'); break;
|
||||
case 't': string_add ('\t'); break;
|
||||
case 'u':
|
||||
|
||||
if (end - i < 4 ||
|
||||
(uc_b1 = hex_value (*++ i)) == 0xFF || (uc_b2 = hex_value (*++ i)) == 0xFF
|
||||
|| (uc_b3 = hex_value (*++ i)) == 0xFF || (uc_b4 = hex_value (*++ i)) == 0xFF)
|
||||
{
|
||||
sprintf (error, "Invalid character value `%c` (at %d:%d)", b, cur_line, e_off);
|
||||
goto e_failed;
|
||||
}
|
||||
|
||||
uc_b1 = uc_b1 * 16 + uc_b2;
|
||||
uc_b2 = uc_b3 * 16 + uc_b4;
|
||||
|
||||
uchar = ((json_char) uc_b1) * 256 + uc_b2;
|
||||
|
||||
if (sizeof (json_char) >= sizeof (json_uchar) || (uc_b1 == 0 && uc_b2 <= 0x7F))
|
||||
{
|
||||
string_add ((json_char) uchar);
|
||||
break;
|
||||
}
|
||||
|
||||
if (uchar <= 0x7FF)
|
||||
{
|
||||
if (state.first_pass)
|
||||
string_length += 2;
|
||||
else
|
||||
{ string [string_length ++] = 0xC0 | ((uc_b2 & 0xC0) >> 6) | ((uc_b1 & 0x7) << 2);
|
||||
string [string_length ++] = 0x80 | (uc_b2 & 0x3F);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (state.first_pass)
|
||||
string_length += 3;
|
||||
else
|
||||
{ string [string_length ++] = 0xE0 | ((uc_b1 & 0xF0) >> 4);
|
||||
string [string_length ++] = 0x80 | ((uc_b1 & 0xF) << 2) | ((uc_b2 & 0xC0) >> 6);
|
||||
string [string_length ++] = 0x80 | (uc_b2 & 0x3F);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
string_add (b);
|
||||
};
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (b == '\\')
|
||||
{
|
||||
flags |= flag_escaped;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (b == '"')
|
||||
{
|
||||
if (!state.first_pass)
|
||||
string [string_length] = 0;
|
||||
|
||||
flags &= ~ flag_string;
|
||||
string = 0;
|
||||
|
||||
switch (top->type)
|
||||
{
|
||||
case json_string:
|
||||
|
||||
top->u.string.length = string_length;
|
||||
flags |= flag_next;
|
||||
|
||||
break;
|
||||
|
||||
case json_object:
|
||||
|
||||
if (state.first_pass)
|
||||
(*(json_char **) &top->u.object.values) += string_length + 1;
|
||||
else
|
||||
{
|
||||
top->u.object.values [top->u.object.length].name
|
||||
= (json_char *) top->_reserved.object_mem;
|
||||
|
||||
top->u.object.values [top->u.object.length].name_length
|
||||
= string_length;
|
||||
|
||||
(*(json_char **) &top->_reserved.object_mem) += string_length + 1;
|
||||
}
|
||||
|
||||
flags |= flag_seek_value | flag_need_colon;
|
||||
continue;
|
||||
|
||||
default:
|
||||
break;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
string_add (b);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (state.settings.settings & json_enable_comments)
|
||||
{
|
||||
if (flags & (flag_line_comment | flag_block_comment))
|
||||
{
|
||||
if (flags & flag_line_comment)
|
||||
{
|
||||
if (b == '\r' || b == '\n' || !b)
|
||||
{
|
||||
flags &= ~ flag_line_comment;
|
||||
-- i; /* so null can be reproc'd */
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (flags & flag_block_comment)
|
||||
{
|
||||
if (!b)
|
||||
{ sprintf (error, "%d:%d: Unexpected EOF in block comment", cur_line, e_off);
|
||||
goto e_failed;
|
||||
}
|
||||
|
||||
if (b == '*' && i < (end - 1) && i [1] == '/')
|
||||
{
|
||||
flags &= ~ flag_block_comment;
|
||||
++ i; /* skip closing sequence */
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (b == '/')
|
||||
{
|
||||
if (! (flags & (flag_seek_value | flag_done)) && top->type != json_object)
|
||||
{
|
||||
sprintf (error, "%d:%d: Comment not allowed here", cur_line, e_off);
|
||||
goto e_failed;
|
||||
}
|
||||
|
||||
if (++ i == end)
|
||||
{ sprintf (error, "%d:%d: EOF unexpected", cur_line, e_off);
|
||||
goto e_failed;
|
||||
}
|
||||
|
||||
switch (b = *i)
|
||||
{
|
||||
case '/':
|
||||
flags |= flag_line_comment;
|
||||
continue;
|
||||
|
||||
case '*':
|
||||
flags |= flag_block_comment;
|
||||
continue;
|
||||
|
||||
default:
|
||||
sprintf (error, "%d:%d: Unexpected `%c` in comment opening sequence", cur_line, e_off, b);
|
||||
goto e_failed;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & flag_done)
|
||||
{
|
||||
if (!b)
|
||||
break;
|
||||
|
||||
switch (b)
|
||||
{
|
||||
whitespace:
|
||||
continue;
|
||||
|
||||
default:
|
||||
sprintf (error, "%d:%d: Trailing garbage: `%c`", cur_line, e_off, b);
|
||||
goto e_failed;
|
||||
};
|
||||
}
|
||||
|
||||
if (flags & flag_seek_value)
|
||||
{
|
||||
switch (b)
|
||||
{
|
||||
whitespace:
|
||||
continue;
|
||||
|
||||
case ']':
|
||||
|
||||
if (top->type == json_array)
|
||||
flags = (flags & ~ (flag_need_comma | flag_seek_value)) | flag_next;
|
||||
else
|
||||
{ sprintf (error, "%d:%d: Unexpected ]", cur_line, e_off);
|
||||
goto e_failed;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
if (flags & flag_need_comma)
|
||||
{
|
||||
if (b == ',')
|
||||
{ flags &= ~ flag_need_comma;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{ sprintf (error, "%d:%d: Expected , before %c", cur_line, e_off, b);
|
||||
goto e_failed;
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & flag_need_colon)
|
||||
{
|
||||
if (b == ':')
|
||||
{ flags &= ~ flag_need_colon;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{ sprintf (error, "%d:%d: Expected : before %c", cur_line, e_off, b);
|
||||
goto e_failed;
|
||||
}
|
||||
}
|
||||
|
||||
flags &= ~ flag_seek_value;
|
||||
|
||||
switch (b)
|
||||
{
|
||||
case '{':
|
||||
|
||||
if (!new_value (&state, &top, &root, &alloc, json_object))
|
||||
goto e_alloc_failure;
|
||||
|
||||
continue;
|
||||
|
||||
case '[':
|
||||
|
||||
if (!new_value (&state, &top, &root, &alloc, json_array))
|
||||
goto e_alloc_failure;
|
||||
|
||||
flags |= flag_seek_value;
|
||||
continue;
|
||||
|
||||
case '"':
|
||||
|
||||
if (!new_value (&state, &top, &root, &alloc, json_string))
|
||||
goto e_alloc_failure;
|
||||
|
||||
flags |= flag_string;
|
||||
|
||||
string = top->u.string.ptr;
|
||||
string_length = 0;
|
||||
|
||||
continue;
|
||||
|
||||
case 't':
|
||||
|
||||
if ((end - i) < 3 || *(++ i) != 'r' || *(++ i) != 'u' || *(++ i) != 'e')
|
||||
goto e_unknown_value;
|
||||
|
||||
if (!new_value (&state, &top, &root, &alloc, json_boolean))
|
||||
goto e_alloc_failure;
|
||||
|
||||
top->u.boolean = 1;
|
||||
|
||||
flags |= flag_next;
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
|
||||
if ((end - i) < 4 || *(++ i) != 'a' || *(++ i) != 'l' || *(++ i) != 's' || *(++ i) != 'e')
|
||||
goto e_unknown_value;
|
||||
|
||||
if (!new_value (&state, &top, &root, &alloc, json_boolean))
|
||||
goto e_alloc_failure;
|
||||
|
||||
flags |= flag_next;
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
|
||||
if ((end - i) < 3 || *(++ i) != 'u' || *(++ i) != 'l' || *(++ i) != 'l')
|
||||
goto e_unknown_value;
|
||||
|
||||
if (!new_value (&state, &top, &root, &alloc, json_null))
|
||||
goto e_alloc_failure;
|
||||
|
||||
flags |= flag_next;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
if (isdigit (b) || b == '-')
|
||||
{
|
||||
if (!new_value (&state, &top, &root, &alloc, json_integer))
|
||||
goto e_alloc_failure;
|
||||
|
||||
if (!state.first_pass)
|
||||
{
|
||||
while (isdigit (b) || b == '+' || b == '-'
|
||||
|| b == 'e' || b == 'E' || b == '.')
|
||||
{
|
||||
if ( (++ i) == end)
|
||||
{
|
||||
b = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
b = *i;
|
||||
}
|
||||
|
||||
flags |= flag_next | flag_reproc;
|
||||
break;
|
||||
}
|
||||
|
||||
flags &= ~ (flag_num_negative | flag_num_e |
|
||||
flag_num_e_got_sign | flag_num_e_negative |
|
||||
flag_num_zero);
|
||||
|
||||
num_digits = 0;
|
||||
num_fraction = 0;
|
||||
num_e = 0;
|
||||
|
||||
if (b != '-')
|
||||
{
|
||||
flags |= flag_reproc;
|
||||
break;
|
||||
}
|
||||
|
||||
flags |= flag_num_negative;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{ sprintf (error, "%d:%d: Unexpected %c when seeking value", cur_line, e_off, b);
|
||||
goto e_failed;
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (top->type)
|
||||
{
|
||||
case json_object:
|
||||
|
||||
switch (b)
|
||||
{
|
||||
whitespace:
|
||||
continue;
|
||||
|
||||
case '"':
|
||||
|
||||
if (flags & flag_need_comma)
|
||||
{
|
||||
sprintf (error, "%d:%d: Expected , before \"", cur_line, e_off);
|
||||
goto e_failed;
|
||||
}
|
||||
|
||||
flags |= flag_string;
|
||||
|
||||
string = (json_char *) top->_reserved.object_mem;
|
||||
string_length = 0;
|
||||
|
||||
break;
|
||||
|
||||
case '}':
|
||||
|
||||
flags = (flags & ~ flag_need_comma) | flag_next;
|
||||
break;
|
||||
|
||||
case ',':
|
||||
|
||||
if (flags & flag_need_comma)
|
||||
{
|
||||
flags &= ~ flag_need_comma;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
sprintf (error, "%d:%d: Unexpected `%c` in object", cur_line, e_off, b);
|
||||
goto e_failed;
|
||||
};
|
||||
|
||||
break;
|
||||
|
||||
case json_integer:
|
||||
case json_double:
|
||||
|
||||
if (isdigit (b))
|
||||
{
|
||||
++ num_digits;
|
||||
|
||||
if (top->type == json_integer || flags & flag_num_e)
|
||||
{
|
||||
if (! (flags & flag_num_e))
|
||||
{
|
||||
if (flags & flag_num_zero)
|
||||
{ sprintf (error, "%d:%d: Unexpected `0` before `%c`", cur_line, e_off, b);
|
||||
goto e_failed;
|
||||
}
|
||||
|
||||
if (num_digits == 1 && b == '0')
|
||||
flags |= flag_num_zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
flags |= flag_num_e_got_sign;
|
||||
num_e = (num_e * 10) + (b - '0');
|
||||
continue;
|
||||
}
|
||||
|
||||
top->u.integer = (top->u.integer * 10) + (b - '0');
|
||||
continue;
|
||||
}
|
||||
|
||||
num_fraction = (num_fraction * 10) + (b - '0');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (b == '+' || b == '-')
|
||||
{
|
||||
if ( (flags & flag_num_e) && !(flags & flag_num_e_got_sign))
|
||||
{
|
||||
flags |= flag_num_e_got_sign;
|
||||
|
||||
if (b == '-')
|
||||
flags |= flag_num_e_negative;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (b == '.' && top->type == json_integer)
|
||||
{
|
||||
if (!num_digits)
|
||||
{ sprintf (error, "%d:%d: Expected digit before `.`", cur_line, e_off);
|
||||
goto e_failed;
|
||||
}
|
||||
|
||||
top->type = json_double;
|
||||
top->u.dbl = (double) top->u.integer;
|
||||
|
||||
num_digits = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! (flags & flag_num_e))
|
||||
{
|
||||
if (top->type == json_double)
|
||||
{
|
||||
if (!num_digits)
|
||||
{ sprintf (error, "%d:%d: Expected digit after `.`", cur_line, e_off);
|
||||
goto e_failed;
|
||||
}
|
||||
|
||||
top->u.dbl += ((double) num_fraction) / (pow (10, (double) num_digits));
|
||||
}
|
||||
|
||||
if (b == 'e' || b == 'E')
|
||||
{
|
||||
flags |= flag_num_e;
|
||||
|
||||
if (top->type == json_integer)
|
||||
{
|
||||
top->type = json_double;
|
||||
top->u.dbl = (double) top->u.integer;
|
||||
}
|
||||
|
||||
num_digits = 0;
|
||||
flags &= ~ flag_num_zero;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!num_digits)
|
||||
{ sprintf (error, "%d:%d: Expected digit after `e`", cur_line, e_off);
|
||||
goto e_failed;
|
||||
}
|
||||
|
||||
top->u.dbl *= pow (10, (double) (flags & flag_num_e_negative ? - num_e : num_e));
|
||||
}
|
||||
|
||||
if (flags & flag_num_negative)
|
||||
{
|
||||
if (top->type == json_integer)
|
||||
top->u.integer = - top->u.integer;
|
||||
else
|
||||
top->u.dbl = - top->u.dbl;
|
||||
}
|
||||
|
||||
flags |= flag_next | flag_reproc;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
if (flags & flag_reproc)
|
||||
{
|
||||
flags &= ~ flag_reproc;
|
||||
-- i;
|
||||
}
|
||||
|
||||
if (flags & flag_next)
|
||||
{
|
||||
flags = (flags & ~ flag_next) | flag_need_comma;
|
||||
|
||||
if (!top->parent)
|
||||
{
|
||||
/* root value done */
|
||||
|
||||
flags |= flag_done;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (top->parent->type == json_array)
|
||||
flags |= flag_seek_value;
|
||||
|
||||
if (!state.first_pass)
|
||||
{
|
||||
json_value * parent = top->parent;
|
||||
|
||||
switch (parent->type)
|
||||
{
|
||||
case json_object:
|
||||
|
||||
parent->u.object.values
|
||||
[parent->u.object.length].value = top;
|
||||
|
||||
break;
|
||||
|
||||
case json_array:
|
||||
|
||||
parent->u.array.values
|
||||
[parent->u.array.length] = top;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
if ( (++ top->parent->u.array.length) > state.uint_max)
|
||||
goto e_overflow;
|
||||
|
||||
top = top->parent;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
alloc = root;
|
||||
}
|
||||
|
||||
return root;
|
||||
|
||||
e_unknown_value:
|
||||
|
||||
sprintf (error, "%d:%d: Unknown value", cur_line, e_off);
|
||||
goto e_failed;
|
||||
|
||||
e_alloc_failure:
|
||||
|
||||
strcpy (error, "Memory allocation failure");
|
||||
goto e_failed;
|
||||
|
||||
e_overflow:
|
||||
|
||||
sprintf (error, "%d:%d: Too long (caught overflow)", cur_line, e_off);
|
||||
goto e_failed;
|
||||
|
||||
e_failed:
|
||||
|
||||
if (error_buf)
|
||||
{
|
||||
if (*error)
|
||||
strcpy (error_buf, error);
|
||||
else
|
||||
strcpy (error_buf, "Unknown error");
|
||||
}
|
||||
|
||||
if (state.first_pass)
|
||||
alloc = root;
|
||||
|
||||
while (alloc)
|
||||
{
|
||||
top = alloc->_reserved.next_alloc;
|
||||
state.settings.mem_free (alloc, state.settings.user_data);
|
||||
alloc = top;
|
||||
}
|
||||
|
||||
if (!state.first_pass)
|
||||
json_value_free_ex (&state.settings, root);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
json_value * json_parse (const json_char * json, size_t length)
|
||||
{
|
||||
json_settings settings = { 0 };
|
||||
return json_parse_ex (&settings, json, length, 0);
|
||||
}
|
||||
|
||||
void json_value_free_ex (json_settings * settings, json_value * value)
|
||||
{
|
||||
json_value * cur_value;
|
||||
|
||||
if (!value)
|
||||
return;
|
||||
|
||||
value->parent = 0;
|
||||
|
||||
while (value)
|
||||
{
|
||||
switch (value->type)
|
||||
{
|
||||
case json_array:
|
||||
|
||||
if (!value->u.array.length)
|
||||
{
|
||||
settings->mem_free (value->u.array.values, settings->user_data);
|
||||
break;
|
||||
}
|
||||
|
||||
value = value->u.array.values [-- value->u.array.length];
|
||||
continue;
|
||||
|
||||
case json_object:
|
||||
|
||||
if (!value->u.object.length)
|
||||
{
|
||||
settings->mem_free (value->u.object.values, settings->user_data);
|
||||
break;
|
||||
}
|
||||
|
||||
value = value->u.object.values [-- value->u.object.length].value;
|
||||
continue;
|
||||
|
||||
case json_string:
|
||||
|
||||
settings->mem_free (value->u.string.ptr, settings->user_data);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
cur_value = value;
|
||||
value = value->parent;
|
||||
settings->mem_free (cur_value, settings->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
void json_value_free (json_value * value)
|
||||
{
|
||||
json_settings settings = { 0 };
|
||||
settings.mem_free = default_free;
|
||||
json_value_free_ex (&settings, value);
|
||||
}
|
468
server/src/main.cpp
Normal file
468
server/src/main.cpp
Normal file
@ -0,0 +1,468 @@
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#include <inttypes.h>
|
||||
#include <time.h>
|
||||
#include <detect.h>
|
||||
#include <system.h>
|
||||
#include <argparse.h>
|
||||
#include <json.h>
|
||||
#include "server.h"
|
||||
#include "main.h"
|
||||
|
||||
#if defined(CONF_FAMILY_UNIX)
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
#ifndef PRId64
|
||||
#define PRId64 "I64d"
|
||||
#endif
|
||||
|
||||
static volatile int gs_Running = 1;
|
||||
static volatile int gs_ReloadConfig = 0;
|
||||
|
||||
static void ExitFunc(int Signal)
|
||||
{
|
||||
printf("[EXIT] Caught signal %d\n", Signal);
|
||||
gs_Running = 0;
|
||||
}
|
||||
|
||||
static void ReloadFunc(int Signal)
|
||||
{
|
||||
printf("[RELOAD] Caught signal %d\n", Signal);
|
||||
gs_ReloadConfig = 1;
|
||||
}
|
||||
|
||||
CConfig::CConfig()
|
||||
{
|
||||
// Initialize to default values
|
||||
m_Verbose = false; // -v, --verbose
|
||||
str_copy(m_aConfigFile, "config.json", sizeof(m_aConfigFile)); // -c, --config
|
||||
str_copy(m_aWebDir, "../web/", sizeof(m_aJSONFile)); // -d, --web-dir
|
||||
str_copy(m_aTemplateFile, "template.html", sizeof(m_aTemplateFile));
|
||||
str_copy(m_aJSONFile, "json/stats.json", sizeof(m_aJSONFile));
|
||||
str_copy(m_aBindAddr, "", sizeof(m_aBindAddr)); // -b, --bind
|
||||
m_Port = 35601; // -p, --port
|
||||
}
|
||||
|
||||
CMain::CMain(CConfig Config) : m_Config(Config)
|
||||
{
|
||||
mem_zero(m_aClients, sizeof(m_aClients));
|
||||
for(int i = 0; i < NET_MAX_CLIENTS; i++)
|
||||
m_aClients[i].m_ClientNetID = -1;
|
||||
}
|
||||
|
||||
CMain::CClient *CMain::ClientNet(int ClientNetID)
|
||||
{
|
||||
if(ClientNetID < 0 || ClientNetID >= NET_MAX_CLIENTS)
|
||||
return 0;
|
||||
|
||||
for(int i = 0; i < NET_MAX_CLIENTS; i++)
|
||||
{
|
||||
if(Client(i)->m_ClientNetID == ClientNetID)
|
||||
return Client(i);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CMain::ClientNetToClient(int ClientNetID)
|
||||
{
|
||||
if(ClientNetID < 0 || ClientNetID >= NET_MAX_CLIENTS)
|
||||
return -1;
|
||||
|
||||
for(int i = 0; i < NET_MAX_CLIENTS; i++)
|
||||
{
|
||||
if(Client(i)->m_ClientNetID == ClientNetID)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void CMain::OnNewClient(int ClientNetID, int ClientID)
|
||||
{
|
||||
dbg_msg("main", "OnNewClient(ncid=%d, cid=%d)", ClientNetID, ClientID);
|
||||
Client(ClientID)->m_ClientNetID = ClientNetID;
|
||||
Client(ClientID)->m_ClientNetType = m_Server.Network()->ClientAddr(ClientNetID)->type;
|
||||
Client(ClientID)->m_TimeConnected = time_get();
|
||||
Client(ClientID)->m_Connected = true;
|
||||
|
||||
if(Client(ClientID)->m_ClientNetType == NETTYPE_IPV4)
|
||||
Client(ClientID)->m_Stats.m_Online4 = true;
|
||||
else if(Client(ClientID)->m_ClientNetType == NETTYPE_IPV6)
|
||||
Client(ClientID)->m_Stats.m_Online6 = true;
|
||||
}
|
||||
|
||||
void CMain::OnDelClient(int ClientNetID)
|
||||
{
|
||||
int ClientID = ClientNetToClient(ClientNetID);
|
||||
dbg_msg("main", "OnDelClient(ncid=%d, cid=%d)", ClientNetID, ClientID);
|
||||
if(ClientID >= 0 && ClientID < NET_MAX_CLIENTS)
|
||||
{
|
||||
Client(ClientID)->m_Connected = false;
|
||||
Client(ClientID)->m_ClientNetID = -1;
|
||||
Client(ClientID)->m_ClientNetType = NETTYPE_INVALID;
|
||||
mem_zero(&Client(ClientID)->m_Stats, sizeof(CClient::CStats));
|
||||
}
|
||||
}
|
||||
|
||||
int CMain::HandleMessage(int ClientNetID, char *pMessage)
|
||||
{
|
||||
CClient *pClient = ClientNet(ClientNetID);
|
||||
if(!pClient)
|
||||
return true;
|
||||
|
||||
if(str_comp_num(pMessage, "update", sizeof("update")-1) == 0)
|
||||
{
|
||||
char *pData = str_skip_whitespaces(&pMessage[sizeof("update")-1]);
|
||||
|
||||
// parse json data
|
||||
json_settings JsonSettings;
|
||||
mem_zero(&JsonSettings, sizeof(JsonSettings));
|
||||
char aError[256];
|
||||
json_value *pJsonData = json_parse_ex(&JsonSettings, pData, strlen(pData), aError);
|
||||
if(!pJsonData)
|
||||
{
|
||||
dbg_msg("main", "JSON Error: %s", aError);
|
||||
if(pClient->m_Stats.m_Pong)
|
||||
m_Server.Network()->Send(ClientNetID, "1");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// extract data
|
||||
const json_value &rStart = (*pJsonData);
|
||||
if(rStart["uptime"].type)
|
||||
pClient->m_Stats.m_Uptime = rStart["uptime"].u.integer;
|
||||
if(rStart["load_1"].type)
|
||||
pClient->m_Stats.m_Load_1 = rStart["load_1"].u.dbl;
|
||||
if(rStart["load_5"].type)
|
||||
pClient->m_Stats.m_Load_5 = rStart["load_5"].u.dbl;
|
||||
if(rStart["load_15"].type)
|
||||
pClient->m_Stats.m_Load_15 = rStart["load_15"].u.dbl;
|
||||
if(rStart["network_rx"].type)
|
||||
pClient->m_Stats.m_NetworkRx = rStart["network_rx"].u.integer;
|
||||
if(rStart["network_tx"].type)
|
||||
pClient->m_Stats.m_NetworkTx = rStart["network_tx"].u.integer;
|
||||
if(rStart["network_in"].type)
|
||||
pClient->m_Stats.m_NetworkIN = rStart["network_in"].u.integer;
|
||||
if(rStart["network_out"].type)
|
||||
pClient->m_Stats.m_NetworkOUT = rStart["network_out"].u.integer;
|
||||
if(rStart["memory_total"].type)
|
||||
pClient->m_Stats.m_MemTotal = rStart["memory_total"].u.integer;
|
||||
if(rStart["memory_used"].type)
|
||||
pClient->m_Stats.m_MemUsed = rStart["memory_used"].u.integer;
|
||||
if(rStart["swap_total"].type)
|
||||
pClient->m_Stats.m_SwapTotal = rStart["swap_total"].u.integer;
|
||||
if(rStart["swap_used"].type)
|
||||
pClient->m_Stats.m_SwapUsed = rStart["swap_used"].u.integer;
|
||||
if(rStart["hdd_total"].type)
|
||||
pClient->m_Stats.m_HDDTotal = rStart["hdd_total"].u.integer;
|
||||
if(rStart["hdd_used"].type)
|
||||
pClient->m_Stats.m_HDDUsed = rStart["hdd_used"].u.integer;
|
||||
if(rStart["cpu"].type)
|
||||
pClient->m_Stats.m_CPU = rStart["cpu"].u.dbl;
|
||||
if(rStart["online4"].type && pClient->m_ClientNetType == NETTYPE_IPV6)
|
||||
pClient->m_Stats.m_Online4 = rStart["online4"].u.boolean;
|
||||
if(rStart["online6"].type && pClient->m_ClientNetType == NETTYPE_IPV4)
|
||||
pClient->m_Stats.m_Online6 = rStart["online6"].u.boolean;
|
||||
if(rStart["ip_status"].type)
|
||||
pClient->m_Stats.m_IpStatus = rStart["ip_status"].u.boolean;
|
||||
if(rStart["custom"].type == json_string)
|
||||
str_copy(pClient->m_Stats.m_aCustom, rStart["custom"].u.string.ptr, sizeof(pClient->m_Stats.m_aCustom));
|
||||
|
||||
if(m_Config.m_Verbose)
|
||||
{
|
||||
if(rStart["online4"].type)
|
||||
dbg_msg("main", "Online4: %s\nUptime: %" PRId64 "\nIpStatus: %s\nLoad_1: %f\nLoad_5: %f\nLoad_15: %f\nNetworkRx: %" PRId64 "\nNetworkTx: %" PRId64 "\nNetworkIN: %" PRId64 "\nNetworkOUT: %" PRId64 "\nMemTotal: %" PRId64 "\nMemUsed: %" PRId64 "\nSwapTotal: %" PRId64 "\nSwapUsed: %" PRId64 "\nHDDTotal: %" PRId64 "\nHDDUsed: %" PRId64 "\nCPU: %f\n",
|
||||
rStart["online4"].u.boolean ? "true" : "false",
|
||||
pClient->m_Stats.m_Uptime,
|
||||
pClient->m_Stats.m_IpStatus ? "true" : "false",
|
||||
pClient->m_Stats.m_Load_1, pClient->m_Stats.m_Load_5, pClient->m_Stats.m_Load_15, pClient->m_Stats.m_NetworkRx, pClient->m_Stats.m_NetworkTx, pClient->m_Stats.m_NetworkIN, pClient->m_Stats.m_NetworkOUT, pClient->m_Stats.m_MemTotal, pClient->m_Stats.m_MemUsed, pClient->m_Stats.m_SwapTotal, pClient->m_Stats.m_SwapUsed, pClient->m_Stats.m_HDDTotal, pClient->m_Stats.m_HDDUsed, pClient->m_Stats.m_CPU);
|
||||
else if(rStart["online6"].type)
|
||||
dbg_msg("main", "Online6: %s\nUptime: %" PRId64 "\nIpStatus: %s\nLoad_1: %f\nLoad_5: %f\nLoad_15: %f\nNetworkRx: %" PRId64 "\nNetworkTx: %" PRId64 "\nNetworkIN: %" PRId64 "\nNetworkOUT: %" PRId64 "\nMemTotal: %" PRId64 "\nMemUsed: %" PRId64 "\nSwapTotal: %" PRId64 "\nSwapUsed: %" PRId64 "\nHDDTotal: %" PRId64 "\nHDDUsed: %" PRId64 "\nCPU: %f\n",
|
||||
rStart["online6"].u.boolean ? "true" : "false",
|
||||
pClient->m_Stats.m_Uptime,
|
||||
pClient->m_Stats.m_IpStatus ? "true" : "false",
|
||||
pClient->m_Stats.m_Load_1, pClient->m_Stats.m_Load_5, pClient->m_Stats.m_Load_15, pClient->m_Stats.m_NetworkRx, pClient->m_Stats.m_NetworkTx, pClient->m_Stats.m_NetworkIN, pClient->m_Stats.m_NetworkOUT, pClient->m_Stats.m_MemTotal, pClient->m_Stats.m_MemUsed, pClient->m_Stats.m_SwapTotal, pClient->m_Stats.m_SwapUsed, pClient->m_Stats.m_HDDTotal, pClient->m_Stats.m_HDDUsed, pClient->m_Stats.m_CPU);
|
||||
else
|
||||
dbg_msg("main", "Uptime: %" PRId64 "\nIpStatus: %s\nLoad_1: %f\nLoad_5: %f\nLoad_15: %f\nNetworkRx: %" PRId64 "\nNetworkTx: %" PRId64 "\nNetworkIN: %" PRId64 "\nNetworkOUT: %" PRId64 "\nMemTotal: %" PRId64 "\nMemUsed: %" PRId64 "\nSwapTotal: %" PRId64 "\nSwapUsed: %" PRId64 "\nHDDTotal: %" PRId64 "\nHDDUsed: %" PRId64 "\nCPU: %f\n",
|
||||
pClient->m_Stats.m_Uptime,
|
||||
pClient->m_Stats.m_IpStatus ? "true" : "false",
|
||||
pClient->m_Stats.m_Load_1, pClient->m_Stats.m_Load_5, pClient->m_Stats.m_Load_15, pClient->m_Stats.m_NetworkRx, pClient->m_Stats.m_NetworkTx, pClient->m_Stats.m_NetworkIN, pClient->m_Stats.m_NetworkOUT, pClient->m_Stats.m_MemTotal, pClient->m_Stats.m_MemUsed, pClient->m_Stats.m_SwapTotal, pClient->m_Stats.m_SwapUsed, pClient->m_Stats.m_HDDTotal, pClient->m_Stats.m_HDDUsed, pClient->m_Stats.m_CPU);
|
||||
}
|
||||
|
||||
// clean up
|
||||
json_value_free(pJsonData);
|
||||
|
||||
if(pClient->m_Stats.m_Pong)
|
||||
m_Server.Network()->Send(ClientNetID, "0");
|
||||
return 0;
|
||||
}
|
||||
else if(str_comp_num(pMessage, "pong", sizeof("pong")-1) == 0)
|
||||
{
|
||||
char *pData = str_skip_whitespaces(&pMessage[sizeof("pong")-1]);
|
||||
|
||||
if(!str_comp(pData, "0") || !str_comp(pData, "off"))
|
||||
pClient->m_Stats.m_Pong = false;
|
||||
else if(!str_comp(pData, "1") || !str_comp(pData, "on"))
|
||||
pClient->m_Stats.m_Pong = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(pClient->m_Stats.m_Pong)
|
||||
m_Server.Network()->Send(ClientNetID, "1");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void CMain::JSONUpdateThread(void *pUser)
|
||||
{
|
||||
CJSONUpdateThreadData *m_pJSONUpdateThreadData = (CJSONUpdateThreadData *)pUser;
|
||||
CClient *pClients = m_pJSONUpdateThreadData->pClients;
|
||||
CConfig *pConfig = m_pJSONUpdateThreadData->pConfig;
|
||||
|
||||
while(gs_Running)
|
||||
{
|
||||
char aFileBuf[2048*NET_MAX_CLIENTS];
|
||||
char *pBuf = aFileBuf;
|
||||
|
||||
str_format(pBuf, sizeof(aFileBuf), "{\n\"servers\": [\n");
|
||||
pBuf += strlen(pBuf);
|
||||
|
||||
for(int i = 0; i < NET_MAX_CLIENTS; i++)
|
||||
{
|
||||
if(!pClients[i].m_Active || pClients[i].m_Disabled)
|
||||
continue;
|
||||
|
||||
if(pClients[i].m_Connected)
|
||||
{
|
||||
// Uptime
|
||||
char aUptime[16];
|
||||
int Days = pClients[i].m_Stats.m_Uptime/60.0/60.0/24.0;
|
||||
if(Days > 0)
|
||||
{
|
||||
if(Days > 1)
|
||||
str_format(aUptime, sizeof(aUptime), "%d 天", Days);
|
||||
else
|
||||
str_format(aUptime, sizeof(aUptime), "%d 天", Days);
|
||||
}
|
||||
else
|
||||
str_format(aUptime, sizeof(aUptime), "%02d:%02d:%02d", (int)(pClients[i].m_Stats.m_Uptime/60.0/60.0), (int)((pClients[i].m_Stats.m_Uptime/60)%60), (int)((pClients[i].m_Stats.m_Uptime)%60));
|
||||
|
||||
str_format(pBuf, sizeof(aFileBuf) - (pBuf - aFileBuf),
|
||||
"{ \"name\": \"%s\",\"type\": \"%s\",\"host\": \"%s\",\"location\": \"%s\",\"online4\": %s, \"online6\": %s,\"ip_status\": %s,\"uptime\": \"%s\",\"load_1\": %.2f, \"load_5\": %.2f, \"load_15\": %.2f,\"network_rx\": %" PRId64 ", \"network_tx\": %" PRId64 ", \"network_in\": %" PRId64 ", \"network_out\": %" PRId64 ", \"cpu\": %d, \"memory_total\": %" PRId64 ", \"memory_used\": %" PRId64 ", \"swap_total\": %" PRId64 ", \"swap_used\": %" PRId64 ", \"hdd_total\": %" PRId64 ", \"hdd_used\": %" PRId64 ", \"custom\": \"%s\" },\n",
|
||||
pClients[i].m_aName,pClients[i].m_aType,pClients[i].m_aHost,pClients[i].m_aLocation,
|
||||
pClients[i].m_Stats.m_Online4 ? "true" : "false",pClients[i].m_Stats.m_Online6 ? "true" : "false",pClients[i].m_Stats.m_IpStatus ? "true": "false",
|
||||
aUptime, pClients[i].m_Stats.m_Load_1, pClients[i].m_Stats.m_Load_5, pClients[i].m_Stats.m_Load_15, pClients[i].m_Stats.m_NetworkRx, pClients[i].m_Stats.m_NetworkTx, pClients[i].m_Stats.m_NetworkIN, pClients[i].m_Stats.m_NetworkOUT, (int)pClients[i].m_Stats.m_CPU, pClients[i].m_Stats.m_MemTotal, pClients[i].m_Stats.m_MemUsed, pClients[i].m_Stats.m_SwapTotal, pClients[i].m_Stats.m_SwapUsed, pClients[i].m_Stats.m_HDDTotal, pClients[i].m_Stats.m_HDDUsed, pClients[i].m_Stats.m_aCustom);
|
||||
pBuf += strlen(pBuf);
|
||||
}
|
||||
else
|
||||
{
|
||||
str_format(pBuf, sizeof(aFileBuf) - (pBuf - aFileBuf), "{ \"name\": \"%s\", \"type\": \"%s\", \"host\": \"%s\", \"location\": \"%s\", \"online4\": false, \"online6\": false },\n",
|
||||
pClients[i].m_aName, pClients[i].m_aType, pClients[i].m_aHost, pClients[i].m_aLocation);
|
||||
pBuf += strlen(pBuf);
|
||||
}
|
||||
}
|
||||
if(!m_pJSONUpdateThreadData->m_ReloadRequired)
|
||||
str_format(pBuf - 2, sizeof(aFileBuf) - (pBuf - aFileBuf), "\n],\n\"updated\": \"%lld\"\n}", (long long)time(/*ago*/0));
|
||||
else
|
||||
{
|
||||
str_format(pBuf - 2, sizeof(aFileBuf) - (pBuf - aFileBuf), "\n],\n\"updated\": \"%lld\",\n\"reload\": true\n}", (long long)time(/*ago*/0));
|
||||
m_pJSONUpdateThreadData->m_ReloadRequired--;
|
||||
}
|
||||
pBuf += strlen(pBuf);
|
||||
|
||||
char aJSONFileTmp[1024];
|
||||
str_format(aJSONFileTmp, sizeof(aJSONFileTmp), "%s~", pConfig->m_aJSONFile);
|
||||
IOHANDLE File = io_open(aJSONFileTmp, IOFLAG_WRITE);
|
||||
if(!File)
|
||||
{
|
||||
dbg_msg("main", "Couldn't open %s", aJSONFileTmp);
|
||||
exit(1);
|
||||
}
|
||||
io_write(File, aFileBuf, (pBuf - aFileBuf));
|
||||
io_flush(File);
|
||||
io_close(File);
|
||||
fs_rename(aJSONFileTmp, pConfig->m_aJSONFile);
|
||||
thread_sleep(1000);
|
||||
}
|
||||
fs_remove(pConfig->m_aJSONFile);
|
||||
}
|
||||
|
||||
int CMain::ReadConfig()
|
||||
{
|
||||
// read and parse config
|
||||
IOHANDLE File = io_open(m_Config.m_aConfigFile, IOFLAG_READ);
|
||||
if(!File)
|
||||
{
|
||||
dbg_msg("main", "Couldn't open %s", m_Config.m_aConfigFile);
|
||||
return 1;
|
||||
}
|
||||
int FileSize = (int)io_length(File);
|
||||
char *pFileData = (char *)mem_alloc(FileSize + 1, 1);
|
||||
|
||||
io_read(File, pFileData, FileSize);
|
||||
pFileData[FileSize] = 0;
|
||||
io_close(File);
|
||||
|
||||
// parse json data
|
||||
json_settings JsonSettings;
|
||||
mem_zero(&JsonSettings, sizeof(JsonSettings));
|
||||
char aError[256];
|
||||
json_value *pJsonData = json_parse_ex(&JsonSettings, pFileData, strlen(pFileData), aError);
|
||||
if(!pJsonData)
|
||||
{
|
||||
dbg_msg("main", "JSON Error in file %s: %s", m_Config.m_aConfigFile, aError);
|
||||
mem_free(pFileData);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// reset clients
|
||||
for(int i = 0; i < NET_MAX_CLIENTS; i++)
|
||||
{
|
||||
if(!Client(i)->m_Active || !Client(i)->m_Connected)
|
||||
continue;
|
||||
|
||||
m_Server.Network()->Drop(Client(i)->m_ClientNetID, "Server reloading...");
|
||||
}
|
||||
mem_zero(m_aClients, sizeof(m_aClients));
|
||||
for(int i = 0; i < NET_MAX_CLIENTS; i++)
|
||||
m_aClients[i].m_ClientNetID = -1;
|
||||
|
||||
// extract data
|
||||
int ID = 0;
|
||||
const json_value &rStart = (*pJsonData)["servers"];
|
||||
if(rStart.type == json_array)
|
||||
{
|
||||
for(unsigned i = 0; i < rStart.u.array.length; i++)
|
||||
{
|
||||
if(ID < 0 || ID >= NET_MAX_CLIENTS)
|
||||
continue;
|
||||
|
||||
Client(ID)->m_Active = true;
|
||||
Client(ID)->m_Disabled = rStart[i]["disabled"].u.boolean;
|
||||
str_copy(Client(ID)->m_aName, rStart[i]["name"].u.string.ptr, sizeof(Client(ID)->m_aName));
|
||||
str_copy(Client(ID)->m_aUsername, rStart[i]["username"].u.string.ptr, sizeof(Client(ID)->m_aUsername));
|
||||
str_copy(Client(ID)->m_aType, rStart[i]["type"].u.string.ptr, sizeof(Client(ID)->m_aType));
|
||||
str_copy(Client(ID)->m_aHost, rStart[i]["host"].u.string.ptr, sizeof(Client(ID)->m_aHost));
|
||||
str_copy(Client(ID)->m_aLocation, rStart[i]["location"].u.string.ptr, sizeof(Client(ID)->m_aLocation));
|
||||
str_copy(Client(ID)->m_aPassword, rStart[i]["password"].u.string.ptr, sizeof(Client(ID)->m_aPassword));
|
||||
|
||||
if(m_Config.m_Verbose)
|
||||
{
|
||||
if(Client(ID)->m_Disabled)
|
||||
dbg_msg("main", "[#%d: Name: \"%s\", Username: \"%s\", Type: \"%s\", Host: \"%s\", Location: \"%s\", Password: \"%s\"]",
|
||||
ID, Client(ID)->m_aName, Client(ID)->m_aUsername, Client(ID)->m_aType, Client(ID)->m_aHost, Client(ID)->m_aLocation, Client(ID)->m_aPassword);
|
||||
else
|
||||
dbg_msg("main", "#%d: Name: \"%s\", Username: \"%s\", Type: \"%s\", Host: \"%s\", Location: \"%s\", Password: \"%s\"",
|
||||
ID, Client(ID)->m_aName, Client(ID)->m_aUsername, Client(ID)->m_aType, Client(ID)->m_aHost, Client(ID)->m_aLocation, Client(ID)->m_aPassword);
|
||||
|
||||
}
|
||||
ID++;
|
||||
}
|
||||
}
|
||||
|
||||
// clean up
|
||||
json_value_free(pJsonData);
|
||||
mem_free(pFileData);
|
||||
|
||||
// tell clients to reload the page
|
||||
m_JSONUpdateThreadData.m_ReloadRequired = 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CMain::Run()
|
||||
{
|
||||
if(m_Server.Init(this, m_Config.m_aBindAddr, m_Config.m_Port))
|
||||
return 1;
|
||||
|
||||
if(ReadConfig())
|
||||
return 1;
|
||||
|
||||
// Start JSON Update Thread
|
||||
m_JSONUpdateThreadData.m_ReloadRequired = 2;
|
||||
m_JSONUpdateThreadData.pClients = m_aClients;
|
||||
m_JSONUpdateThreadData.pConfig = &m_Config;
|
||||
void *LoadThread = thread_create(JSONUpdateThread, &m_JSONUpdateThreadData);
|
||||
//thread_detach(LoadThread);
|
||||
|
||||
while(gs_Running)
|
||||
{
|
||||
if(gs_ReloadConfig)
|
||||
{
|
||||
if(ReadConfig())
|
||||
return 1;
|
||||
m_Server.NetBan()->UnbanAll();
|
||||
gs_ReloadConfig = 0;
|
||||
}
|
||||
|
||||
m_Server.Update();
|
||||
|
||||
// wait for incomming data
|
||||
net_socket_read_wait(*m_Server.Network()->Socket(), 10);
|
||||
}
|
||||
|
||||
dbg_msg("server", "Closing.");
|
||||
m_Server.Network()->Close();
|
||||
thread_wait(LoadThread);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[])
|
||||
{
|
||||
int RetVal;
|
||||
dbg_logger_stdout();
|
||||
|
||||
#if defined(CONF_FAMILY_UNIX)
|
||||
signal(SIGINT, ExitFunc);
|
||||
signal(SIGTERM, ExitFunc);
|
||||
signal(SIGQUIT, ExitFunc);
|
||||
signal(SIGHUP, ReloadFunc);
|
||||
#endif
|
||||
|
||||
char aUsage[128];
|
||||
CConfig Config;
|
||||
str_format(aUsage, sizeof(aUsage), "%s [options]", argv[0]);
|
||||
const char *pConfigFile = 0;
|
||||
const char *pWebDir = 0;
|
||||
const char *pBindAddr = 0;
|
||||
|
||||
struct argparse_option aOptions[] = {
|
||||
OPT_HELP(),
|
||||
OPT_BOOLEAN('v', "verbose", &Config.m_Verbose, "Verbose output", 0),
|
||||
OPT_STRING('c', "config", &pConfigFile, "Config file to use", 0),
|
||||
OPT_STRING('d', "web-dir", &pWebDir, "Location of the web directory", 0),
|
||||
OPT_STRING('b', "bind", &pBindAddr, "Bind to address", 0),
|
||||
OPT_INTEGER('p', "port", &Config.m_Port, "Listen on port", 0),
|
||||
OPT_END(),
|
||||
};
|
||||
struct argparse Argparse;
|
||||
argparse_init(&Argparse, aOptions, aUsage, 0);
|
||||
argc = argparse_parse(&Argparse, argc, argv);
|
||||
|
||||
if(pConfigFile)
|
||||
str_copy(Config.m_aConfigFile, pConfigFile, sizeof(Config.m_aConfigFile));
|
||||
if(pWebDir)
|
||||
str_copy(Config.m_aWebDir, pWebDir, sizeof(Config.m_aWebDir));
|
||||
if(pBindAddr)
|
||||
str_copy(Config.m_aBindAddr, pBindAddr, sizeof(Config.m_aBindAddr));
|
||||
|
||||
if(Config.m_aWebDir[strlen(Config.m_aWebDir)-1] != '/')
|
||||
str_append(Config.m_aWebDir, "/", sizeof(Config.m_aWebDir));
|
||||
if(!fs_is_dir(Config.m_aWebDir))
|
||||
{
|
||||
dbg_msg("main", "ERROR: Can't find web directory: %s", Config.m_aWebDir);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char aTmp[1024];
|
||||
str_format(aTmp, sizeof(aTmp), "%s%s", Config.m_aWebDir, Config.m_aJSONFile);
|
||||
str_copy(Config.m_aJSONFile, aTmp, sizeof(Config.m_aJSONFile));
|
||||
|
||||
CMain Main(Config);
|
||||
RetVal = Main.Run();
|
||||
|
||||
return RetVal;
|
||||
}
|
93
server/src/main.h
Normal file
93
server/src/main.h
Normal file
@ -0,0 +1,93 @@
|
||||
#ifndef MAIN_H
|
||||
#define MAIN_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "server.h"
|
||||
|
||||
class CConfig
|
||||
{
|
||||
public:
|
||||
bool m_Verbose;
|
||||
char m_aConfigFile[1024];
|
||||
char m_aWebDir[1024];
|
||||
char m_aTemplateFile[1024];
|
||||
char m_aJSONFile[1024];
|
||||
char m_aBindAddr[256];
|
||||
int m_Port;
|
||||
|
||||
CConfig();
|
||||
};
|
||||
|
||||
class CMain
|
||||
{
|
||||
CConfig m_Config;
|
||||
CServer m_Server;
|
||||
|
||||
struct CClient
|
||||
{
|
||||
bool m_Active;
|
||||
bool m_Disabled;
|
||||
bool m_Connected;
|
||||
int m_ClientNetID;
|
||||
int m_ClientNetType;
|
||||
char m_aUsername[128];
|
||||
char m_aName[128];
|
||||
char m_aType[128];
|
||||
char m_aHost[128];
|
||||
char m_aLocation[128];
|
||||
char m_aPassword[128];
|
||||
|
||||
int64 m_TimeConnected;
|
||||
int64 m_LastUpdate;
|
||||
|
||||
struct CStats
|
||||
{
|
||||
bool m_Online4;
|
||||
bool m_Online6;
|
||||
bool m_IpStatus; //mh361 or mh370, mourn mh370, 2014-03-0 01:20 lost from all over the world.
|
||||
int64_t m_Uptime;
|
||||
double m_Load_1; //1 minutes load average
|
||||
double m_Load_5; //5 minutes load average
|
||||
double m_Load_15; //15 minutes load average
|
||||
int64_t m_NetworkRx;
|
||||
int64_t m_NetworkTx;
|
||||
int64_t m_NetworkIN;
|
||||
int64_t m_NetworkOUT;
|
||||
int64_t m_MemTotal;
|
||||
int64_t m_MemUsed;
|
||||
int64_t m_SwapTotal;
|
||||
int64_t m_SwapUsed;
|
||||
int64_t m_HDDTotal;
|
||||
int64_t m_HDDUsed;
|
||||
double m_CPU;
|
||||
char m_aCustom[512];
|
||||
// Options
|
||||
bool m_Pong;
|
||||
} m_Stats;
|
||||
} m_aClients[NET_MAX_CLIENTS];
|
||||
|
||||
struct CJSONUpdateThreadData
|
||||
{
|
||||
CClient *pClients;
|
||||
CConfig *pConfig;
|
||||
volatile short m_ReloadRequired;
|
||||
} m_JSONUpdateThreadData;
|
||||
|
||||
static void JSONUpdateThread(void *pUser);
|
||||
public:
|
||||
CMain(CConfig Config);
|
||||
|
||||
void OnNewClient(int ClienNettID, int ClientID);
|
||||
void OnDelClient(int ClientNetID);
|
||||
int HandleMessage(int ClientNetID, char *pMessage);
|
||||
int ReadConfig();
|
||||
int Run();
|
||||
|
||||
CClient *Client(int ClientID) { return &m_aClients[ClientID]; }
|
||||
CClient *ClientNet(int ClientNetID);
|
||||
const CConfig *Config() const { return &m_Config; }
|
||||
int ClientNetToClient(int ClientNetID);
|
||||
};
|
||||
|
||||
|
||||
#endif
|
463
server/src/netban.cpp
Normal file
463
server/src/netban.cpp
Normal file
@ -0,0 +1,463 @@
|
||||
#include <math.h>
|
||||
#include "netban.h"
|
||||
|
||||
bool CNetBan::StrAllnum(const char *pStr)
|
||||
{
|
||||
while(*pStr)
|
||||
{
|
||||
if(!(*pStr >= '0' && *pStr <= '9'))
|
||||
return false;
|
||||
pStr++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
CNetBan::CNetHash::CNetHash(const NETADDR *pAddr)
|
||||
{
|
||||
if(pAddr->type==NETTYPE_IPV4)
|
||||
m_Hash = (pAddr->ip[0]+pAddr->ip[1]+pAddr->ip[2]+pAddr->ip[3])&0xFF;
|
||||
else
|
||||
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]+
|
||||
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;
|
||||
m_HashIndex = 0;
|
||||
}
|
||||
|
||||
CNetBan::CNetHash::CNetHash(const CNetRange *pRange)
|
||||
{
|
||||
m_Hash = 0;
|
||||
m_HashIndex = 0;
|
||||
for(int i = 0; pRange->m_LB.ip[i] == pRange->m_UB.ip[i]; ++i)
|
||||
{
|
||||
m_Hash += pRange->m_LB.ip[i];
|
||||
++m_HashIndex;
|
||||
}
|
||||
m_Hash &= 0xFF;
|
||||
}
|
||||
|
||||
int CNetBan::CNetHash::MakeHashArray(const NETADDR *pAddr, CNetHash aHash[17])
|
||||
{
|
||||
int Length = pAddr->type==NETTYPE_IPV4 ? 4 : 16;
|
||||
aHash[0].m_Hash = 0;
|
||||
aHash[0].m_HashIndex = 0;
|
||||
for(int i = 1, Sum = 0; i <= Length; ++i)
|
||||
{
|
||||
Sum += pAddr->ip[i-1];
|
||||
aHash[i].m_Hash = Sum&0xFF;
|
||||
aHash[i].m_HashIndex = i%Length;
|
||||
}
|
||||
return Length;
|
||||
}
|
||||
|
||||
|
||||
template<class T, int HashCount>
|
||||
typename CNetBan::CBan<T> *CNetBan::CBanPool<T, HashCount>::Add(const T *pData, const CBanInfo *pInfo, const CNetHash *pNetHash)
|
||||
{
|
||||
if(!m_pFirstFree)
|
||||
return 0;
|
||||
|
||||
// create new ban
|
||||
CBan<T> *pBan = m_pFirstFree;
|
||||
pBan->m_Data = *pData;
|
||||
pBan->m_Info = *pInfo;
|
||||
pBan->m_NetHash = *pNetHash;
|
||||
if(pBan->m_pNext)
|
||||
pBan->m_pNext->m_pPrev = pBan->m_pPrev;
|
||||
if(pBan->m_pPrev)
|
||||
pBan->m_pPrev->m_pNext = pBan->m_pNext;
|
||||
else
|
||||
m_pFirstFree = pBan->m_pNext;
|
||||
|
||||
// add it to the hash list
|
||||
if(m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash])
|
||||
m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]->m_pHashPrev = pBan;
|
||||
pBan->m_pHashPrev = 0;
|
||||
pBan->m_pHashNext = m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash];
|
||||
m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash] = pBan;
|
||||
|
||||
// insert it into the used list
|
||||
if(m_pFirstUsed)
|
||||
{
|
||||
for(CBan<T> *p = m_pFirstUsed; ; p = p->m_pNext)
|
||||
{
|
||||
if(p->m_Info.m_Expires == CBanInfo::EXPIRES_NEVER || (pInfo->m_Expires != CBanInfo::EXPIRES_NEVER && pInfo->m_Expires <= p->m_Info.m_Expires))
|
||||
{
|
||||
// insert before
|
||||
pBan->m_pNext = p;
|
||||
pBan->m_pPrev = p->m_pPrev;
|
||||
if(p->m_pPrev)
|
||||
p->m_pPrev->m_pNext = pBan;
|
||||
else
|
||||
m_pFirstUsed = pBan;
|
||||
p->m_pPrev = pBan;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!p->m_pNext)
|
||||
{
|
||||
// last entry
|
||||
p->m_pNext = pBan;
|
||||
pBan->m_pPrev = p;
|
||||
pBan->m_pNext = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pFirstUsed = pBan;
|
||||
pBan->m_pNext = pBan->m_pPrev = 0;
|
||||
}
|
||||
|
||||
// update ban count
|
||||
++m_CountUsed;
|
||||
|
||||
return pBan;
|
||||
}
|
||||
|
||||
template<class T, int HashCount>
|
||||
int CNetBan::CBanPool<T, HashCount>::Remove(CBan<T> *pBan)
|
||||
{
|
||||
if(pBan == 0)
|
||||
return -1;
|
||||
|
||||
// remove from hash list
|
||||
if(pBan->m_pHashNext)
|
||||
pBan->m_pHashNext->m_pHashPrev = pBan->m_pHashPrev;
|
||||
if(pBan->m_pHashPrev)
|
||||
pBan->m_pHashPrev->m_pHashNext = pBan->m_pHashNext;
|
||||
else
|
||||
m_paaHashList[pBan->m_NetHash.m_HashIndex][pBan->m_NetHash.m_Hash] = pBan->m_pHashNext;
|
||||
pBan->m_pHashNext = pBan->m_pHashPrev = 0;
|
||||
|
||||
// remove from used list
|
||||
if(pBan->m_pNext)
|
||||
pBan->m_pNext->m_pPrev = pBan->m_pPrev;
|
||||
if(pBan->m_pPrev)
|
||||
pBan->m_pPrev->m_pNext = pBan->m_pNext;
|
||||
else
|
||||
m_pFirstUsed = pBan->m_pNext;
|
||||
|
||||
// add to recycle list
|
||||
if(m_pFirstFree)
|
||||
m_pFirstFree->m_pPrev = pBan;
|
||||
pBan->m_pPrev = 0;
|
||||
pBan->m_pNext = m_pFirstFree;
|
||||
m_pFirstFree = pBan;
|
||||
|
||||
// update ban count
|
||||
--m_CountUsed;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class T, int HashCount>
|
||||
void CNetBan::CBanPool<T, HashCount>::Update(CBan<CDataType> *pBan, const CBanInfo *pInfo)
|
||||
{
|
||||
pBan->m_Info = *pInfo;
|
||||
|
||||
// remove from used list
|
||||
if(pBan->m_pNext)
|
||||
pBan->m_pNext->m_pPrev = pBan->m_pPrev;
|
||||
if(pBan->m_pPrev)
|
||||
pBan->m_pPrev->m_pNext = pBan->m_pNext;
|
||||
else
|
||||
m_pFirstUsed = pBan->m_pNext;
|
||||
|
||||
// insert it into the used list
|
||||
if(m_pFirstUsed)
|
||||
{
|
||||
for(CBan<T> *p = m_pFirstUsed; ; p = p->m_pNext)
|
||||
{
|
||||
if(p->m_Info.m_Expires == CBanInfo::EXPIRES_NEVER || (pInfo->m_Expires != CBanInfo::EXPIRES_NEVER && pInfo->m_Expires <= p->m_Info.m_Expires))
|
||||
{
|
||||
// insert before
|
||||
pBan->m_pNext = p;
|
||||
pBan->m_pPrev = p->m_pPrev;
|
||||
if(p->m_pPrev)
|
||||
p->m_pPrev->m_pNext = pBan;
|
||||
else
|
||||
m_pFirstUsed = pBan;
|
||||
p->m_pPrev = pBan;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!p->m_pNext)
|
||||
{
|
||||
// last entry
|
||||
p->m_pNext = pBan;
|
||||
pBan->m_pPrev = p;
|
||||
pBan->m_pNext = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pFirstUsed = pBan;
|
||||
pBan->m_pNext = pBan->m_pPrev = 0;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T, int HashCount>
|
||||
void CNetBan::CBanPool<T, HashCount>::Reset()
|
||||
{
|
||||
mem_zero(m_paaHashList, sizeof(m_paaHashList));
|
||||
mem_zero(m_aBans, sizeof(m_aBans));
|
||||
m_pFirstUsed = 0;
|
||||
m_CountUsed = 0;
|
||||
|
||||
for(int i = 1; i < MAX_BANS-1; ++i)
|
||||
{
|
||||
m_aBans[i].m_pNext = &m_aBans[i+1];
|
||||
m_aBans[i].m_pPrev = &m_aBans[i-1];
|
||||
}
|
||||
|
||||
m_aBans[0].m_pNext = &m_aBans[1];
|
||||
m_aBans[MAX_BANS-1].m_pPrev = &m_aBans[MAX_BANS-2];
|
||||
m_pFirstFree = &m_aBans[0];
|
||||
}
|
||||
|
||||
template<class T, int HashCount>
|
||||
typename CNetBan::CBan<T> *CNetBan::CBanPool<T, HashCount>::Get(int Index) const
|
||||
{
|
||||
if(Index < 0 || Index >= Num())
|
||||
return 0;
|
||||
|
||||
for(CNetBan::CBan<T> *pBan = m_pFirstUsed; pBan; pBan = pBan->m_pNext, --Index)
|
||||
{
|
||||
if(Index == 0)
|
||||
return pBan;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
template<class T>
|
||||
void CNetBan::MakeBanInfo(const CBan<T> *pBan, char *pBuf, unsigned BuffSize, int Type) const
|
||||
{
|
||||
if(pBan == 0 || pBuf == 0)
|
||||
{
|
||||
if(BuffSize > 0)
|
||||
pBuf[0] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// build type based part
|
||||
char aBuf[256];
|
||||
if(Type == MSGTYPE_PLAYER)
|
||||
str_copy(aBuf, "You have been banned", sizeof(aBuf));
|
||||
else
|
||||
{
|
||||
char aTemp[256];
|
||||
switch(Type)
|
||||
{
|
||||
case MSGTYPE_LIST:
|
||||
str_format(aBuf, sizeof(aBuf), "%s banned", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break;
|
||||
case MSGTYPE_BANADD:
|
||||
str_format(aBuf, sizeof(aBuf), "banned %s", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break;
|
||||
case MSGTYPE_BANREM:
|
||||
str_format(aBuf, sizeof(aBuf), "unbanned %s", NetToString(&pBan->m_Data, aTemp, sizeof(aTemp))); break;
|
||||
default:
|
||||
aBuf[0] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// add info part
|
||||
if(pBan->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER)
|
||||
{
|
||||
int Mins = ((pBan->m_Info.m_Expires-time_timestamp()) + 59) / 60;
|
||||
if(Mins <= 1)
|
||||
str_format(pBuf, BuffSize, "%s for 1 minute (%s)", aBuf, pBan->m_Info.m_aReason);
|
||||
else
|
||||
str_format(pBuf, BuffSize, "%s for %d minutes (%s)", aBuf, Mins, pBan->m_Info.m_aReason);
|
||||
}
|
||||
else
|
||||
str_format(pBuf, BuffSize, "%s for life (%s)", aBuf, pBan->m_Info.m_aReason);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
int CNetBan::Ban(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason)
|
||||
{
|
||||
// do not ban localhost
|
||||
if(NetMatch(pData, &m_LocalhostIPV4) || NetMatch(pData, &m_LocalhostIPV6))
|
||||
{
|
||||
dbg_msg("net_ban", "ban failed (localhost)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Stamp = Seconds > 0 ? time_timestamp()+Seconds : CBanInfo::EXPIRES_NEVER;
|
||||
|
||||
// set up info
|
||||
CBanInfo Info = {0};
|
||||
Info.m_Expires = Stamp;
|
||||
str_copy(Info.m_aReason, pReason, sizeof(Info.m_aReason));
|
||||
|
||||
// check if it already exists
|
||||
CNetHash NetHash(pData);
|
||||
CBan<typename T::CDataType> *pBan = pBanPool->Find(pData, &NetHash);
|
||||
if(pBan)
|
||||
{
|
||||
// adjust the ban
|
||||
pBanPool->Update(pBan, &Info);
|
||||
char aBuf[128];
|
||||
MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_LIST);
|
||||
dbg_msg("net_ban", aBuf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// add ban and print result
|
||||
pBan = pBanPool->Add(pData, &Info, &NetHash);
|
||||
if(pBan)
|
||||
{
|
||||
char aBuf[128];
|
||||
MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_BANADD);
|
||||
dbg_msg("net_ban", aBuf);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
dbg_msg("net_ban", "ban failed (full banlist)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
int CNetBan::Unban(T *pBanPool, const typename T::CDataType *pData)
|
||||
{
|
||||
CNetHash NetHash(pData);
|
||||
CBan<typename T::CDataType> *pBan = pBanPool->Find(pData, &NetHash);
|
||||
if(pBan)
|
||||
{
|
||||
char aBuf[256];
|
||||
MakeBanInfo(pBan, aBuf, sizeof(aBuf), MSGTYPE_BANREM);
|
||||
pBanPool->Remove(pBan);
|
||||
dbg_msg("net_ban", aBuf);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
dbg_msg("net_ban", "unban failed (invalid entry)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
void CNetBan::Init()
|
||||
{
|
||||
m_BanAddrPool.Reset();
|
||||
m_BanRangePool.Reset();
|
||||
|
||||
net_host_lookup("localhost", &m_LocalhostIPV4, NETTYPE_IPV4);
|
||||
net_host_lookup("localhost", &m_LocalhostIPV6, NETTYPE_IPV6);
|
||||
}
|
||||
|
||||
void CNetBan::Update()
|
||||
{
|
||||
int Now = time_timestamp();
|
||||
|
||||
// remove expired bans
|
||||
char aBuf[256], aNetStr[256];
|
||||
while(m_BanAddrPool.First() && m_BanAddrPool.First()->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER && m_BanAddrPool.First()->m_Info.m_Expires < Now)
|
||||
{
|
||||
str_format(aBuf, sizeof(aBuf), "ban %s expired", NetToString(&m_BanAddrPool.First()->m_Data, aNetStr, sizeof(aNetStr)));
|
||||
dbg_msg("net_ban", aBuf);
|
||||
m_BanAddrPool.Remove(m_BanAddrPool.First());
|
||||
}
|
||||
while(m_BanRangePool.First() && m_BanRangePool.First()->m_Info.m_Expires != CBanInfo::EXPIRES_NEVER && m_BanRangePool.First()->m_Info.m_Expires < Now)
|
||||
{
|
||||
str_format(aBuf, sizeof(aBuf), "ban %s expired", NetToString(&m_BanRangePool.First()->m_Data, aNetStr, sizeof(aNetStr)));
|
||||
dbg_msg("net_ban", aBuf);
|
||||
m_BanRangePool.Remove(m_BanRangePool.First());
|
||||
}
|
||||
}
|
||||
|
||||
int CNetBan::BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason)
|
||||
{
|
||||
return Ban(&m_BanAddrPool, pAddr, Seconds, pReason);
|
||||
}
|
||||
|
||||
int CNetBan::BanRange(const CNetRange *pRange, int Seconds, const char *pReason)
|
||||
{
|
||||
if(pRange->IsValid())
|
||||
return Ban(&m_BanRangePool, pRange, Seconds, pReason);
|
||||
|
||||
dbg_msg("net_ban", "ban failed (invalid range)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int CNetBan::UnbanByAddr(const NETADDR *pAddr)
|
||||
{
|
||||
return Unban(&m_BanAddrPool, pAddr);
|
||||
}
|
||||
|
||||
int CNetBan::UnbanByRange(const CNetRange *pRange)
|
||||
{
|
||||
if(pRange->IsValid())
|
||||
return Unban(&m_BanRangePool, pRange);
|
||||
|
||||
dbg_msg("net_ban", "ban failed (invalid range)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int CNetBan::UnbanByIndex(int Index)
|
||||
{
|
||||
int Result;
|
||||
char aBuf[256];
|
||||
CBanAddr *pBan = m_BanAddrPool.Get(Index);
|
||||
if(pBan)
|
||||
{
|
||||
NetToString(&pBan->m_Data, aBuf, sizeof(aBuf));
|
||||
Result = m_BanAddrPool.Remove(pBan);
|
||||
}
|
||||
else
|
||||
{
|
||||
CBanRange *pBan = m_BanRangePool.Get(Index-m_BanAddrPool.Num());
|
||||
if(pBan)
|
||||
{
|
||||
NetToString(&pBan->m_Data, aBuf, sizeof(aBuf));
|
||||
Result = m_BanRangePool.Remove(pBan);
|
||||
}
|
||||
else
|
||||
{
|
||||
dbg_msg("net_ban", "unban failed (invalid index)");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
char aMsg[256];
|
||||
str_format(aMsg, sizeof(aMsg), "unbanned index %i (%s)", Index, aBuf);
|
||||
dbg_msg("net_ban", aMsg);
|
||||
return Result;
|
||||
}
|
||||
|
||||
void CNetBan::UnbanAll()
|
||||
{
|
||||
m_BanAddrPool.Reset();
|
||||
m_BanRangePool.Reset();
|
||||
}
|
||||
|
||||
bool CNetBan::IsBanned(const NETADDR *pAddr, char *pBuf, unsigned BufferSize) const
|
||||
{
|
||||
CNetHash aHash[17];
|
||||
int Length = CNetHash::MakeHashArray(pAddr, aHash);
|
||||
|
||||
// check ban adresses
|
||||
CBanAddr *pBan = m_BanAddrPool.Find(pAddr, &aHash[Length]);
|
||||
if(pBan)
|
||||
{
|
||||
MakeBanInfo(pBan, pBuf, BufferSize, MSGTYPE_PLAYER);
|
||||
return true;
|
||||
}
|
||||
|
||||
// check ban ranges
|
||||
for(int i = Length-1; i >= 0; --i)
|
||||
{
|
||||
for(CBanRange *pBan = m_BanRangePool.First(&aHash[i]); pBan; pBan = pBan->m_pHashNext)
|
||||
{
|
||||
if(NetMatch(&pBan->m_Data, pAddr, i, Length))
|
||||
{
|
||||
MakeBanInfo(pBan, pBuf, BufferSize, MSGTYPE_PLAYER);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
179
server/src/netban.h
Normal file
179
server/src/netban.h
Normal file
@ -0,0 +1,179 @@
|
||||
#ifndef NETBAN_H
|
||||
#define NETBAN_H
|
||||
|
||||
#include <system.h>
|
||||
|
||||
inline int NetComp(const NETADDR *pAddr1, const NETADDR *pAddr2)
|
||||
{
|
||||
return mem_comp(pAddr1, pAddr2, pAddr1->type==NETTYPE_IPV4 ? 8 : 20);
|
||||
}
|
||||
|
||||
class CNetRange
|
||||
{
|
||||
public:
|
||||
NETADDR m_LB;
|
||||
NETADDR m_UB;
|
||||
|
||||
bool IsValid() const { return m_LB.type == m_UB.type && NetComp(&m_LB, &m_UB) < 0; }
|
||||
};
|
||||
|
||||
inline int NetComp(const CNetRange *pRange1, const CNetRange *pRange2)
|
||||
{
|
||||
return NetComp(&pRange1->m_LB, &pRange2->m_LB) || NetComp(&pRange1->m_UB, &pRange2->m_UB);
|
||||
}
|
||||
|
||||
|
||||
class CNetBan
|
||||
{
|
||||
protected:
|
||||
bool NetMatch(const NETADDR *pAddr1, const NETADDR *pAddr2) const
|
||||
{
|
||||
return NetComp(pAddr1, pAddr2) == 0;
|
||||
}
|
||||
|
||||
bool NetMatch(const CNetRange *pRange, const NETADDR *pAddr, int Start, int Length) const
|
||||
{
|
||||
return pRange->m_LB.type == pAddr->type && (Start == 0 || mem_comp(&pRange->m_LB.ip[0], &pAddr->ip[0], Start) == 0) &&
|
||||
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;
|
||||
}
|
||||
|
||||
bool NetMatch(const CNetRange *pRange, const NETADDR *pAddr) const
|
||||
{
|
||||
return NetMatch(pRange, pAddr, 0, pRange->m_LB.type==NETTYPE_IPV4 ? 4 : 16);
|
||||
}
|
||||
|
||||
const char *NetToString(const NETADDR *pData, char *pBuffer, unsigned BufferSize) const
|
||||
{
|
||||
char aAddrStr[NETADDR_MAXSTRSIZE];
|
||||
net_addr_str(pData, aAddrStr, sizeof(aAddrStr), false);
|
||||
str_format(pBuffer, BufferSize, "'%s'", aAddrStr);
|
||||
return pBuffer;
|
||||
}
|
||||
|
||||
const char *NetToString(const CNetRange *pData, char *pBuffer, unsigned BufferSize) const
|
||||
{
|
||||
char aAddrStr1[NETADDR_MAXSTRSIZE], aAddrStr2[NETADDR_MAXSTRSIZE];
|
||||
net_addr_str(&pData->m_LB, aAddrStr1, sizeof(aAddrStr1), false);
|
||||
net_addr_str(&pData->m_UB, aAddrStr2, sizeof(aAddrStr2), false);
|
||||
str_format(pBuffer, BufferSize, "'%s' - '%s'", aAddrStr1, aAddrStr2);
|
||||
return pBuffer;
|
||||
}
|
||||
|
||||
// todo: move?
|
||||
static bool StrAllnum(const char *pStr);
|
||||
|
||||
class CNetHash
|
||||
{
|
||||
public:
|
||||
int m_Hash;
|
||||
int m_HashIndex; // matching parts for ranges, 0 for addr
|
||||
|
||||
CNetHash() {}
|
||||
CNetHash(const NETADDR *pAddr);
|
||||
CNetHash(const CNetRange *pRange);
|
||||
|
||||
static int MakeHashArray(const NETADDR *pAddr, CNetHash aHash[17]);
|
||||
};
|
||||
|
||||
struct CBanInfo
|
||||
{
|
||||
enum
|
||||
{
|
||||
EXPIRES_NEVER=-1,
|
||||
REASON_LENGTH=64,
|
||||
};
|
||||
int m_Expires;
|
||||
char m_aReason[REASON_LENGTH];
|
||||
};
|
||||
|
||||
template<class T> struct CBan
|
||||
{
|
||||
T m_Data;
|
||||
CBanInfo m_Info;
|
||||
CNetHash m_NetHash;
|
||||
|
||||
// hash list
|
||||
CBan *m_pHashNext;
|
||||
CBan *m_pHashPrev;
|
||||
|
||||
// used or free list
|
||||
CBan *m_pNext;
|
||||
CBan *m_pPrev;
|
||||
};
|
||||
|
||||
template<class T, int HashCount> class CBanPool
|
||||
{
|
||||
public:
|
||||
typedef T CDataType;
|
||||
|
||||
CBan<CDataType> *Add(const CDataType *pData, const CBanInfo *pInfo, const CNetHash *pNetHash);
|
||||
int Remove(CBan<CDataType> *pBan);
|
||||
void Update(CBan<CDataType> *pBan, const CBanInfo *pInfo);
|
||||
void Reset();
|
||||
|
||||
int Num() const { return m_CountUsed; }
|
||||
bool IsFull() const { return m_CountUsed == MAX_BANS; }
|
||||
|
||||
CBan<CDataType> *First() const { return m_pFirstUsed; }
|
||||
CBan<CDataType> *First(const CNetHash *pNetHash) const { return m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]; }
|
||||
CBan<CDataType> *Find(const CDataType *pData, const CNetHash *pNetHash) const
|
||||
{
|
||||
for(CBan<CDataType> *pBan = m_paaHashList[pNetHash->m_HashIndex][pNetHash->m_Hash]; pBan; pBan = pBan->m_pHashNext)
|
||||
{
|
||||
if(NetComp(&pBan->m_Data, pData) == 0)
|
||||
return pBan;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
CBan<CDataType> *Get(int Index) const;
|
||||
|
||||
private:
|
||||
enum
|
||||
{
|
||||
MAX_BANS=1024,
|
||||
};
|
||||
|
||||
CBan<CDataType> *m_paaHashList[HashCount][256];
|
||||
CBan<CDataType> m_aBans[MAX_BANS];
|
||||
CBan<CDataType> *m_pFirstFree;
|
||||
CBan<CDataType> *m_pFirstUsed;
|
||||
int m_CountUsed;
|
||||
};
|
||||
|
||||
typedef CBanPool<NETADDR, 1> CBanAddrPool;
|
||||
typedef CBanPool<CNetRange, 16> CBanRangePool;
|
||||
typedef CBan<NETADDR> CBanAddr;
|
||||
typedef CBan<CNetRange> CBanRange;
|
||||
|
||||
template<class T> void MakeBanInfo(const CBan<T> *pBan, char *pBuf, unsigned BuffSize, int Type) const;
|
||||
template<class T> int Ban(T *pBanPool, const typename T::CDataType *pData, int Seconds, const char *pReason);
|
||||
template<class T> int Unban(T *pBanPool, const typename T::CDataType *pData);
|
||||
|
||||
CBanAddrPool m_BanAddrPool;
|
||||
CBanRangePool m_BanRangePool;
|
||||
NETADDR m_LocalhostIPV4, m_LocalhostIPV6;
|
||||
|
||||
public:
|
||||
enum
|
||||
{
|
||||
MSGTYPE_PLAYER=0,
|
||||
MSGTYPE_LIST,
|
||||
MSGTYPE_BANADD,
|
||||
MSGTYPE_BANREM,
|
||||
};
|
||||
|
||||
virtual ~CNetBan() {}
|
||||
void Init();
|
||||
void Update();
|
||||
|
||||
virtual int BanAddr(const NETADDR *pAddr, int Seconds, const char *pReason);
|
||||
virtual int BanRange(const CNetRange *pRange, int Seconds, const char *pReason);
|
||||
int UnbanByAddr(const NETADDR *pAddr);
|
||||
int UnbanByRange(const CNetRange *pRange);
|
||||
int UnbanByIndex(int Index);
|
||||
void UnbanAll();
|
||||
bool IsBanned(const NETADDR *pAddr, char *pBuf, unsigned BufferSize) const;
|
||||
};
|
||||
|
||||
#endif
|
144
server/src/network.cpp
Normal file
144
server/src/network.cpp
Normal file
@ -0,0 +1,144 @@
|
||||
#include <system.h>
|
||||
#include "netban.h"
|
||||
#include "network.h"
|
||||
|
||||
bool CNetwork::Open(NETADDR BindAddr, CNetBan *pNetBan)
|
||||
{
|
||||
// zero out the whole structure
|
||||
mem_zero(this, sizeof(*this));
|
||||
m_Socket.type = NETTYPE_INVALID;
|
||||
m_Socket.ipv4sock = -1;
|
||||
m_Socket.ipv6sock = -1;
|
||||
m_pNetBan = pNetBan;
|
||||
|
||||
// open socket
|
||||
m_Socket = net_tcp_create(BindAddr);
|
||||
if(!m_Socket.type)
|
||||
return false;
|
||||
if(net_tcp_listen(m_Socket, NET_MAX_CLIENTS))
|
||||
return false;
|
||||
net_set_non_blocking(m_Socket);
|
||||
|
||||
for(int i = 0; i < NET_MAX_CLIENTS; i++)
|
||||
m_aSlots[i].m_Connection.Reset();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CNetwork::SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser)
|
||||
{
|
||||
m_pfnNewClient = pfnNewClient;
|
||||
m_pfnDelClient = pfnDelClient;
|
||||
m_UserPtr = pUser;
|
||||
}
|
||||
|
||||
int CNetwork::Close()
|
||||
{
|
||||
for(int i = 0; i < NET_MAX_CLIENTS; i++)
|
||||
m_aSlots[i].m_Connection.Disconnect("Closing connection.");
|
||||
|
||||
net_tcp_close(m_Socket);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CNetwork::Drop(int ClientID, const char *pReason)
|
||||
{
|
||||
if(m_pfnDelClient)
|
||||
m_pfnDelClient(ClientID, pReason, m_UserPtr);
|
||||
|
||||
m_aSlots[ClientID].m_Connection.Disconnect(pReason);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CNetwork::AcceptClient(NETSOCKET Socket, const NETADDR *pAddr)
|
||||
{
|
||||
char aError[256] = { 0 };
|
||||
int FreeSlot = -1;
|
||||
|
||||
// look for free slot or multiple client
|
||||
for(int i = 0; i < NET_MAX_CLIENTS; i++)
|
||||
{
|
||||
if(FreeSlot == -1 && m_aSlots[i].m_Connection.State() == NET_CONNSTATE_OFFLINE)
|
||||
FreeSlot = i;
|
||||
if(m_aSlots[i].m_Connection.State() != NET_CONNSTATE_OFFLINE)
|
||||
{
|
||||
if(net_addr_comp(pAddr, m_aSlots[i].m_Connection.PeerAddress()) == 0)
|
||||
{
|
||||
str_copy(aError, "Only one client per IP allowed.", sizeof(aError));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// accept client
|
||||
if(!aError[0] && FreeSlot != -1)
|
||||
{
|
||||
m_aSlots[FreeSlot].m_Connection.Init(Socket, pAddr);
|
||||
if(m_pfnNewClient)
|
||||
m_pfnNewClient(FreeSlot, m_UserPtr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// reject client
|
||||
if(!aError[0])
|
||||
str_copy(aError, "No free slot available.", sizeof(aError));
|
||||
|
||||
net_tcp_send(Socket, aError, str_length(aError));
|
||||
net_tcp_close(Socket);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int CNetwork::Update()
|
||||
{
|
||||
NETSOCKET Socket;
|
||||
NETADDR Addr;
|
||||
|
||||
if(net_tcp_accept(m_Socket, &Socket, &Addr) > 0)
|
||||
{
|
||||
// check if we should just drop the packet
|
||||
char aBuf[128];
|
||||
if(NetBan() && NetBan()->IsBanned(&Addr, aBuf, sizeof(aBuf)))
|
||||
{
|
||||
// banned, reply with a message and drop
|
||||
net_tcp_send(Socket, aBuf, str_length(aBuf));
|
||||
net_tcp_close(Socket);
|
||||
}
|
||||
else
|
||||
AcceptClient(Socket, &Addr);
|
||||
}
|
||||
|
||||
for(int i = 0; i < NET_MAX_CLIENTS; i++)
|
||||
{
|
||||
if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE)
|
||||
m_aSlots[i].m_Connection.Update();
|
||||
if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ERROR)
|
||||
Drop(i, m_aSlots[i].m_Connection.ErrorString());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CNetwork::Recv(char *pLine, int MaxLength, int *pClientID)
|
||||
{
|
||||
for(int i = 0; i < NET_MAX_CLIENTS; i++)
|
||||
{
|
||||
if(m_aSlots[i].m_Connection.State() == NET_CONNSTATE_ONLINE && m_aSlots[i].m_Connection.Recv(pLine, MaxLength))
|
||||
{
|
||||
if(pClientID)
|
||||
*pClientID = i;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CNetwork::Send(int ClientID, const char *pLine)
|
||||
{
|
||||
if(m_aSlots[ClientID].m_Connection.State() == NET_CONNSTATE_ONLINE)
|
||||
return m_aSlots[ClientID].m_Connection.Send(pLine);
|
||||
else
|
||||
return -1;
|
||||
}
|
87
server/src/network.h
Normal file
87
server/src/network.h
Normal file
@ -0,0 +1,87 @@
|
||||
#ifndef NETWORK_H
|
||||
#define NETWORK_H
|
||||
|
||||
enum
|
||||
{
|
||||
NET_CONNSTATE_OFFLINE=0,
|
||||
NET_CONNSTATE_CONNECT=1,
|
||||
NET_CONNSTATE_PENDING=2,
|
||||
NET_CONNSTATE_ONLINE=3,
|
||||
NET_CONNSTATE_ERROR=4,
|
||||
|
||||
NET_MAX_PACKETSIZE = 1400,
|
||||
NET_MAX_CLIENTS = 128
|
||||
};
|
||||
|
||||
typedef int (*NETFUNC_DELCLIENT)(int ClientID, const char* pReason, void *pUser);
|
||||
typedef int (*NETFUNC_NEWCLIENT)(int ClientID, void *pUser);
|
||||
|
||||
class CNetworkClient
|
||||
{
|
||||
private:
|
||||
int m_State;
|
||||
|
||||
NETADDR m_PeerAddr;
|
||||
NETSOCKET m_Socket;
|
||||
|
||||
char m_aBuffer[NET_MAX_PACKETSIZE];
|
||||
int m_BufferOffset;
|
||||
|
||||
char m_aErrorString[256];
|
||||
|
||||
bool m_LineEndingDetected;
|
||||
char m_aLineEnding[3];
|
||||
|
||||
public:
|
||||
void Init(NETSOCKET Socket, const NETADDR *pAddr);
|
||||
void Disconnect(const char *pReason);
|
||||
|
||||
int State() const { return m_State; }
|
||||
const NETADDR *PeerAddress() const { return &m_PeerAddr; }
|
||||
const char *ErrorString() const { return m_aErrorString; }
|
||||
|
||||
void Reset();
|
||||
int Update();
|
||||
int Send(const char *pLine);
|
||||
int Recv(char *pLine, int MaxLength);
|
||||
};
|
||||
|
||||
class CNetwork
|
||||
{
|
||||
private:
|
||||
struct CSlot
|
||||
{
|
||||
CNetworkClient m_Connection;
|
||||
};
|
||||
|
||||
NETSOCKET m_Socket;
|
||||
class CNetBan *m_pNetBan;
|
||||
CSlot m_aSlots[NET_MAX_CLIENTS];
|
||||
|
||||
NETFUNC_NEWCLIENT m_pfnNewClient;
|
||||
NETFUNC_DELCLIENT m_pfnDelClient;
|
||||
void *m_UserPtr;
|
||||
|
||||
public:
|
||||
void SetCallbacks(NETFUNC_NEWCLIENT pfnNewClient, NETFUNC_DELCLIENT pfnDelClient, void *pUser);
|
||||
|
||||
//
|
||||
bool Open(NETADDR BindAddr, CNetBan *pNetBan);
|
||||
int Close();
|
||||
|
||||
//
|
||||
int Recv(char *pLine, int MaxLength, int *pClientID = 0);
|
||||
int Send(int ClientID, const char *pLine);
|
||||
int Update();
|
||||
|
||||
//
|
||||
int AcceptClient(NETSOCKET Socket, const NETADDR *pAddr);
|
||||
int Drop(int ClientID, const char *pReason);
|
||||
|
||||
// status requests
|
||||
const NETADDR *ClientAddr(int ClientID) const { return m_aSlots[ClientID].m_Connection.PeerAddress(); }
|
||||
const NETSOCKET *Socket() const { return &m_Socket; }
|
||||
class CNetBan *NetBan() const { return m_pNetBan; }
|
||||
};
|
||||
|
||||
#endif
|
184
server/src/network_client.cpp
Normal file
184
server/src/network_client.cpp
Normal file
@ -0,0 +1,184 @@
|
||||
#include <system.h>
|
||||
#include "network.h"
|
||||
|
||||
void CNetworkClient::Reset()
|
||||
{
|
||||
m_State = NET_CONNSTATE_OFFLINE;
|
||||
mem_zero(&m_PeerAddr, sizeof(m_PeerAddr));
|
||||
m_aErrorString[0] = 0;
|
||||
|
||||
m_Socket.type = NETTYPE_INVALID;
|
||||
m_Socket.ipv4sock = -1;
|
||||
m_Socket.ipv6sock = -1;
|
||||
m_aBuffer[0] = 0;
|
||||
m_BufferOffset = 0;
|
||||
|
||||
m_LineEndingDetected = false;
|
||||
#if defined(CONF_FAMILY_WINDOWS)
|
||||
m_aLineEnding[0] = '\r';
|
||||
m_aLineEnding[1] = '\n';
|
||||
m_aLineEnding[2] = 0;
|
||||
#else
|
||||
m_aLineEnding[0] = '\n';
|
||||
m_aLineEnding[1] = 0;
|
||||
m_aLineEnding[2] = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void CNetworkClient::Init(NETSOCKET Socket, const NETADDR *pAddr)
|
||||
{
|
||||
Reset();
|
||||
|
||||
m_Socket = Socket;
|
||||
net_set_non_blocking(m_Socket);
|
||||
|
||||
m_PeerAddr = *pAddr;
|
||||
m_State = NET_CONNSTATE_ONLINE;
|
||||
}
|
||||
|
||||
void CNetworkClient::Disconnect(const char *pReason)
|
||||
{
|
||||
if(State() == NET_CONNSTATE_OFFLINE)
|
||||
return;
|
||||
|
||||
if(pReason && pReason[0])
|
||||
Send(pReason);
|
||||
|
||||
net_tcp_close(m_Socket);
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
int CNetworkClient::Update()
|
||||
{
|
||||
if(State() == NET_CONNSTATE_ONLINE)
|
||||
{
|
||||
if((int)(sizeof(m_aBuffer)) <= m_BufferOffset)
|
||||
{
|
||||
m_State = NET_CONNSTATE_ERROR;
|
||||
str_copy(m_aErrorString, "too weak connection (out of buffer)", sizeof(m_aErrorString));
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Bytes = net_tcp_recv(m_Socket, m_aBuffer+m_BufferOffset, (int)(sizeof(m_aBuffer))-m_BufferOffset);
|
||||
|
||||
if(Bytes > 0)
|
||||
{
|
||||
m_BufferOffset += Bytes;
|
||||
}
|
||||
else if(Bytes < 0)
|
||||
{
|
||||
if(net_would_block()) // no data received
|
||||
return 0;
|
||||
|
||||
m_State = NET_CONNSTATE_ERROR; // error
|
||||
str_copy(m_aErrorString, "connection failure", sizeof(m_aErrorString));
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_State = NET_CONNSTATE_ERROR;
|
||||
str_copy(m_aErrorString, "remote end closed the connection", sizeof(m_aErrorString));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CNetworkClient::Recv(char *pLine, int MaxLength)
|
||||
{
|
||||
if(State() == NET_CONNSTATE_ONLINE)
|
||||
{
|
||||
if(m_BufferOffset)
|
||||
{
|
||||
// find message start
|
||||
int StartOffset = 0;
|
||||
while(m_aBuffer[StartOffset] == '\r' || m_aBuffer[StartOffset] == '\n')
|
||||
{
|
||||
// detect clients line ending format
|
||||
if(!m_LineEndingDetected)
|
||||
{
|
||||
m_aLineEnding[0] = m_aBuffer[StartOffset];
|
||||
if(StartOffset+1 < m_BufferOffset && (m_aBuffer[StartOffset+1] == '\r' || m_aBuffer[StartOffset+1] == '\n') &&
|
||||
m_aBuffer[StartOffset] != m_aBuffer[StartOffset+1])
|
||||
m_aLineEnding[1] = m_aBuffer[StartOffset+1];
|
||||
m_LineEndingDetected = true;
|
||||
}
|
||||
|
||||
if(++StartOffset >= m_BufferOffset)
|
||||
{
|
||||
m_BufferOffset = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// find message end
|
||||
int EndOffset = StartOffset;
|
||||
while(m_aBuffer[EndOffset] != '\r' && m_aBuffer[EndOffset] != '\n')
|
||||
{
|
||||
if(++EndOffset >= m_BufferOffset)
|
||||
{
|
||||
if(StartOffset > 0)
|
||||
{
|
||||
mem_move(m_aBuffer, m_aBuffer+StartOffset, m_BufferOffset-StartOffset);
|
||||
m_BufferOffset -= StartOffset;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// extract message and update buffer
|
||||
if(MaxLength-1 < EndOffset-StartOffset)
|
||||
{
|
||||
if(StartOffset > 0)
|
||||
{
|
||||
mem_move(m_aBuffer, m_aBuffer+StartOffset, m_BufferOffset-StartOffset);
|
||||
m_BufferOffset -= StartOffset;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
mem_copy(pLine, m_aBuffer+StartOffset, EndOffset-StartOffset);
|
||||
pLine[EndOffset-StartOffset] = 0;
|
||||
str_sanitize_cc(pLine);
|
||||
mem_move(m_aBuffer, m_aBuffer+EndOffset, m_BufferOffset-EndOffset);
|
||||
m_BufferOffset -= EndOffset;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CNetworkClient::Send(const char *pLine)
|
||||
{
|
||||
if(State() != NET_CONNSTATE_ONLINE)
|
||||
return -1;
|
||||
|
||||
char aBuf[1024];
|
||||
str_copy(aBuf, pLine, (int)(sizeof(aBuf))-2);
|
||||
int Length = str_length(aBuf);
|
||||
aBuf[Length] = m_aLineEnding[0];
|
||||
aBuf[Length+1] = m_aLineEnding[1];
|
||||
aBuf[Length+2] = m_aLineEnding[2];
|
||||
Length += 3;
|
||||
const char *pData = aBuf;
|
||||
|
||||
while(1)
|
||||
{
|
||||
int Send = net_tcp_send(m_Socket, pData, Length);
|
||||
if(Send < 0)
|
||||
{
|
||||
m_State = NET_CONNSTATE_ERROR;
|
||||
str_copy(m_aErrorString, "failed to send packet", sizeof(m_aErrorString));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(Send >= Length)
|
||||
break;
|
||||
|
||||
pData += Send;
|
||||
Length -= Send;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
204
server/src/server.cpp
Normal file
204
server/src/server.cpp
Normal file
@ -0,0 +1,204 @@
|
||||
#include <system.h>
|
||||
#include "netban.h"
|
||||
#include "network.h"
|
||||
#include "main.h"
|
||||
#include "server.h"
|
||||
|
||||
int CServer::NewClientCallback(int ClientID, void *pUser)
|
||||
{
|
||||
CServer *pThis = (CServer *)pUser;
|
||||
|
||||
char aAddrStr[NETADDR_MAXSTRSIZE];
|
||||
net_addr_str(pThis->m_Network.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true);
|
||||
if(pThis->Main()->Config()->m_Verbose)
|
||||
dbg_msg("server", "Connection accepted. ncid=%d addr=%s'", ClientID, aAddrStr);
|
||||
|
||||
pThis->m_aClients[ClientID].m_State = CClient::STATE_CONNECTED;
|
||||
pThis->m_aClients[ClientID].m_TimeConnected = time_get();
|
||||
pThis->m_Network.Send(ClientID, "Authentication required:");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CServer::DelClientCallback(int ClientID, const char *pReason, void *pUser)
|
||||
{
|
||||
CServer *pThis = (CServer *)pUser;
|
||||
|
||||
char aAddrStr[NETADDR_MAXSTRSIZE];
|
||||
net_addr_str(pThis->m_Network.ClientAddr(ClientID), aAddrStr, sizeof(aAddrStr), true);
|
||||
if(pThis->Main()->Config()->m_Verbose)
|
||||
dbg_msg("server", "Client dropped. ncid=%d addr=%s reason='%s'", ClientID, aAddrStr, pReason);
|
||||
|
||||
if(pThis->m_aClients[ClientID].m_State == CClient::STATE_AUTHED)
|
||||
pThis->Main()->OnDelClient(ClientID);
|
||||
pThis->m_aClients[ClientID].m_State = CClient::STATE_EMPTY;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CServer::Init(CMain *pMain, const char *Bind, int Port)
|
||||
{
|
||||
m_pMain = pMain;
|
||||
m_NetBan.Init();
|
||||
|
||||
for(int i = 0; i < NET_MAX_CLIENTS; i++)
|
||||
m_aClients[i].m_State = CClient::STATE_EMPTY;
|
||||
|
||||
m_Ready = false;
|
||||
|
||||
if(Port == 0)
|
||||
{
|
||||
dbg_msg("server", "Will not bind to port 0.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
NETADDR BindAddr;
|
||||
if(Bind[0] && net_host_lookup(Bind, &BindAddr, NETTYPE_ALL) == 0)
|
||||
{
|
||||
// got bindaddr
|
||||
BindAddr.type = NETTYPE_ALL;
|
||||
BindAddr.port = Port;
|
||||
}
|
||||
else
|
||||
{
|
||||
mem_zero(&BindAddr, sizeof(BindAddr));
|
||||
BindAddr.type = NETTYPE_ALL;
|
||||
BindAddr.port = Port;
|
||||
}
|
||||
|
||||
if(m_Network.Open(BindAddr, &m_NetBan))
|
||||
{
|
||||
m_Network.SetCallbacks(NewClientCallback, DelClientCallback, this);
|
||||
m_Ready = true;
|
||||
dbg_msg("server", "Bound to %s:%d", Bind, Port);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
dbg_msg("server", "Couldn't open socket. Port (%d) might already be in use.", Port);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void CServer::Update()
|
||||
{
|
||||
if(!m_Ready)
|
||||
return;
|
||||
|
||||
m_NetBan.Update();
|
||||
m_Network.Update();
|
||||
|
||||
char aBuf[NET_MAX_PACKETSIZE];
|
||||
int ClientID;
|
||||
|
||||
while(m_Network.Recv(aBuf, (int)(sizeof(aBuf))-1, &ClientID))
|
||||
{
|
||||
dbg_assert(m_aClients[ClientID].m_State != CClient::STATE_EMPTY, "Got message from empty slot.");
|
||||
if(m_aClients[ClientID].m_State == CClient::STATE_CONNECTED)
|
||||
{
|
||||
int ID = -1;
|
||||
char aUsername[128] = {0};
|
||||
char aPassword[128] = {0};
|
||||
const char *pTmp;
|
||||
|
||||
if(!(pTmp = str_find(aBuf, ":"))
|
||||
|| (unsigned)(pTmp - aBuf) > sizeof(aUsername) || (unsigned)(str_length(pTmp) - 1) > sizeof(aPassword))
|
||||
{
|
||||
m_Network.NetBan()->BanAddr(m_Network.ClientAddr(ClientID), 60, "You're an idiot, go away.");
|
||||
m_Network.Drop(ClientID, "Fuck off.");
|
||||
return;
|
||||
}
|
||||
|
||||
str_copy(aUsername, aBuf, pTmp - aBuf + 1);
|
||||
str_copy(aPassword, pTmp + 1, sizeof(aPassword));
|
||||
if(!*aUsername || !*aPassword)
|
||||
{
|
||||
m_Network.NetBan()->BanAddr(m_Network.ClientAddr(ClientID), 60, "You're an idiot, go away.");
|
||||
m_Network.Drop(ClientID, "Username and password must not be blank.");
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i = 0; i < NET_MAX_CLIENTS; i++)
|
||||
{
|
||||
if(!Main()->Client(i)->m_Active)
|
||||
continue;
|
||||
|
||||
if(str_comp(Main()->Client(i)->m_aUsername, aUsername) == 0 && str_comp(Main()->Client(i)->m_aPassword, aPassword) == 0)
|
||||
ID = i;
|
||||
}
|
||||
|
||||
if(ID == -1)
|
||||
{
|
||||
m_Network.NetBan()->BanAddr(m_Network.ClientAddr(ClientID), 60, "Wrong username and/or password.");
|
||||
m_Network.Drop(ClientID, "Wrong username and/or password.");
|
||||
}
|
||||
else if(Main()->Client(ID)->m_ClientNetID != -1)
|
||||
{
|
||||
m_Network.Drop(ClientID, "Only one connection per user allowed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_aClients[ClientID].m_State = CClient::STATE_AUTHED;
|
||||
m_aClients[ClientID].m_LastReceived = time_get();
|
||||
m_Network.Send(ClientID, "Authentication successful. Access granted.");
|
||||
|
||||
if(m_Network.ClientAddr(ClientID)->type == NETTYPE_IPV4)
|
||||
m_Network.Send(ClientID, "You are connecting via: IPv4");
|
||||
else if(m_Network.ClientAddr(ClientID)->type == NETTYPE_IPV6)
|
||||
m_Network.Send(ClientID, "You are connecting via: IPv6");
|
||||
|
||||
if(Main()->Config()->m_Verbose)
|
||||
dbg_msg("server", "ncid=%d authed", ClientID);
|
||||
Main()->OnNewClient(ClientID, ID);
|
||||
}
|
||||
}
|
||||
else if(m_aClients[ClientID].m_State == CClient::STATE_AUTHED)
|
||||
{
|
||||
m_aClients[ClientID].m_LastReceived = time_get();
|
||||
if(Main()->Config()->m_Verbose)
|
||||
dbg_msg("server", "ncid=%d cmd='%s'", ClientID, aBuf);
|
||||
|
||||
if(str_comp(aBuf, "logout") == 0)
|
||||
m_Network.Drop(ClientID, "Logout. Bye Bye ~");
|
||||
else
|
||||
Main()->HandleMessage(ClientID, aBuf);
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = 0; i < NET_MAX_CLIENTS; ++i)
|
||||
{
|
||||
if(m_aClients[i].m_State == CClient::STATE_CONNECTED &&
|
||||
time_get() > m_aClients[i].m_TimeConnected + 5 * time_freq())
|
||||
{
|
||||
m_Network.NetBan()->BanAddr(m_Network.ClientAddr(i), 30, "Authentication timeout.");
|
||||
m_Network.Drop(i, "Authentication timeout.");
|
||||
}
|
||||
else if(m_aClients[i].m_State == CClient::STATE_AUTHED &&
|
||||
time_get() > m_aClients[i].m_LastReceived + 15 * time_freq())
|
||||
m_Network.Drop(i, "Timeout.");
|
||||
}
|
||||
}
|
||||
|
||||
void CServer::Send(int ClientID, const char *pLine)
|
||||
{
|
||||
if(!m_Ready)
|
||||
return;
|
||||
|
||||
if(ClientID == -1)
|
||||
{
|
||||
for(int i = 0; i < NET_MAX_CLIENTS; i++)
|
||||
{
|
||||
if(m_aClients[i].m_State == CClient::STATE_AUTHED)
|
||||
m_Network.Send(i, pLine);
|
||||
}
|
||||
}
|
||||
else if(ClientID >= 0 && ClientID < NET_MAX_CLIENTS && m_aClients[ClientID].m_State == CClient::STATE_AUTHED)
|
||||
m_Network.Send(ClientID, pLine);
|
||||
}
|
||||
|
||||
void CServer::Shutdown()
|
||||
{
|
||||
if(!m_Ready)
|
||||
return;
|
||||
|
||||
m_Network.Close();
|
||||
}
|
46
server/src/server.h
Normal file
46
server/src/server.h
Normal file
@ -0,0 +1,46 @@
|
||||
#ifndef SERVER_H
|
||||
#define SERVER_H
|
||||
|
||||
#include "netban.h"
|
||||
#include "network.h"
|
||||
|
||||
class CServer
|
||||
{
|
||||
class CClient
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
STATE_EMPTY=0,
|
||||
STATE_CONNECTED,
|
||||
STATE_AUTHED,
|
||||
};
|
||||
|
||||
int m_State;
|
||||
int64 m_TimeConnected;
|
||||
int64 m_LastReceived;
|
||||
};
|
||||
CClient m_aClients[NET_MAX_CLIENTS];
|
||||
|
||||
CNetwork m_Network;
|
||||
CNetBan m_NetBan;
|
||||
|
||||
class CMain *m_pMain;
|
||||
|
||||
bool m_Ready;
|
||||
|
||||
static int NewClientCallback(int ClientID, void *pUser);
|
||||
static int DelClientCallback(int ClientID, const char *pReason, void *pUser);
|
||||
|
||||
public:
|
||||
int Init(CMain *pMain, const char *Bind, int Port);
|
||||
void Update();
|
||||
void Send(int ClientID, const char *pLine);
|
||||
void Shutdown();
|
||||
|
||||
CNetwork *Network() { return &m_Network; }
|
||||
CNetBan *NetBan() { return &m_NetBan; }
|
||||
CMain *Main() { return m_pMain; }
|
||||
};
|
||||
|
||||
#endif
|
2001
server/src/system.c
Normal file
2001
server/src/system.c
Normal file
File diff suppressed because it is too large
Load Diff
49
web/css/dark.css
Normal file
49
web/css/dark.css
Normal file
@ -0,0 +1,49 @@
|
||||
body { background: #222 url('../img/dark.png'); color: #fff; }
|
||||
.navbar { min-height: 40px; }
|
||||
.navbar-brand { color: #FFF !important; padding: 10px; font-size: 20px; }
|
||||
.dropdown .dropdown-toggle { padding-bottom: 10px; padding-top: 10px; }
|
||||
.dropdown-menu > li > a { color: #FFF !important; background-color: #222222 !important; }
|
||||
.dropdown-menu > li > a:hover { color: #FFF !important; background: #000 !important; }
|
||||
.dropdown-menu { background: #222 !important; background-color: #222222 !important; }
|
||||
.navbar-inverse .navbar-inner { background-color:#1B1B1B; background-image:-moz-linear-gradient(top, #222222, #111111); background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111)); background-image:-webkit-linear-gradient(top, #222222, #111111); background-image:-o-linear-gradient(top, #222222, #111111); background-image:linear-gradient(to bottom, #222222, #111111); background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0); border-color: #252525; }
|
||||
.content { background: #222; padding: 20px; border-radius: 5px; border: 1px #000 solid; -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, .1); -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, .1); box-shadow: 0 1px 10px rgba(0, 0, 0, .1); margin-bottom: 20px; }
|
||||
.table { background: #000; margin-bottom: 0; border-collapse: collapse; border-radius: 3px; }
|
||||
.table th { text-align: center; }
|
||||
.table-striped tbody > tr.even > td, .table-striped tbody > tr.even > th { background-color: #2F2F2F; }
|
||||
.table-striped tbody > tr.odd > td, .table-striped tbody > tr.odd > th { background-color: #000; }
|
||||
.table td { text-align: center; border-color: #2F2F2F; }
|
||||
.progress { margin-bottom: 0; background: #222; }
|
||||
.table-hover > tbody > tr:hover > td { background: #414141; }
|
||||
tr.even.expandRow > :hover { background: #2F2F2F !important; }
|
||||
tr.odd.expandRow > :hover { background: #000 !important; }
|
||||
.expandRow > td { padding: 0 !important; border-top: 0px !important; }
|
||||
#cpu, #ram, #hdd, #network { min-width: 55px; max-width: 100px; }
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
|
||||
}
|
||||
@media only screen and (max-width: 720px) {
|
||||
#type, tr td:nth-child(4) { display:none; visibility:hidden; }
|
||||
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
|
||||
}
|
||||
@media only screen and (max-width: 600px) {
|
||||
#type, tr td:nth-child(4) { display:none; visibility:hidden; }
|
||||
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
|
||||
#uptime, tr td:nth-child(6) { display:none; visibility:hidden; }
|
||||
}
|
||||
@media only screen and (max-width: 533px) {
|
||||
#type, tr td:nth-child(4) { display:none; visibility:hidden; }
|
||||
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
|
||||
#uptime, tr td:nth-child(6) { display:none; visibility:hidden; }
|
||||
#network, tr td:nth-child(8) { display:none; visibility:hidden; }
|
||||
}
|
||||
@media only screen and (max-width: 450px) {
|
||||
body { font-size: 10px; }
|
||||
.content { padding: 0; }
|
||||
#name, tr td:nth-child(3) { min-width: 10px; max-width: 25px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; }
|
||||
#type, tr td:nth-child(4) { display:none; visibility:hidden; }
|
||||
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
|
||||
#uptime, tr td:nth-child(6) { display:none; visibility:hidden; }
|
||||
#network, tr td:nth-child(8) { display:none; visibility:hidden; }
|
||||
#cpu, #ram, #hdd { min-width: 25px; max-width: 50px; }
|
||||
}
|
46
web/css/light.css
Normal file
46
web/css/light.css
Normal file
@ -0,0 +1,46 @@
|
||||
body { background: #ebebeb url('../img/light.png'); }
|
||||
.navbar { min-height: 40px; }
|
||||
.navbar-brand { color: #fff; padding: 10px; font-size: 20px; }
|
||||
.dropdown .dropdown-toggle { padding-bottom: 10px; padding-top: 10px; }
|
||||
.navbar-inverse .navbar-brand { color: #fff; padding: 10px; font-size: 20px; }
|
||||
.content { background: #ffffff; padding: 20px; border-radius: 5px; border: 1px #cecece solid; -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, .1); -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, .1); box-shadow: 0 1px 10px rgba(0, 0, 0, .1); margin-bottom: 20px; }
|
||||
.table { background: #ffffff; margin-bottom: 0; border-collapse: collapse; border-radius: 3px; }
|
||||
.table th, .table td { text-align: center; }
|
||||
.table-striped tbody > tr.even > td, .table-striped tbody > tr.even > th { background-color: #F9F9F9; }
|
||||
.table-striped tbody > tr.odd > td, .table-striped tbody > tr.odd > th { background-color: #FFF; }
|
||||
.progress { margin-bottom: 0; }
|
||||
.progress-bar { color: #000; }
|
||||
.table-hover > tbody > tr:hover > td { background: #E6E6E6; }
|
||||
tr.even.expandRow > :hover { background: #F9F9F9 !important; }
|
||||
tr.odd.expandRow > :hover { background: #FFF !important; }
|
||||
.expandRow > td { padding: 0 !important; border-top: 0px !important; }
|
||||
#cpu, #ram, #hdd, #network { min-width: 55px; max-width: 100px; }
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
|
||||
}
|
||||
@media only screen and (max-width: 720px) {
|
||||
#type, tr td:nth-child(4) { display:none; visibility:hidden; }
|
||||
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
|
||||
}
|
||||
@media only screen and (max-width: 600px) {
|
||||
#type, tr td:nth-child(4) { display:none; visibility:hidden; }
|
||||
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
|
||||
#uptime, tr td:nth-child(6) { display:none; visibility:hidden; }
|
||||
}
|
||||
@media only screen and (max-width: 533px) {
|
||||
#type, tr td:nth-child(4) { display:none; visibility:hidden; }
|
||||
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
|
||||
#uptime, tr td:nth-child(6) { display:none; visibility:hidden; }
|
||||
#network, tr td:nth-child(8) { display:none; visibility:hidden; }
|
||||
}
|
||||
@media only screen and (max-width: 450px) {
|
||||
body { font-size: 10px; }
|
||||
.content { padding: 0; }
|
||||
#name, tr td:nth-child(3) { min-width: 10px; max-width: 25px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; }
|
||||
#type, tr td:nth-child(4) { display:none; visibility:hidden; }
|
||||
#location, tr td:nth-child(5) { display:none; visibility:hidden; }
|
||||
#uptime, tr td:nth-child(6) { display:none; visibility:hidden; }
|
||||
#network, tr td:nth-child(8) { display:none; visibility:hidden; }
|
||||
#cpu, #ram, #hdd { min-width: 25px; max-width: 50px; }
|
||||
}
|
BIN
web/favicon.ico
Normal file
BIN
web/favicon.ico
Normal file
Binary file not shown.
After Width: 128px | Height: 86px | Size: 44 KiB |
BIN
web/img/dark.png
Normal file
BIN
web/img/dark.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 600 B |
BIN
web/img/light.png
Normal file
BIN
web/img/light.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 19 KiB |
112
web/index.html
Normal file
112
web/index.html
Normal file
@ -0,0 +1,112 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
json字段保持完整, 后期更新会向下兼容
|
||||
可以自定义前端展示
|
||||
ლ(•̀ _ •́ ლ)
|
||||
ლ(•̀ _ •́ ლ)ლ(•̀ _ •́ ლ)
|
||||
ლ(•̀ _ •́ ლ)ლ(•̀ _ •́ ლ)ლ(•̀ _ •́ ლ)
|
||||
ლ(•̀ _ •́ ლ)ლ(•̀ _ •́ ლ)
|
||||
ლ(•̀ _ •́ ლ)
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>云监控</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="云监控">
|
||||
<meta name="author" content="BotoX">
|
||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap-theme.min.css">
|
||||
<link rel="stylesheet" href="css/dark.css" title="dark">
|
||||
<link rel="stylesheet" href="css/light.css" title="light">
|
||||
<style>
|
||||
body {
|
||||
padding-top: 70px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||||
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<div role="navigation" class="navbar navbar-inverse navbar-fixed-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button data-target=".navbar-collapse" data-toggle="collapse" class="navbar-toggle" type="button">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a href="#" class="navbar-brand">云监控</a>
|
||||
</div>
|
||||
<div class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="dropdown">
|
||||
<a data-toggle="dropdown" class="dropdown-toggle" href="#">风格<b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#" onclick="setActiveStyleSheet('dark')">黑夜</a></li>
|
||||
<li><a href="#" onclick="setActiveStyleSheet('light')">白天</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div><!--/.nav-collapse -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container content">
|
||||
<div id="loading-notice">
|
||||
<noscript>
|
||||
<div class="alert alert-danger" style="text-align: center;">
|
||||
<strong>Enable JavaScript</strong> you fucking autist neckbeard, it's not gonna hurt you.
|
||||
</div>
|
||||
</noscript>
|
||||
<div class="progress progress-striped active">
|
||||
<div class="progress-bar progress-bar-warning" style="width: 100%;">加载中...</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
如果出现此消息,请确保您已启用Javascript! <br />否则云监控主服务没启动或已关闭.
|
||||
</div>
|
||||
<p></p>
|
||||
</div>
|
||||
<table class="table table-striped table-condensed table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="status4" style="text-align: center;">IPv4</th>
|
||||
<th id="ipstatus" style="text-align: center;">Flight</th>
|
||||
<th id="name">节点名</th>
|
||||
<th id="type">虚拟化</th>
|
||||
<th id="location">位置</th>
|
||||
<th id="uptime">在线时间</th>
|
||||
<th id="load">负载</th>
|
||||
<th id="network">网络 ↓|↑</th>
|
||||
<th id="traffic">流量 ↓|↑</th>
|
||||
<th id="cpu">处理器</th>
|
||||
<th id="ram">内存</th>
|
||||
<th id="hdd">硬盘</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="servers">
|
||||
<!-- Servers here \o/ -->
|
||||
</tbody>
|
||||
</table>
|
||||
<br />
|
||||
<div id="updated">Updating...</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<p style="text-align: center; font-size: 10px;">
|
||||
<a href="https://github.com/cppla/ServerStatus">ServerStatus中文版</a>
|
||||
</p>
|
||||
</div>
|
||||
<script src="//code.jquery.com/jquery-1.10.2.min.js"></script>
|
||||
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
|
||||
<script src="js/serverstatus.js"></script>
|
||||
</body>
|
||||
</html>
|
393
web/js/serverstatus.js
Normal file
393
web/js/serverstatus.js
Normal file
@ -0,0 +1,393 @@
|
||||
// serverstatus.js
|
||||
var error = 0;
|
||||
var d = 0;
|
||||
var server_status = new Array();
|
||||
|
||||
function timeSince(date) {
|
||||
if(date == 0)
|
||||
return "从未.";
|
||||
|
||||
var seconds = Math.floor((new Date() - date) / 1000);
|
||||
var interval = Math.floor(seconds / 31536000);
|
||||
|
||||
if (interval > 1)
|
||||
return interval + " 年前.";
|
||||
interval = Math.floor(seconds / 2592000);
|
||||
if (interval > 1)
|
||||
return interval + " 月前.";
|
||||
interval = Math.floor(seconds / 86400);
|
||||
if (interval > 1)
|
||||
return interval + " 日前.";
|
||||
interval = Math.floor(seconds / 3600);
|
||||
if (interval > 1)
|
||||
return interval + " 小时前.";
|
||||
interval = Math.floor(seconds / 60);
|
||||
if (interval > 1)
|
||||
return interval + " 分钟前.";
|
||||
/*if(Math.floor(seconds) >= 5)
|
||||
return Math.floor(seconds) + " seconds";*/
|
||||
else
|
||||
return "几秒前.";
|
||||
}
|
||||
|
||||
function bytesToSize(bytes, precision, si)
|
||||
{
|
||||
var ret;
|
||||
si = typeof si !== 'undefined' ? si : 0;
|
||||
if(si != 0) {
|
||||
var kilobyte = 1000;
|
||||
var megabyte = kilobyte * 1000;
|
||||
var gigabyte = megabyte * 1000;
|
||||
var terabyte = gigabyte * 1000;
|
||||
} else {
|
||||
var kilobyte = 1024;
|
||||
var megabyte = kilobyte * 1024;
|
||||
var gigabyte = megabyte * 1024;
|
||||
var terabyte = gigabyte * 1024;
|
||||
}
|
||||
|
||||
if ((bytes >= 0) && (bytes < kilobyte)) {
|
||||
return bytes + ' B';
|
||||
|
||||
} else if ((bytes >= kilobyte) && (bytes < megabyte)) {
|
||||
ret = (bytes / kilobyte).toFixed(precision) + ' K';
|
||||
|
||||
} else if ((bytes >= megabyte) && (bytes < gigabyte)) {
|
||||
ret = (bytes / megabyte).toFixed(precision) + ' M';
|
||||
|
||||
} else if ((bytes >= gigabyte) && (bytes < terabyte)) {
|
||||
ret = (bytes / gigabyte).toFixed(precision) + ' G';
|
||||
|
||||
} else if (bytes >= terabyte) {
|
||||
ret = (bytes / terabyte).toFixed(precision) + ' T';
|
||||
|
||||
} else {
|
||||
return bytes + ' B';
|
||||
}
|
||||
if(si != 0) {
|
||||
return ret + 'B';
|
||||
} else {
|
||||
return ret + 'iB';
|
||||
}
|
||||
}
|
||||
|
||||
function uptime() {
|
||||
$.getJSON("json/stats.json", function(result) {
|
||||
$("#loading-notice").remove();
|
||||
if(result.reload)
|
||||
setTimeout(function() { location.reload(true) }, 1000);
|
||||
|
||||
for (var i = 0; i < result.servers.length; i++) {
|
||||
var TableRow = $("#servers tr#r" + i);
|
||||
var ExpandRow = $("#servers #rt" + i);
|
||||
var hack; // fuck CSS for making me do this
|
||||
if(i%2) hack="odd"; else hack="even";
|
||||
if (!TableRow.length) {
|
||||
$("#servers").append(
|
||||
"<tr id=\"r" + i + "\" data-toggle=\"collapse\" data-target=\"#rt" + i + "\" class=\"accordion-toggle " + hack + "\">" +
|
||||
"<td id=\"online4\"><div class=\"progress\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" +
|
||||
"<td id=\"ip_status\"><div class=\"progress\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" +
|
||||
"<td id=\"name\">加载中</td>" +
|
||||
"<td id=\"type\">加载中</td>" +
|
||||
"<td id=\"location\">加载中</td>" +
|
||||
"<td id=\"uptime\">加载中</td>" +
|
||||
"<td id=\"load\">加载中</td>" +
|
||||
"<td id=\"network\">加载中</td>" +
|
||||
"<td id=\"traffic\">加载中</td>" +
|
||||
"<td id=\"cpu\"><div class=\"progress progress-striped active\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" +
|
||||
"<td id=\"memory\"><div class=\"progress progress-striped active\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" +
|
||||
"<td id=\"hdd\"><div class=\"progress progress-striped active\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-warning\"><small>加载中</small></div></div></td>" +
|
||||
"</tr>" +
|
||||
"<tr class=\"expandRow " + hack + "\"><td colspan=\"12\"><div class=\"accordian-body collapse\" id=\"rt" + i + "\">" +
|
||||
"<div id=\"expand_mem\">加载中</div>" +
|
||||
"<div id=\"expand_swap\">加载中</div>" +
|
||||
"<div id=\"expand_hdd\">加载中</div>" +
|
||||
"<div id=\"expand_custom\">加载中</div>" +
|
||||
"</div></td></tr>"
|
||||
);
|
||||
TableRow = $("#servers tr#r" + i);
|
||||
ExpandRow = $("#servers #rt" + i);
|
||||
server_status[i] = true;
|
||||
}
|
||||
TableRow = TableRow[0];
|
||||
if(error) {
|
||||
TableRow.setAttribute("data-target", "#rt" + i);
|
||||
server_status[i] = true;
|
||||
}
|
||||
|
||||
// Online4
|
||||
if (result.servers[i].online4) {
|
||||
TableRow.children["online4"].children[0].children[0].className = "progress-bar progress-bar-success";
|
||||
TableRow.children["online4"].children[0].children[0].innerHTML = "<small>开启</small>";
|
||||
} else {
|
||||
TableRow.children["online4"].children[0].children[0].className = "progress-bar progress-bar-danger";
|
||||
TableRow.children["online4"].children[0].children[0].innerHTML = "<small>关闭</small>";
|
||||
}
|
||||
|
||||
// Online6
|
||||
//if (result.servers[i].online6) {
|
||||
// TableRow.children["online6"].children[0].children[0].className = "progress-bar progress-bar-success";
|
||||
// TableRow.children["online6"].children[0].children[0].innerHTML = "<small>开启</small>";
|
||||
//} else {
|
||||
// TableRow.children["online6"].children[0].children[0].className = "progress-bar progress-bar-danger";
|
||||
// TableRow.children["online6"].children[0].children[0].innerHTML = "<small>关闭</small>";
|
||||
//}
|
||||
|
||||
// Ipstatus
|
||||
if (result.servers[i].ip_status) {
|
||||
TableRow.children["ip_status"].children[0].children[0].className = "progress-bar progress-bar-success";
|
||||
TableRow.children["ip_status"].children[0].children[0].innerHTML = "<small>MH361</small>";
|
||||
} else {
|
||||
TableRow.children["ip_status"].children[0].children[0].className = "progress-bar progress-bar-danger";
|
||||
TableRow.children["ip_status"].children[0].children[0].innerHTML = "<small>MH370</small>";
|
||||
}
|
||||
|
||||
// Name
|
||||
TableRow.children["name"].innerHTML = result.servers[i].name;
|
||||
|
||||
// Type
|
||||
TableRow.children["type"].innerHTML = result.servers[i].type;
|
||||
|
||||
// Location
|
||||
TableRow.children["location"].innerHTML = result.servers[i].location;
|
||||
if (!result.servers[i].online4 && !result.servers[i].online6) {
|
||||
if (server_status[i]) {
|
||||
TableRow.children["uptime"].innerHTML = "–";
|
||||
TableRow.children["load"].innerHTML = "–";
|
||||
TableRow.children["network"].innerHTML = "–";
|
||||
TableRow.children["traffic"].innerHTML = "–";
|
||||
TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-danger";
|
||||
TableRow.children["cpu"].children[0].children[0].style.width = "100%";
|
||||
TableRow.children["cpu"].children[0].children[0].innerHTML = "<small>关闭</small>";
|
||||
TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-danger";
|
||||
TableRow.children["memory"].children[0].children[0].style.width = "100%";
|
||||
TableRow.children["memory"].children[0].children[0].innerHTML = "<small>关闭</small>";
|
||||
TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-danger";
|
||||
TableRow.children["hdd"].children[0].children[0].style.width = "100%";
|
||||
TableRow.children["hdd"].children[0].children[0].innerHTML = "<small>关闭</small>";
|
||||
if(ExpandRow.hasClass("in")) {
|
||||
ExpandRow.collapse("hide");
|
||||
}
|
||||
TableRow.setAttribute("data-target", "");
|
||||
server_status[i] = false;
|
||||
}
|
||||
} else {
|
||||
if (!server_status[i]) {
|
||||
TableRow.setAttribute("data-target", "#rt" + i);
|
||||
server_status[i] = true;
|
||||
}
|
||||
|
||||
// Uptime
|
||||
TableRow.children["uptime"].innerHTML = result.servers[i].uptime;
|
||||
|
||||
// Load
|
||||
if(result.servers[i].load == -1) {
|
||||
TableRow.children["load"].innerHTML = "–";
|
||||
} else {
|
||||
var loadstr = ""
|
||||
loadstr += result.servers[i].load_1.toFixed(2);
|
||||
loadstr += " | "
|
||||
loadstr += result.servers[i].load_5.toFixed(2);
|
||||
loadstr += " | "
|
||||
loadstr += result.servers[i].load_15.toFixed(2);
|
||||
TableRow.children["load"].innerHTML = loadstr
|
||||
}
|
||||
|
||||
// Network
|
||||
var netstr = "";
|
||||
if(result.servers[i].network_rx < 1000)
|
||||
netstr += result.servers[i].network_rx.toFixed(0) + "B";
|
||||
else if(result.servers[i].network_rx < 1000*1000)
|
||||
netstr += (result.servers[i].network_rx/1000).toFixed(0) + "K";
|
||||
else
|
||||
netstr += (result.servers[i].network_rx/1000/1000).toFixed(1) + "M";
|
||||
netstr += " | "
|
||||
if(result.servers[i].network_tx < 1000)
|
||||
netstr += result.servers[i].network_tx.toFixed(0) + "B";
|
||||
else if(result.servers[i].network_tx < 1000*1000)
|
||||
netstr += (result.servers[i].network_tx/1000).toFixed(0) + "K";
|
||||
else
|
||||
netstr += (result.servers[i].network_tx/1000/1000).toFixed(1) + "M";
|
||||
TableRow.children["network"].innerHTML = netstr;
|
||||
|
||||
//Traffic
|
||||
var trafficstr = "";
|
||||
if(result.servers[i].network_in < 1024)
|
||||
trafficstr += result.servers[i].network_in.toFixed(0) + "B";
|
||||
else if(result.servers[i].network_in < 1024*1024)
|
||||
trafficstr += (result.servers[i].network_in/1024).toFixed(0) + "K";
|
||||
else if(result.servers[i].network_in < 1024*1024*1024)
|
||||
trafficstr += (result.servers[i].network_in/1024/1024).toFixed(1) + "M";
|
||||
else
|
||||
trafficstr += (result.servers[i].network_in/1024/1024/1024).toFixed(2) + "G";
|
||||
trafficstr += " | "
|
||||
if(result.servers[i].network_out < 1024)
|
||||
trafficstr += result.servers[i].network_out.toFixed(0) + "B";
|
||||
else if(result.servers[i].network_out < 1024*1024)
|
||||
trafficstr += (result.servers[i].network_out/1024).toFixed(0) + "K";
|
||||
else if(result.servers[i].network_out < 1024*1024*1024)
|
||||
trafficstr += (result.servers[i].network_out/1024/1024).toFixed(1) + "M";
|
||||
else
|
||||
trafficstr += (result.servers[i].network_out/1024/1024/1024).toFixed(2) + "G";
|
||||
TableRow.children["traffic"].innerHTML = trafficstr;
|
||||
|
||||
// CPU
|
||||
if (result.servers[i].cpu >= 90)
|
||||
TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-danger";
|
||||
else if (result.servers[i].cpu >= 80)
|
||||
TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-warning";
|
||||
else
|
||||
TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-success";
|
||||
TableRow.children["cpu"].children[0].children[0].style.width = result.servers[i].cpu + "%";
|
||||
TableRow.children["cpu"].children[0].children[0].innerHTML = result.servers[i].cpu + "%";
|
||||
|
||||
// Memory
|
||||
var Mem = ((result.servers[i].memory_used/result.servers[i].memory_total)*100.0).toFixed(0);
|
||||
if (Mem >= 90)
|
||||
TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-danger";
|
||||
else if (Mem >= 80)
|
||||
TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-warning";
|
||||
else
|
||||
TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-success";
|
||||
TableRow.children["memory"].children[0].children[0].style.width = Mem + "%";
|
||||
TableRow.children["memory"].children[0].children[0].innerHTML = Mem + "%";
|
||||
ExpandRow[0].children["expand_mem"].innerHTML = "内存: " + bytesToSize(result.servers[i].memory_used*1024, 2) + " / " + bytesToSize(result.servers[i].memory_total*1024, 2);
|
||||
// Swap
|
||||
ExpandRow[0].children["expand_swap"].innerHTML = "交换分区: " + bytesToSize(result.servers[i].swap_used*1024, 2) + " / " + bytesToSize(result.servers[i].swap_total*1024, 2);
|
||||
|
||||
// HDD
|
||||
var HDD = ((result.servers[i].hdd_used/result.servers[i].hdd_total)*100.0).toFixed(0);
|
||||
if (HDD >= 90)
|
||||
TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-danger";
|
||||
else if (HDD >= 80)
|
||||
TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-warning";
|
||||
else
|
||||
TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-success";
|
||||
TableRow.children["hdd"].children[0].children[0].style.width = HDD + "%";
|
||||
TableRow.children["hdd"].children[0].children[0].innerHTML = HDD + "%";
|
||||
ExpandRow[0].children["expand_hdd"].innerHTML = "硬盘: " + bytesToSize(result.servers[i].hdd_used*1024*1024, 2) + " / " + bytesToSize(result.servers[i].hdd_total*1024*1024, 2);
|
||||
|
||||
// Custom
|
||||
if (result.servers[i].custom) {
|
||||
ExpandRow[0].children["expand_custom"].innerHTML = result.servers[i].custom
|
||||
} else {
|
||||
ExpandRow[0].children["expand_custom"].innerHTML = ""
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
d = new Date(result.updated*1000);
|
||||
error = 0;
|
||||
}).fail(function(update_error) {
|
||||
if (!error) {
|
||||
$("#servers > tr.accordion-toggle").each(function(i) {
|
||||
var TableRow = $("#servers tr#r" + i)[0];
|
||||
var ExpandRow = $("#servers #rt" + i);
|
||||
TableRow.children["online4"].children[0].children[0].className = "progress-bar progress-bar-error";
|
||||
TableRow.children["online4"].children[0].children[0].innerHTML = "<small>错误</small>";
|
||||
//TableRow.children["online6"].children[0].children[0].className = "progress-bar progress-bar-error";
|
||||
//TableRow.children["online6"].children[0].children[0].innerHTML = "<small>错误</small>";
|
||||
TableRow.children["ip_status"].children[0].children[0].className = "progress-bar progress-bar-error";
|
||||
TableRow.children["ip_status"].children[0].children[0].innerHTML = "<small>错误</small>";
|
||||
TableRow.children["uptime"].innerHTML = "<div class=\"progress progress-striped active\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-error\"><small>错误</small></div></div>";
|
||||
TableRow.children["load"].innerHTML = "<div class=\"progress progress-striped active\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-error\"><small>错误</small></div></div>";
|
||||
TableRow.children["network"].innerHTML = "<div class=\"progress progress-striped active\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-error\"><small>错误</small></div></div>";
|
||||
TableRow.children["traffic"].innerHTML = "<div class=\"progress progress-striped active\"><div style=\"width: 100%;\" class=\"progress-bar progress-bar-error\"><small>错误</small></div></div>";
|
||||
TableRow.children["cpu"].children[0].children[0].className = "progress-bar progress-bar-error";
|
||||
TableRow.children["cpu"].children[0].children[0].style.width = "100%";
|
||||
TableRow.children["cpu"].children[0].children[0].innerHTML = "<small>错误</small>";
|
||||
TableRow.children["memory"].children[0].children[0].className = "progress-bar progress-bar-error";
|
||||
TableRow.children["memory"].children[0].children[0].style.width = "100%";
|
||||
TableRow.children["memory"].children[0].children[0].innerHTML = "<small>错误</small>";
|
||||
TableRow.children["hdd"].children[0].children[0].className = "progress-bar progress-bar-error";
|
||||
TableRow.children["hdd"].children[0].children[0].style.width = "100%";
|
||||
TableRow.children["hdd"].children[0].children[0].innerHTML = "<small>错误</small>";
|
||||
if(ExpandRow.hasClass("in")) {
|
||||
ExpandRow.collapse("hide");
|
||||
}
|
||||
TableRow.setAttribute("data-target", "");
|
||||
server_status[i] = false;
|
||||
});
|
||||
}
|
||||
error = 1;
|
||||
$("#updated").html("更新错误.");
|
||||
});
|
||||
}
|
||||
|
||||
function updateTime() {
|
||||
if (!error)
|
||||
$("#updated").html("最后更新: " + timeSince(d));
|
||||
}
|
||||
|
||||
uptime();
|
||||
updateTime();
|
||||
setInterval(uptime, 2000);
|
||||
setInterval(updateTime, 500);
|
||||
|
||||
|
||||
// styleswitcher.js
|
||||
function setActiveStyleSheet(title) {
|
||||
var i, a, main;
|
||||
for(i=0; (a = document.getElementsByTagName("link")[i]); i++) {
|
||||
if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")) {
|
||||
a.disabled = true;
|
||||
if(a.getAttribute("title") == title) a.disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getActiveStyleSheet() {
|
||||
var i, a;
|
||||
for(i=0; (a = document.getElementsByTagName("link")[i]); i++) {
|
||||
if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title") && !a.disabled)
|
||||
return a.getAttribute("title");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getPreferredStyleSheet() {
|
||||
var i, a;
|
||||
for(i=0; (a = document.getElementsByTagName("link")[i]); i++) {
|
||||
if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("rel").indexOf("alt") == -1 && a.getAttribute("title"))
|
||||
return a.getAttribute("title");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function createCookie(name,value,days) {
|
||||
if (days) {
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime()+(days*24*60*60*1000));
|
||||
var expires = "; expires="+date.toGMTString();
|
||||
}
|
||||
else expires = "";
|
||||
document.cookie = name+"="+value+expires+"; path=/";
|
||||
}
|
||||
|
||||
function readCookie(name) {
|
||||
var nameEQ = name + "=";
|
||||
var ca = document.cookie.split(';');
|
||||
for(var i=0;i < ca.length;i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0)==' ')
|
||||
c = c.substring(1,c.length);
|
||||
if (c.indexOf(nameEQ) == 0)
|
||||
return c.substring(nameEQ.length,c.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
window.onload = function(e) {
|
||||
var cookie = readCookie("style");
|
||||
var title = cookie ? cookie : getPreferredStyleSheet();
|
||||
setActiveStyleSheet(title);
|
||||
}
|
||||
|
||||
window.onunload = function(e) {
|
||||
var title = getActiveStyleSheet();
|
||||
createCookie("style", title, 365);
|
||||
}
|
||||
|
||||
var cookie = readCookie("style");
|
||||
var title = cookie ? cookie : getPreferredStyleSheet();
|
||||
setActiveStyleSheet(title);
|
2
web/json/.gitignore
vendored
Normal file
2
web/json/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
stats.json
|
||||
stats.json~
|
Loading…
x
Reference in New Issue
Block a user