mirror of
https://github.com/netbirdio/netbird.git
synced 2024-12-12 09:50:47 +01:00
Extend the system informations
This commit is contained in:
parent
9fa0fbda0d
commit
ef4a2ccbca
119
client/system/chassis.go
Normal file
119
client/system/chassis.go
Normal file
@ -0,0 +1,119 @@
|
||||
package system
|
||||
|
||||
const (
|
||||
ChassisTypeOther uint = 0x01 // Other
|
||||
ChassisTypeUnknown uint = 0x02 // Unknown
|
||||
ChassisTypeDesktop uint = 0x03 // Desktop
|
||||
ChassisTypeLowProfileDesktop uint = 0x04 // Low Profile Desktop
|
||||
ChassisTypePizzaBox uint = 0x05 // Pizza Box
|
||||
ChassisTypeMiniTower uint = 0x06 // Mini Tower
|
||||
ChassisTypeTower uint = 0x07 // Tower
|
||||
ChassisTypePortable uint = 0x08 // Portable
|
||||
ChassisTypeLaptop uint = 0x09 // Laptop
|
||||
ChassisTypeNotebook uint = 0x0a // Notebook
|
||||
ChassisTypeHandHeld uint = 0x0b // Hand Held
|
||||
ChassisTypeDockingStation uint = 0x0c // Docking Station
|
||||
ChassisTypeAllInOne uint = 0x0d // All in One
|
||||
ChassisTypeSubNotebook uint = 0x0e // Sub Notebook
|
||||
ChassisTypeSpacesaving uint = 0x0f // Space-saving
|
||||
ChassisTypeLunchBox uint = 0x10 // Lunch Box
|
||||
ChassisTypeMainServerChassis uint = 0x11 // Main Server Chassis
|
||||
ChassisTypeExpansionChassis uint = 0x12 // Expansion Chassis
|
||||
ChassisTypeSubChassis uint = 0x13 // SubChassis
|
||||
ChassisTypeBusExpansionChassis uint = 0x14 // Bus Expansion Chassis
|
||||
ChassisTypePeripheralChassis uint = 0x15 // Peripheral Chassis
|
||||
ChassisTypeRAIDChassis uint = 0x16 // RAID Chassis
|
||||
ChassisTypeRackMountChassis uint = 0x17 // Rack Mount Chassis
|
||||
ChassisTypeSealedcasePC uint = 0x18 // Sealed-case PC
|
||||
ChassisTypeMultisystemChassis uint = 0x19 // Multi-system chassis
|
||||
ChassisTypeCompactPCI uint = 0x1a // Compact PCI
|
||||
ChassisTypeAdvancedTCA uint = 0x1b // Advanced TCA
|
||||
ChassisTypeBlade uint = 0x1c // Blade
|
||||
ChassisTypeBladeChassis uint = 0x1d // Blade Chassis
|
||||
ChassisTypeTablet uint = 0x1e // Tablet
|
||||
ChassisTypeConvertible uint = 0x1f // Convertible
|
||||
ChassisTypeDetachable uint = 0x20 // Detachable
|
||||
ChassisTypeIoTGateway uint = 0x21 // IoT Gateway
|
||||
ChassisTypeEmbeddedPC uint = 0x22 // Embedded PC
|
||||
ChassisTypeMiniPC uint = 0x23 // Mini PC
|
||||
ChassisTypeStickPC uint = 0x24 // Stick PC
|
||||
)
|
||||
|
||||
func chassisTypeDesc(id uint) string {
|
||||
switch id {
|
||||
case ChassisTypeOther:
|
||||
return "Other"
|
||||
case ChassisTypeUnknown:
|
||||
return "Unknown"
|
||||
case ChassisTypeDesktop:
|
||||
return "Desktop"
|
||||
case ChassisTypeLowProfileDesktop:
|
||||
return "Low Profile Desktop"
|
||||
case ChassisTypePizzaBox:
|
||||
return "Pizza Box"
|
||||
case ChassisTypeMiniTower:
|
||||
return "Mini Tower"
|
||||
case ChassisTypeTower:
|
||||
return "Tower"
|
||||
case ChassisTypePortable:
|
||||
return "Portable"
|
||||
case ChassisTypeLaptop:
|
||||
return "Laptop"
|
||||
case ChassisTypeNotebook:
|
||||
return "Notebook"
|
||||
case ChassisTypeHandHeld:
|
||||
return "Hand Held"
|
||||
case ChassisTypeDockingStation:
|
||||
return "Docking Station"
|
||||
case ChassisTypeAllInOne:
|
||||
return "All In One"
|
||||
case ChassisTypeSubNotebook:
|
||||
return "Sub Notebook"
|
||||
case ChassisTypeSpacesaving:
|
||||
return "Space-saving"
|
||||
case ChassisTypeLunchBox:
|
||||
return "Lunch Box"
|
||||
case ChassisTypeMainServerChassis:
|
||||
return "Main Server Chassis"
|
||||
case ChassisTypeExpansionChassis:
|
||||
return "Expansion Chassis"
|
||||
case ChassisTypeSubChassis:
|
||||
return "Sub Chassis"
|
||||
case ChassisTypeBusExpansionChassis:
|
||||
return "Bus Expansion Chassis"
|
||||
case ChassisTypePeripheralChassis:
|
||||
return "Peripheral Chassis"
|
||||
case ChassisTypeRAIDChassis:
|
||||
return "RAID Chassis"
|
||||
case ChassisTypeRackMountChassis:
|
||||
return "Rack Mount Chassis"
|
||||
case ChassisTypeSealedcasePC:
|
||||
return "Sealed-case PC"
|
||||
case ChassisTypeMultisystemChassis:
|
||||
return "Multi-system"
|
||||
case ChassisTypeCompactPCI:
|
||||
return "CompactPCI"
|
||||
case ChassisTypeAdvancedTCA:
|
||||
return "AdvancedTCA"
|
||||
case ChassisTypeBlade:
|
||||
return "Blade"
|
||||
case ChassisTypeBladeChassis:
|
||||
return "Blade Chassis"
|
||||
case ChassisTypeTablet:
|
||||
return "Tablet"
|
||||
case ChassisTypeConvertible:
|
||||
return "Convertible"
|
||||
case ChassisTypeDetachable:
|
||||
return "Detachable"
|
||||
case ChassisTypeIoTGateway:
|
||||
return "IoT Gateway"
|
||||
case ChassisTypeEmbeddedPC:
|
||||
return "Embedded PC"
|
||||
case ChassisTypeMiniPC:
|
||||
return "Mini PC"
|
||||
case ChassisTypeStickPC:
|
||||
return "Stick PC"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
@ -31,6 +31,27 @@ type Info struct {
|
||||
CPUs int
|
||||
WiretrusteeVersion string
|
||||
UIVersion string
|
||||
|
||||
BiosManufacturer string
|
||||
BiosVersion string
|
||||
ChassisType uint
|
||||
ChassisTypeDesc string
|
||||
ConnectionIp string
|
||||
ConnectionMacAddress string
|
||||
CPUSignature string
|
||||
DefaultGatewayIp string
|
||||
ExternalIp string
|
||||
LastReboot string
|
||||
LocalIp string
|
||||
MacAddress string
|
||||
KernelMajorVersion string
|
||||
KernelMinorVersion string
|
||||
OSBuild string
|
||||
OSProductName string
|
||||
ProductTypeDesc string
|
||||
SerialNumber string
|
||||
SystemManufacturer string
|
||||
SystemProductName string
|
||||
}
|
||||
|
||||
// extractUserAgent extracts Netbird's agent (client) name and version from the outgoing context
|
||||
|
@ -33,11 +33,23 @@ func GetInfo(ctx context.Context) *Info {
|
||||
log.Warnf("got an error while retrieving macOS version with sw_vers, error: %s. Using darwin version instead.\n", err)
|
||||
swVersion = []byte(release)
|
||||
}
|
||||
gio := &Info{Kernel: sysName, OSVersion: strings.TrimSpace(string(swVersion)), Core: release, Platform: machine, OS: sysName, GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
|
||||
systemHostname, _ := os.Hostname()
|
||||
gio.Hostname = extractDeviceName(ctx, systemHostname)
|
||||
gio.WiretrusteeVersion = version.NetbirdVersion()
|
||||
gio.UIVersion = extractUserAgent(ctx)
|
||||
|
||||
systemHostname, _ := os.Hostname()
|
||||
localAddr, macAddr := localAddresses()
|
||||
gio := &Info{
|
||||
Kernel: sysName,
|
||||
OSVersion: strings.TrimSpace(string(swVersion)),
|
||||
Core: release,
|
||||
Platform: machine,
|
||||
OS: sysName,
|
||||
GoOS: runtime.GOOS,
|
||||
CPUs: runtime.NumCPU(),
|
||||
Hostname: extractDeviceName(ctx, systemHostname),
|
||||
WiretrusteeVersion: version.NetbirdVersion(),
|
||||
UIVersion: extractUserAgent(ctx),
|
||||
LastReboot: lastReboot(),
|
||||
LocalIp: localAddr,
|
||||
MacAddress: macAddr,
|
||||
}
|
||||
return gio
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/zcalusic/sysinfo"
|
||||
|
||||
"github.com/netbirdio/netbird/version"
|
||||
)
|
||||
@ -26,7 +27,7 @@ func GetInfo(ctx context.Context) *Info {
|
||||
}
|
||||
|
||||
releaseInfo := _getReleaseInfo()
|
||||
for strings.Contains(info, "broken pipe") {
|
||||
for strings.Contains(releaseInfo, "broken pipe") {
|
||||
releaseInfo = _getReleaseInfo()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
@ -50,15 +51,51 @@ func GetInfo(ctx context.Context) *Info {
|
||||
if osName == "" {
|
||||
osName = osInfo[3]
|
||||
}
|
||||
gio := &Info{Kernel: osInfo[0], Core: osInfo[1], Platform: osInfo[2], OS: osName, OSVersion: osVer, GoOS: runtime.GOOS, CPUs: runtime.NumCPU()}
|
||||
systemHostname, _ := os.Hostname()
|
||||
gio.Hostname = extractDeviceName(ctx, systemHostname)
|
||||
gio.WiretrusteeVersion = version.NetbirdVersion()
|
||||
gio.UIVersion = extractUserAgent(ctx)
|
||||
|
||||
sysinfo := extendedInfo()
|
||||
localAddr, macAddr := localAddresses()
|
||||
gio := &Info{
|
||||
Kernel: osInfo[0],
|
||||
Core: osInfo[1],
|
||||
Platform: osInfo[2],
|
||||
OS: osName,
|
||||
OSVersion: osVer,
|
||||
GoOS: runtime.GOOS,
|
||||
CPUs: runtime.NumCPU(),
|
||||
Hostname: extractDeviceName(ctx, systemHostname),
|
||||
WiretrusteeVersion: version.NetbirdVersion(),
|
||||
UIVersion: extractUserAgent(ctx),
|
||||
BiosManufacturer: sysinfo.BIOS.Vendor,
|
||||
BiosVersion: sysinfo.BIOS.Version,
|
||||
ChassisType: sysinfo.Chassis.Type,
|
||||
ChassisTypeDesc: chassisTypeDesc(sysinfo.Chassis.Type), // make no sense to send the string to the server
|
||||
/*
|
||||
ConnectionIp: "", // "10.145.236.123",
|
||||
ConnectionMacAddress: "", // 52-54-00-1a-31-05"
|
||||
CPUSignature: "", // "198339"
|
||||
*/
|
||||
LastReboot: lastReboot(),
|
||||
LocalIp: localAddr,
|
||||
MacAddress: macAddr,
|
||||
/*
|
||||
OSBuild: string // 22621
|
||||
OSProductName: string // "Windows 11 Home"
|
||||
ProductTypeDesc: string // "Workstation"
|
||||
SerialNumber: string // "MP1PKC2C""
|
||||
SystemProductName: string // "81ND", # how to get this?
|
||||
*/
|
||||
SystemManufacturer: sysinfo.Product.Vendor, // todo validate
|
||||
}
|
||||
return gio
|
||||
}
|
||||
|
||||
func extendedInfo() sysinfo.SysInfo {
|
||||
var si sysinfo.SysInfo
|
||||
si.GetSysInfo()
|
||||
return si
|
||||
}
|
||||
|
||||
func _getInfo() string {
|
||||
cmd := exec.Command("uname", "-srio")
|
||||
cmd.Stdin = strings.NewReader("some")
|
||||
|
13
client/system/info_linux_test.go
Normal file
13
client/system/info_linux_test.go
Normal file
@ -0,0 +1,13 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func TestGetInfo(t *testing.T) {
|
||||
info := GetInfo(context.Background())
|
||||
log.Infof("info: %+v", info)
|
||||
}
|
42
client/system/ip.go
Normal file
42
client/system/ip.go
Normal file
@ -0,0 +1,42 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func localAddresses() (string, string) {
|
||||
conn, err := net.Dial("udp", "8.8.8.8:53")
|
||||
if err != nil {
|
||||
log.Errorf("failed to check ip: %s", err)
|
||||
return "", ""
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
localAddr := conn.LocalAddr().(*net.UDPAddr)
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
log.Errorf("failed to list interfaces: %s", err)
|
||||
return "", ""
|
||||
}
|
||||
for _, i := range ifaces {
|
||||
addrs, err := i.Addrs()
|
||||
if err != nil {
|
||||
log.Errorf("failed to list addresses: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
cidr, _, err := net.ParseCIDR(addr.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if localAddr.IP.String() == cidr.String() {
|
||||
return addr.String(), i.HardwareAddr.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
17
client/system/reboot.go
Normal file
17
client/system/reboot.go
Normal file
@ -0,0 +1,17 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/host"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func lastReboot() string {
|
||||
info, err := host.Info()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get boot time: %s", err)
|
||||
return ""
|
||||
}
|
||||
return time.Unix(int64(info.BootTime), 0).String()
|
||||
}
|
6
go.mod
6
go.mod
@ -22,7 +22,7 @@ require (
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
|
||||
golang.org/x/crypto v0.14.0
|
||||
golang.org/x/sys v0.13.0
|
||||
golang.org/x/sys v0.15.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230704135630-469159ecf7d1
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||
@ -64,10 +64,12 @@ require (
|
||||
github.com/pion/transport/v3 v3.0.1
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/rs/xid v1.3.0
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/things-go/go-socks5 v0.0.4
|
||||
github.com/yusufpapurcu/wmi v1.2.3
|
||||
github.com/zcalusic/sysinfo v1.0.2
|
||||
go.opentelemetry.io/otel v1.11.1
|
||||
go.opentelemetry.io/otel/exporters/prometheus v0.33.0
|
||||
go.opentelemetry.io/otel/metric v0.33.0
|
||||
@ -143,6 +145,8 @@ require (
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
|
||||
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.13 // indirect
|
||||
github.com/tklauser/numcpus v0.7.0 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||
github.com/yuin/goldmark v1.4.13 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
|
11
go.sum
11
go.sum
@ -476,6 +476,8 @@ github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
|
||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
@ -519,6 +521,10 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/things-go/go-socks5 v0.0.4 h1:jMQjIc+qhD4z9cITOMnBiwo9dDmpGuXmBlkRFrl/qD0=
|
||||
github.com/things-go/go-socks5 v0.0.4/go.mod h1:sh4K6WHrmHZpjxLTCHyYtXYH8OUuD+yZun41NomR1IQ=
|
||||
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
|
||||
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
|
||||
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
|
||||
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
@ -537,6 +543,8 @@ github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zcalusic/sysinfo v1.0.2 h1:nwTTo2a+WQ0NXwo0BGRojOJvJ/5XKvQih+2RrtWqfxc=
|
||||
github.com/zcalusic/sysinfo v1.0.2/go.mod h1:kluzTYflRWo6/tXVMJPdEjShsbPpsFRyy+p1mBQPC30=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
@ -755,8 +763,9 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
|
Loading…
Reference in New Issue
Block a user