Compare commits

..

6 Commits

Author SHA1 Message Date
wyx2685
248ff3764f update sing-box core 2024-07-24 19:50:55 +09:00
wyx2685
3dfeba7e68 xray-core 1.8.21 2024-07-22 07:49:04 +09:00
wyx2685
8eb623b3f0 update xray core 2024-07-21 20:56:35 +09:00
wyx2685
cdcbddd464 update core 2024-07-21 06:14:16 +09:00
wyx2685
e81d47321b xray-core 1.8.19 2024-07-17 23:26:09 +09:00
wyx2685
4d82eff518 update Xray-core 1.8.18 2024-07-17 09:21:57 +09:00
12 changed files with 118 additions and 66 deletions

View File

@@ -2,7 +2,6 @@ package panel
import (
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"reflect"
@@ -10,7 +9,6 @@ import (
"strings"
"time"
"github.com/InazumaV/V2bX/common/crypt"
"github.com/goccy/go-json"
)
@@ -186,18 +184,6 @@ func (c *Client) GetNodeInfo() (node *NodeInfo, err error) {
cm = &rsp.CommonNode
node.VAllss = rsp
node.Security = node.VAllss.Tls
if len(rsp.NetworkSettings) > 0 {
err = json.Unmarshal(rsp.NetworkSettings, &rsp.RealityConfig)
if err != nil {
return nil, fmt.Errorf("decode reality config error: %s", err)
}
}
if node.Security == Reality {
if rsp.TlsSettings.PrivateKey == "" {
key := crypt.GenX25519Private([]byte("vless" + c.Token))
rsp.TlsSettings.PrivateKey = base64.RawURLEncoding.EncodeToString(key)
}
}
case "shadowsocks":
rsp := &ShadowsocksNode{}
err = json.Unmarshal(r.Body(), rsp)

View File

@@ -284,15 +284,23 @@ func (n *Hysteria2node) getMasqHandler(tlsconfig *server.TLSConfig, conn net.Pac
}
u, err := url.Parse(c.Masquerade.Proxy.URL)
if err != nil {
return nil, fmt.Errorf(fmt.Sprintf("masquerade.proxy.url %s", err))
return nil, fmt.Errorf("masquerade.proxy.url %s", err)
}
handler = &httputil.ReverseProxy{
Rewrite: func(r *httputil.ProxyRequest) {
r.SetURL(u)
// SetURL rewrites the Host header,
// but we don't want that if rewriteHost is false
Director: func(req *http.Request) {
req.URL.Scheme = u.Scheme
req.URL.Host = u.Host
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
xff := req.Header.Get("X-Forwarded-For")
if xff != "" {
clientIP = xff + ", " + clientIP
}
req.Header.Set("X-Forwarded-For", clientIP)
}
if !c.Masquerade.Proxy.RewriteHost {
r.Out.Host = r.In.Host
req.Host = req.URL.Host
}
},
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {

View File

@@ -51,7 +51,7 @@ func (l *serverLogger) Connect(addr net.Addr, uuid string, tx uint64) {
if err != nil {
l.logger.Panic("Get limiter error", zap.String("tag", l.Tag), zap.Error(err))
}
if _, r := limiterinfo.CheckLimit(format.UserTag(l.Tag, uuid), extractIPFromAddr(addr), addr.Network() == "tcp"); r {
if _, r := limiterinfo.CheckLimit(format.UserTag(l.Tag, uuid), extractIPFromAddr(addr), addr.Network() == "tcp", true); r {
if userLimit, ok := limiterinfo.UserLimitInfo.Load(format.UserTag(l.Tag, uuid)); ok {
userLimit.(*limiter.UserLimitInfo).OverLimit = true
}
@@ -72,7 +72,7 @@ func (l *serverLogger) TCPRequest(addr net.Addr, uuid, reqAddr string) {
if err != nil {
l.logger.Panic("Get limiter error", zap.String("tag", l.Tag), zap.Error(err))
}
if _, r := limiterinfo.CheckLimit(format.UserTag(l.Tag, uuid), extractIPFromAddr(addr), addr.Network() == "tcp"); r {
if _, r := limiterinfo.CheckLimit(format.UserTag(l.Tag, uuid), extractIPFromAddr(addr), addr.Network() == "tcp", true); r {
if userLimit, ok := limiterinfo.UserLimitInfo.Load(format.UserTag(l.Tag, uuid)); ok {
userLimit.(*limiter.UserLimitInfo).OverLimit = true
}
@@ -97,7 +97,7 @@ func (l *serverLogger) UDPRequest(addr net.Addr, uuid string, sessionId uint32,
if err != nil {
l.logger.Panic("Get limiter error", zap.String("tag", l.Tag), zap.Error(err))
}
if _, r := limiterinfo.CheckLimit(format.UserTag(l.Tag, uuid), extractIPFromAddr(addr), addr.Network() == "tcp"); r {
if _, r := limiterinfo.CheckLimit(format.UserTag(l.Tag, uuid), extractIPFromAddr(addr), addr.Network() == "tcp", true); r {
if userLimit, ok := limiterinfo.UserLimitInfo.Load(format.UserTag(l.Tag, uuid)); ok {
userLimit.(*limiter.UserLimitInfo).OverLimit = true
}

View File

@@ -1,6 +1,8 @@
package hy2
import (
"strings"
"github.com/InazumaV/V2bX/api/panel"
"github.com/InazumaV/V2bX/conf"
"github.com/apernet/hysteria/core/v2/server"
@@ -56,7 +58,9 @@ func (h *Hysteria2) AddNode(tag string, info *panel.NodeInfo, config *conf.Optio
h.Hy2nodes[tag] = n
go func() {
if err := s.Serve(); err != nil {
h.Logger.Error("Server Error", zap.Error(err))
if !strings.Contains(err.Error(), "quic: server closed") {
h.Logger.Error("Server Error", zap.Error(err))
}
}
}()
return nil

View File

@@ -108,7 +108,7 @@ func (h *HookServer) RoutedConnection(_ context.Context, conn net.Conn, m adapte
return conn, t
}
ip := m.Source.Addr.String()
if b, r := l.CheckLimit(format.UserTag(m.Inbound, m.User), ip, true); r {
if b, r := l.CheckLimit(format.UserTag(m.Inbound, m.User), ip, true, true); r {
conn.Close()
log.Error("[", m.Inbound, "] ", "Limited ", m.User, " by ip or conn")
return conn, t
@@ -162,7 +162,7 @@ func (h *HookServer) RoutedPacketConnection(_ context.Context, conn N.PacketConn
return conn, t
}
ip := m.Source.Addr.String()
if b, r := l.CheckLimit(format.UserTag(m.Inbound, m.User), ip, true); r {
if b, r := l.CheckLimit(format.UserTag(m.Inbound, m.User), ip, true, false); r {
conn.Close()
log.Error("[", m.Inbound, "] ", "Limited ", m.User, " by ip or conn")
return conn, t

View File

@@ -40,6 +40,15 @@ type WsNetworkConfig struct {
Headers map[string]string `json:"headers"`
}
type GrpcNetworkConfig struct {
ServiceName string `json:"serviceName"`
}
type HttpupgradeNetworkConfig struct {
Path string `json:"path"`
Host string `json:"host"`
}
func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (option.Inbound, error) {
addr, err := netip.ParseAddr(c.ListenIP)
if err != nil {
@@ -170,12 +179,28 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
Headers: headers,
}
case "grpc":
network := GrpcNetworkConfig{}
if len(n.NetworkSettings) != 0 {
err := json.Unmarshal(n.NetworkSettings, &t.GRPCOptions)
err := json.Unmarshal(n.NetworkSettings, &network)
if err != nil {
return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err)
}
}
t.GRPCOptions = option.V2RayGRPCOptions{
ServiceName: network.ServiceName,
}
case "httpupgrade":
network := HttpupgradeNetworkConfig{}
if len(n.NetworkSettings) != 0 {
err := json.Unmarshal(n.NetworkSettings, &network)
if err != nil {
return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err)
}
}
t.HTTPUpgradeOptions = option.V2RayHTTPUpgradeOptions{
Path: network.Path,
Host: network.Host,
}
}
if info.Type == "vless" {
in.Type = "vless"
@@ -203,7 +228,7 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
switch n.Cipher {
case "2022-blake3-aes-128-gcm":
keyLength = 16
case "2022-blake3-aes-256-gcm":
case "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305":
keyLength = 32
default:
keyLength = 16
@@ -263,12 +288,16 @@ func getInboundOptions(tag string, info *panel.NodeInfo, c *conf.Options) (optio
Headers: headers,
}
case "grpc":
network := GrpcNetworkConfig{}
if len(n.NetworkSettings) != 0 {
err := json.Unmarshal(n.NetworkSettings, &t.GRPCOptions)
err := json.Unmarshal(n.NetworkSettings, &network)
if err != nil {
return option.Inbound{}, fmt.Errorf("decode NetworkSettings error: %s", err)
}
}
t.GRPCOptions = option.V2RayGRPCOptions{
ServiceName: network.ServiceName,
}
default:
t.Type = ""
}

View File

@@ -177,7 +177,8 @@ func (d *DefaultDispatcher) getLink(ctx context.Context, network net.Network) (*
// Speed Limit and Device Limit
w, reject := limit.CheckLimit(user.Email,
sessionInbound.Source.Address.IP().String(),
network == net.Network_TCP)
network == net.Network_TCP,
sessionInbound.Source.Network == net.Network_TCP)
if reject {
errors.LogInfo(ctx, "Limited ", user.Email, " by conn or ip")
common.Close(outboundLink.Writer)
@@ -241,7 +242,7 @@ func (d *DefaultDispatcher) shouldOverride(ctx context.Context, result SniffResu
protocolString = resComp.ProtocolForDomainResult()
}
for _, p := range request.OverrideDestinationForProtocol {
if strings.HasPrefix(protocolString, p) || strings.HasPrefix(protocolString, p) {
if strings.HasPrefix(protocolString, p) || strings.HasPrefix(p, protocolString) {
return true
}
if fkr0, ok := d.fdns.(dns.FakeDNSEngineRev0); ok && protocolString != "bittorrent" && p == "fakedns" &&

View File

@@ -190,7 +190,7 @@ func buildV2ray(config *conf.Options, nodeInfo *panel.NodeInfo, inbound *coreCon
return nil
}
t := coreConf.TransportProtocol(nodeInfo.VAllss.Network)
t := coreConf.TransportProtocol(v.Network)
inbound.StreamSetting = &coreConf.StreamConfig{Network: &t}
switch v.Network {
case "tcp":
@@ -208,6 +208,26 @@ func buildV2ray(config *conf.Options, nodeInfo *panel.NodeInfo, inbound *coreCon
if err != nil {
return fmt.Errorf("unmarshal grpc settings error: %s", err)
}
case "http":
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.HTTPSettings)
if err != nil {
return fmt.Errorf("unmarshal grpc settings error: %s", err)
}
case "quic":
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.QUICSettings)
if err != nil {
return fmt.Errorf("unmarshal grpc settings error: %s", err)
}
case "httpupgrade":
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.HTTPUPGRADESettings)
if err != nil {
return fmt.Errorf("unmarshal httpupgrade settings error: %s", err)
}
case "splithttp":
err := json.Unmarshal(v.NetworkSettings, &inbound.StreamSetting.SplitHTTPSettings)
if err != nil {
return fmt.Errorf("unmarshal splithttp settings error: %s", err)
}
default:
return errors.New("the network type is not vail")
}

12
go.mod
View File

@@ -14,12 +14,12 @@ require (
github.com/goccy/go-json v0.10.3
github.com/hashicorp/go-multierror v1.1.1
github.com/juju/ratelimit v1.0.2
github.com/sagernet/sing v0.5.0-alpha.12
github.com/sagernet/sing-box v1.10.0
github.com/sagernet/sing v0.5.0-alpha.13
github.com/sagernet/sing-box v1.10.0-alpha.23
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.15.0
github.com/xtls/xray-core v1.8.17
github.com/xtls/xray-core v1.8.21
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.25.0
golang.org/x/sys v0.22.0
@@ -211,7 +211,7 @@ require (
github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 // indirect
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.27 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
github.com/selectel/domains-go v1.1.0 // indirect
github.com/selectel/go-selvpcclient/v3 v3.1.1 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
@@ -273,5 +273,5 @@ require (
lukechampine.com/blake3 v1.3.0 // indirect
)
//github.com/apernet/hysteria/core v1.3.5-0.20240201034858-bb99579bb92c => /root/hysteria/core
replace github.com/sagernet/sing-box v1.10.0 => github.com/wyx2685/sing-box_mod v0.0.9
// replace github.com/sagernet/sing-box v1.10.0-alpha.22 => /root/sing-box_mod
replace github.com/sagernet/sing-box v1.10.0-alpha.23 => github.com/wyx2685/sing-box_mod v1.10.0-alpha.23

16
go.sum
View File

@@ -777,8 +777,8 @@ github.com/sagernet/quic-go v0.45.1-beta.2/go.mod h1:+N3FqM9DAzOWfe64uxXuBejVJwX
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.5.0-alpha.12 h1:pjffG3SUpuF9PLDCqPO2fOAUozXItIBmnMVTKQ/QMhM=
github.com/sagernet/sing v0.5.0-alpha.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.5.0-alpha.13 h1:fpR4TFZfu/9V3LbHSAnnnwcaXGMF8ijmAAPoY2WHSKw=
github.com/sagernet/sing v0.5.0-alpha.13/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.3.0-beta.10 h1:Js61EjQXVpcu2VDegWEQTH1isCcVwJju8WEHYgG4tQ0=
github.com/sagernet/sing-dns v0.3.0-beta.10/go.mod h1:nXE6EYMXahB5DV3AcXYbFfuorqF7tbQ86kxweSxRKM4=
github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo=
@@ -808,8 +808,8 @@ github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.27 h1:yGAraK1uUjlhSXgNMIy8o/J4LFNcy7yeipBqt9N9mVg=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.27/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/selectel/domains-go v1.1.0 h1:futG50J43ALLKQAnZk9H9yOtLGnSUh7c5hSvuC5gSHo=
github.com/selectel/domains-go v1.1.0/go.mod h1:SugRKfq4sTpnOHquslCpzda72wV8u0cMBHx0C0l+bzA=
github.com/selectel/go-selvpcclient/v3 v3.1.1 h1:C1q2LqqosiapoLpnGITGmysg0YCSQYDo2Gh69CioevM=
@@ -941,16 +941,16 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs=
github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI=
github.com/wyx2685/sing-box_mod v0.0.9 h1:xxxPk107vFiAFiD8jOCCAHbNLPJ5Jgq5IJIi1Lil4NI=
github.com/wyx2685/sing-box_mod v0.0.9/go.mod h1:0IErt79whUyLappgPDCSORcDQCP1kubkf9KW6FVqPOk=
github.com/wyx2685/sing-box_mod v1.10.0-alpha.23 h1:DqByg8ldMTMXoUBLJO1fGhmOE7DavSR7Zlr1uYGl1ZY=
github.com/wyx2685/sing-box_mod v1.10.0-alpha.23/go.mod h1:a9BYzpu6AtQHdiIkf2B059xPFd95jh9QKXS2EaX8p7E=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d h1:+B97uD9uHLgAAulhigmys4BVwZZypzK7gPN3WtpgRJg=
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
github.com/xtls/xray-core v1.8.17 h1:T+A/hkBB33P0sEGXDiuKrQMZUOYxbZ84JlsYfyRiK8w=
github.com/xtls/xray-core v1.8.17/go.mod h1:qEVGJD2suPN7EArG3r5EX6pYGV0QLiSRTlDMn0paJkc=
github.com/xtls/xray-core v1.8.21 h1:cNdepud+R9PENKzXlSZsq0je4BWI6liXAuep6CD6xvk=
github.com/xtls/xray-core v1.8.21/go.mod h1:0CwyMPNA5Cs+ukPXHbYQGgne/ug0PuXOSVqBu7zyXOc=
github.com/yandex-cloud/go-genproto v0.0.0-20240318083951-4fe6125f286e h1:jLIqA7M9qY31g/Nw/5htVD0DFbxmLnlFZcHKJiG3osI=
github.com/yandex-cloud/go-genproto v0.0.0-20240318083951-4fe6125f286e/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
github.com/yandex-cloud/go-sdk v0.0.0-20240318084659-dfa50323a0b4 h1:wtzLQJmghkSUb1YkeFphIh7ST7NNVDaVOJZSAJcjMdw=

View File

@@ -3,6 +3,7 @@ package limiter
import (
"errors"
"regexp"
"strings"
"sync"
"time"
@@ -129,7 +130,10 @@ func (l *Limiter) UpdateDynamicSpeedLimit(tag, uuid string, limit int, expire ti
return nil
}
func (l *Limiter) CheckLimit(taguuid string, ip string, isTcp bool) (Bucket *ratelimit.Bucket, Reject bool) {
func (l *Limiter) CheckLimit(taguuid string, ip string, isTcp bool, noSSUDP bool) (Bucket *ratelimit.Bucket, Reject bool) {
// check if ipv4 mapped ipv6
ip = strings.TrimPrefix(ip, "::ffff:")
// ip and conn limiter
if l.ConnLimiter.AddConnCount(taguuid, ip, isTcp) {
return nil, true
@@ -155,23 +159,24 @@ func (l *Limiter) CheckLimit(taguuid string, ip string, isTcp bool) (Bucket *rat
userLimit = determineSpeedLimit(u.SpeedLimit, u.DynamicSpeedLimit)
}
}
// Store online user for device limit
ipMap := new(sync.Map)
ipMap.Store(ip, uid)
// If any device is online
if v, ok := l.UserOnlineIP.LoadOrStore(taguuid, ipMap); ok {
ipMap := v.(*sync.Map)
// If this is a new ip
if _, ok := ipMap.LoadOrStore(ip, uid); !ok {
counter := 0
ipMap.Range(func(key, value interface{}) bool {
counter++
return true
})
if counter > deviceLimit && deviceLimit > 0 {
ipMap.Delete(ip)
return nil, true
if noSSUDP {
// Store online user for device limit
ipMap := new(sync.Map)
ipMap.Store(ip, uid)
// If any device is online
if v, ok := l.UserOnlineIP.LoadOrStore(taguuid, ipMap); ok {
ipMap := v.(*sync.Map)
// If this is a new ip
if _, ok := ipMap.LoadOrStore(ip, uid); !ok {
counter := 0
ipMap.Range(func(key, value interface{}) bool {
counter++
return true
})
if counter > deviceLimit && deviceLimit > 0 {
ipMap.Delete(ip)
return nil, true
}
}
}
}

View File

@@ -1,7 +1,6 @@
package node
import (
"fmt"
"strconv"
"github.com/InazumaV/V2bX/api/panel"
@@ -60,7 +59,7 @@ func (c *Controller) reportUserTrafficTask() (err error) {
data := make(map[int][]string)
for _, onlineuser := range result {
// json structure: { UID1:["ip1","ip2"],UID2:["ip3","ip4"] }
data[onlineuser.UID] = append(data[onlineuser.UID], fmt.Sprintf("%s_%d", onlineuser.IP, c.info.Id))
data[onlineuser.UID] = append(data[onlineuser.UID], onlineuser.IP)
if _, ok := reportOnline[onlineuser.UID]; ok {
reportOnline[onlineuser.UID]++
} else {