mirror of
https://github.com/VaalaCat/frp-panel.git
synced 2025-12-24 11:51:06 +08:00
feat: more info on wg detail page
This commit is contained in:
@@ -32,6 +32,8 @@ func GetWireGuardRuntimeInfo(ctx *app.Context, req *pb.GetWireGuardRuntimeInfoRe
|
||||
log.WithError(err).Errorf("failed to call get wireguard runtime info with clientId: %s", wgRecord.ClientID)
|
||||
return nil, errors.New("failed to call get wireguard runtime info")
|
||||
}
|
||||
resp.WgDeviceRuntimeInfo.ClientId = wgRecord.ClientID
|
||||
resp.WgDeviceRuntimeInfo.VirtualIp = wgRecord.LocalAddress
|
||||
|
||||
log.Debugf("get wireguard runtime info success with clientId: %s, interfaceName: %s, runtimeInfo: %s",
|
||||
wgRecord.ClientID, wgRecord.Name, resp.GetWgDeviceRuntimeInfo().String())
|
||||
|
||||
@@ -2,6 +2,7 @@ package defs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
|
||||
"github.com/VaalaCat/frp-panel/pb"
|
||||
@@ -63,6 +64,11 @@ func (w *WireGuardPeerConfig) GetParsedPublicKey() wgtypes.Key {
|
||||
return w.parsedPublicKey
|
||||
}
|
||||
|
||||
func (w *WireGuardPeerConfig) HexPublicKey() string {
|
||||
pk := w.GetParsedPublicKey()
|
||||
return hex.EncodeToString(pk[:])
|
||||
}
|
||||
|
||||
func (w *WireGuardPeerConfig) GetParsedPresharedKey() *wgtypes.Key {
|
||||
if w.GetPresharedKey() == "" {
|
||||
return nil
|
||||
|
||||
@@ -16,6 +16,8 @@ message WireGuardPeerConfig {
|
||||
Endpoint endpoint = 8; // (可选) Peer 的公网端点 "host:port"
|
||||
uint32 persistent_keepalive = 9; // 可选
|
||||
repeated string tags = 10; // 标签
|
||||
|
||||
string virtual_ip = 11; // 节点虚拟 IP
|
||||
}
|
||||
|
||||
// WireGuardConfig wg 配置
|
||||
@@ -88,14 +90,15 @@ message WGPeerRuntimeInfo {
|
||||
string public_key = 1;
|
||||
string preshared_key = 2;
|
||||
repeated string allowed_ips = 3;
|
||||
string endpoint_host = 4;
|
||||
uint32 endpoint_port = 5;
|
||||
// string endpoint_host = 4; // 不再使用
|
||||
// uint32 endpoint_port = 5; // 不再使用
|
||||
uint64 tx_bytes = 6;
|
||||
uint64 rx_bytes = 7;
|
||||
uint32 persistent_keepalive_interval = 8;
|
||||
uint64 last_handshake_time_nsec = 9;
|
||||
uint64 last_handshake_time_sec = 10;
|
||||
string client_id = 11;
|
||||
string endpoint = 12;
|
||||
|
||||
map<string, string> extra = 100;
|
||||
}
|
||||
@@ -109,6 +112,10 @@ message WGDeviceRuntimeInfo {
|
||||
string client_id = 6;
|
||||
map<uint32, uint32> ping_map = 7; // to peer endpoint ping
|
||||
string interface_name = 8;
|
||||
map<string, uint32> virt_addr_ping_map = 9; // to peer virtual address ping
|
||||
map<string, uint32> peer_virt_addr_map = 10; // to peer virtual address map
|
||||
map<string, WireGuardPeerConfig> peer_config_map = 11; // to peer config map
|
||||
string virtual_ip = 12; // 节点虚拟 IP
|
||||
|
||||
map<string, string> extra = 100;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func (w *WireGuard) AsBasePeerConfig(specifiedEndpoint *Endpoint) (*pb.WireGuard
|
||||
if err != nil {
|
||||
return nil, errors.Join(errors.New("parse private key error"), err)
|
||||
}
|
||||
_, localIPPrefix, err := ParseIPOrCIDRWithNetip(w.LocalAddress)
|
||||
addr, localIPPrefix, err := ParseIPOrCIDRWithNetip(w.LocalAddress)
|
||||
if err != nil {
|
||||
return nil, errors.Join(errors.New("parse local address error"), err)
|
||||
}
|
||||
@@ -87,6 +87,7 @@ func (w *WireGuard) AsBasePeerConfig(specifiedEndpoint *Endpoint) (*pb.WireGuard
|
||||
AllowedIps: []string{localIPPrefixAllowed.String()},
|
||||
PersistentKeepalive: 20,
|
||||
Tags: w.Tags,
|
||||
VirtualIp: addr.String(),
|
||||
}
|
||||
|
||||
// 优先使用指定的 Endpoint
|
||||
|
||||
@@ -34,6 +34,7 @@ type WireGuardPeerConfig struct {
|
||||
Endpoint *Endpoint `protobuf:"bytes,8,opt,name=endpoint,proto3" json:"endpoint,omitempty"` // (可选) Peer 的公网端点 "host:port"
|
||||
PersistentKeepalive uint32 `protobuf:"varint,9,opt,name=persistent_keepalive,json=persistentKeepalive,proto3" json:"persistent_keepalive,omitempty"` // 可选
|
||||
Tags []string `protobuf:"bytes,10,rep,name=tags,proto3" json:"tags,omitempty"` // 标签
|
||||
VirtualIp string `protobuf:"bytes,11,opt,name=virtual_ip,json=virtualIp,proto3" json:"virtual_ip,omitempty"` // 节点虚拟 IP
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -138,6 +139,13 @@ func (x *WireGuardPeerConfig) GetTags() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *WireGuardPeerConfig) GetVirtualIp() string {
|
||||
if x != nil {
|
||||
return x.VirtualIp
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// WireGuardConfig wg 配置
|
||||
type WireGuardConfig struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
@@ -728,19 +736,20 @@ func (x *AclRuleConfig) GetDst() []string {
|
||||
}
|
||||
|
||||
type WGPeerRuntimeInfo struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
PublicKey string `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
|
||||
PresharedKey string `protobuf:"bytes,2,opt,name=preshared_key,json=presharedKey,proto3" json:"preshared_key,omitempty"`
|
||||
AllowedIps []string `protobuf:"bytes,3,rep,name=allowed_ips,json=allowedIps,proto3" json:"allowed_ips,omitempty"`
|
||||
EndpointHost string `protobuf:"bytes,4,opt,name=endpoint_host,json=endpointHost,proto3" json:"endpoint_host,omitempty"`
|
||||
EndpointPort uint32 `protobuf:"varint,5,opt,name=endpoint_port,json=endpointPort,proto3" json:"endpoint_port,omitempty"`
|
||||
TxBytes uint64 `protobuf:"varint,6,opt,name=tx_bytes,json=txBytes,proto3" json:"tx_bytes,omitempty"`
|
||||
RxBytes uint64 `protobuf:"varint,7,opt,name=rx_bytes,json=rxBytes,proto3" json:"rx_bytes,omitempty"`
|
||||
PersistentKeepaliveInterval uint32 `protobuf:"varint,8,opt,name=persistent_keepalive_interval,json=persistentKeepaliveInterval,proto3" json:"persistent_keepalive_interval,omitempty"`
|
||||
LastHandshakeTimeNsec uint64 `protobuf:"varint,9,opt,name=last_handshake_time_nsec,json=lastHandshakeTimeNsec,proto3" json:"last_handshake_time_nsec,omitempty"`
|
||||
LastHandshakeTimeSec uint64 `protobuf:"varint,10,opt,name=last_handshake_time_sec,json=lastHandshakeTimeSec,proto3" json:"last_handshake_time_sec,omitempty"`
|
||||
ClientId string `protobuf:"bytes,11,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
|
||||
Extra map[string]string `protobuf:"bytes,100,rep,name=extra,proto3" json:"extra,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
PublicKey string `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
|
||||
PresharedKey string `protobuf:"bytes,2,opt,name=preshared_key,json=presharedKey,proto3" json:"preshared_key,omitempty"`
|
||||
AllowedIps []string `protobuf:"bytes,3,rep,name=allowed_ips,json=allowedIps,proto3" json:"allowed_ips,omitempty"`
|
||||
// string endpoint_host = 4; // 不再使用
|
||||
// uint32 endpoint_port = 5; // 不再使用
|
||||
TxBytes uint64 `protobuf:"varint,6,opt,name=tx_bytes,json=txBytes,proto3" json:"tx_bytes,omitempty"`
|
||||
RxBytes uint64 `protobuf:"varint,7,opt,name=rx_bytes,json=rxBytes,proto3" json:"rx_bytes,omitempty"`
|
||||
PersistentKeepaliveInterval uint32 `protobuf:"varint,8,opt,name=persistent_keepalive_interval,json=persistentKeepaliveInterval,proto3" json:"persistent_keepalive_interval,omitempty"`
|
||||
LastHandshakeTimeNsec uint64 `protobuf:"varint,9,opt,name=last_handshake_time_nsec,json=lastHandshakeTimeNsec,proto3" json:"last_handshake_time_nsec,omitempty"`
|
||||
LastHandshakeTimeSec uint64 `protobuf:"varint,10,opt,name=last_handshake_time_sec,json=lastHandshakeTimeSec,proto3" json:"last_handshake_time_sec,omitempty"`
|
||||
ClientId string `protobuf:"bytes,11,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
|
||||
Endpoint string `protobuf:"bytes,12,opt,name=endpoint,proto3" json:"endpoint,omitempty"`
|
||||
Extra map[string]string `protobuf:"bytes,100,rep,name=extra,proto3" json:"extra,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -796,20 +805,6 @@ func (x *WGPeerRuntimeInfo) GetAllowedIps() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *WGPeerRuntimeInfo) GetEndpointHost() string {
|
||||
if x != nil {
|
||||
return x.EndpointHost
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *WGPeerRuntimeInfo) GetEndpointPort() uint32 {
|
||||
if x != nil {
|
||||
return x.EndpointPort
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *WGPeerRuntimeInfo) GetTxBytes() uint64 {
|
||||
if x != nil {
|
||||
return x.TxBytes
|
||||
@@ -852,6 +847,13 @@ func (x *WGPeerRuntimeInfo) GetClientId() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *WGPeerRuntimeInfo) GetEndpoint() string {
|
||||
if x != nil {
|
||||
return x.Endpoint
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *WGPeerRuntimeInfo) GetExtra() map[string]string {
|
||||
if x != nil {
|
||||
return x.Extra
|
||||
@@ -860,16 +862,20 @@ func (x *WGPeerRuntimeInfo) GetExtra() map[string]string {
|
||||
}
|
||||
|
||||
type WGDeviceRuntimeInfo struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
PrivateKey string `protobuf:"bytes,1,opt,name=private_key,json=privateKey,proto3" json:"private_key,omitempty"`
|
||||
ListenPort uint32 `protobuf:"varint,2,opt,name=listen_port,json=listenPort,proto3" json:"listen_port,omitempty"`
|
||||
Peers []*WGPeerRuntimeInfo `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"`
|
||||
ProtocolVersion uint32 `protobuf:"varint,4,opt,name=protocol_version,json=protocolVersion,proto3" json:"protocol_version,omitempty"`
|
||||
Errno int32 `protobuf:"varint,5,opt,name=errno,proto3" json:"errno,omitempty"`
|
||||
ClientId string `protobuf:"bytes,6,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
|
||||
PingMap map[uint32]uint32 `protobuf:"bytes,7,rep,name=ping_map,json=pingMap,proto3" json:"ping_map,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` // to peer endpoint ping
|
||||
InterfaceName string `protobuf:"bytes,8,opt,name=interface_name,json=interfaceName,proto3" json:"interface_name,omitempty"`
|
||||
Extra map[string]string `protobuf:"bytes,100,rep,name=extra,proto3" json:"extra,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
PrivateKey string `protobuf:"bytes,1,opt,name=private_key,json=privateKey,proto3" json:"private_key,omitempty"`
|
||||
ListenPort uint32 `protobuf:"varint,2,opt,name=listen_port,json=listenPort,proto3" json:"listen_port,omitempty"`
|
||||
Peers []*WGPeerRuntimeInfo `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"`
|
||||
ProtocolVersion uint32 `protobuf:"varint,4,opt,name=protocol_version,json=protocolVersion,proto3" json:"protocol_version,omitempty"`
|
||||
Errno int32 `protobuf:"varint,5,opt,name=errno,proto3" json:"errno,omitempty"`
|
||||
ClientId string `protobuf:"bytes,6,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
|
||||
PingMap map[uint32]uint32 `protobuf:"bytes,7,rep,name=ping_map,json=pingMap,proto3" json:"ping_map,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` // to peer endpoint ping
|
||||
InterfaceName string `protobuf:"bytes,8,opt,name=interface_name,json=interfaceName,proto3" json:"interface_name,omitempty"`
|
||||
VirtAddrPingMap map[string]uint32 `protobuf:"bytes,9,rep,name=virt_addr_ping_map,json=virtAddrPingMap,proto3" json:"virt_addr_ping_map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` // to peer virtual address ping
|
||||
PeerVirtAddrMap map[string]uint32 `protobuf:"bytes,10,rep,name=peer_virt_addr_map,json=peerVirtAddrMap,proto3" json:"peer_virt_addr_map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` // to peer virtual address map
|
||||
PeerConfigMap map[string]*WireGuardPeerConfig `protobuf:"bytes,11,rep,name=peer_config_map,json=peerConfigMap,proto3" json:"peer_config_map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // to peer config map
|
||||
VirtualIp string `protobuf:"bytes,12,opt,name=virtual_ip,json=virtualIp,proto3" json:"virtual_ip,omitempty"` // 节点虚拟 IP
|
||||
Extra map[string]string `protobuf:"bytes,100,rep,name=extra,proto3" json:"extra,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -960,6 +966,34 @@ func (x *WGDeviceRuntimeInfo) GetInterfaceName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *WGDeviceRuntimeInfo) GetVirtAddrPingMap() map[string]uint32 {
|
||||
if x != nil {
|
||||
return x.VirtAddrPingMap
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *WGDeviceRuntimeInfo) GetPeerVirtAddrMap() map[string]uint32 {
|
||||
if x != nil {
|
||||
return x.PeerVirtAddrMap
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *WGDeviceRuntimeInfo) GetPeerConfigMap() map[string]*WireGuardPeerConfig {
|
||||
if x != nil {
|
||||
return x.PeerConfigMap
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *WGDeviceRuntimeInfo) GetVirtualIp() string {
|
||||
if x != nil {
|
||||
return x.VirtualIp
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *WGDeviceRuntimeInfo) GetExtra() map[string]string {
|
||||
if x != nil {
|
||||
return x.Extra
|
||||
@@ -971,7 +1005,7 @@ var File_types_wg_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_types_wg_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x0etypes_wg.proto\x12\twireguard\"\xd5\x02\n" +
|
||||
"\x0etypes_wg.proto\x12\twireguard\"\xf4\x02\n" +
|
||||
"\x13WireGuardPeerConfig\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\rR\x02id\x12\x1b\n" +
|
||||
"\tclient_id\x18\x02 \x01(\tR\bclientId\x12\x17\n" +
|
||||
@@ -985,7 +1019,9 @@ const file_types_wg_proto_rawDesc = "" +
|
||||
"\bendpoint\x18\b \x01(\v2\x13.wireguard.EndpointR\bendpoint\x121\n" +
|
||||
"\x14persistent_keepalive\x18\t \x01(\rR\x13persistentKeepalive\x12\x12\n" +
|
||||
"\x04tags\x18\n" +
|
||||
" \x03(\tR\x04tags\"\xc5\x04\n" +
|
||||
" \x03(\tR\x04tags\x12\x1d\n" +
|
||||
"\n" +
|
||||
"virtual_ip\x18\v \x01(\tR\tvirtualIp\"\xc5\x04\n" +
|
||||
"\x0fWireGuardConfig\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\rR\x02id\x12\x1b\n" +
|
||||
"\tclient_id\x18\x02 \x01(\tR\bclientId\x12\x17\n" +
|
||||
@@ -1041,27 +1077,26 @@ const file_types_wg_proto_rawDesc = "" +
|
||||
"\rAclRuleConfig\x12\x16\n" +
|
||||
"\x06action\x18\x01 \x01(\tR\x06action\x12\x10\n" +
|
||||
"\x03src\x18\x02 \x03(\tR\x03src\x12\x10\n" +
|
||||
"\x03dst\x18\x03 \x03(\tR\x03dst\"\xc2\x04\n" +
|
||||
"\x03dst\x18\x03 \x03(\tR\x03dst\"\x94\x04\n" +
|
||||
"\x11WGPeerRuntimeInfo\x12\x1d\n" +
|
||||
"\n" +
|
||||
"public_key\x18\x01 \x01(\tR\tpublicKey\x12#\n" +
|
||||
"\rpreshared_key\x18\x02 \x01(\tR\fpresharedKey\x12\x1f\n" +
|
||||
"\vallowed_ips\x18\x03 \x03(\tR\n" +
|
||||
"allowedIps\x12#\n" +
|
||||
"\rendpoint_host\x18\x04 \x01(\tR\fendpointHost\x12#\n" +
|
||||
"\rendpoint_port\x18\x05 \x01(\rR\fendpointPort\x12\x19\n" +
|
||||
"allowedIps\x12\x19\n" +
|
||||
"\btx_bytes\x18\x06 \x01(\x04R\atxBytes\x12\x19\n" +
|
||||
"\brx_bytes\x18\a \x01(\x04R\arxBytes\x12B\n" +
|
||||
"\x1dpersistent_keepalive_interval\x18\b \x01(\rR\x1bpersistentKeepaliveInterval\x127\n" +
|
||||
"\x18last_handshake_time_nsec\x18\t \x01(\x04R\x15lastHandshakeTimeNsec\x125\n" +
|
||||
"\x17last_handshake_time_sec\x18\n" +
|
||||
" \x01(\x04R\x14lastHandshakeTimeSec\x12\x1b\n" +
|
||||
"\tclient_id\x18\v \x01(\tR\bclientId\x12=\n" +
|
||||
"\tclient_id\x18\v \x01(\tR\bclientId\x12\x1a\n" +
|
||||
"\bendpoint\x18\f \x01(\tR\bendpoint\x12=\n" +
|
||||
"\x05extra\x18d \x03(\v2'.wireguard.WGPeerRuntimeInfo.ExtraEntryR\x05extra\x1a8\n" +
|
||||
"\n" +
|
||||
"ExtraEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x8f\x04\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xb7\b\n" +
|
||||
"\x13WGDeviceRuntimeInfo\x12\x1f\n" +
|
||||
"\vprivate_key\x18\x01 \x01(\tR\n" +
|
||||
"privateKey\x12\x1f\n" +
|
||||
@@ -1072,11 +1107,26 @@ const file_types_wg_proto_rawDesc = "" +
|
||||
"\x05errno\x18\x05 \x01(\x05R\x05errno\x12\x1b\n" +
|
||||
"\tclient_id\x18\x06 \x01(\tR\bclientId\x12F\n" +
|
||||
"\bping_map\x18\a \x03(\v2+.wireguard.WGDeviceRuntimeInfo.PingMapEntryR\apingMap\x12%\n" +
|
||||
"\x0einterface_name\x18\b \x01(\tR\rinterfaceName\x12?\n" +
|
||||
"\x0einterface_name\x18\b \x01(\tR\rinterfaceName\x12`\n" +
|
||||
"\x12virt_addr_ping_map\x18\t \x03(\v23.wireguard.WGDeviceRuntimeInfo.VirtAddrPingMapEntryR\x0fvirtAddrPingMap\x12`\n" +
|
||||
"\x12peer_virt_addr_map\x18\n" +
|
||||
" \x03(\v23.wireguard.WGDeviceRuntimeInfo.PeerVirtAddrMapEntryR\x0fpeerVirtAddrMap\x12Y\n" +
|
||||
"\x0fpeer_config_map\x18\v \x03(\v21.wireguard.WGDeviceRuntimeInfo.PeerConfigMapEntryR\rpeerConfigMap\x12\x1d\n" +
|
||||
"\n" +
|
||||
"virtual_ip\x18\f \x01(\tR\tvirtualIp\x12?\n" +
|
||||
"\x05extra\x18d \x03(\v2).wireguard.WGDeviceRuntimeInfo.ExtraEntryR\x05extra\x1a:\n" +
|
||||
"\fPingMapEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\rR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\rR\x05value:\x028\x01\x1a8\n" +
|
||||
"\x05value\x18\x02 \x01(\rR\x05value:\x028\x01\x1aB\n" +
|
||||
"\x14VirtAddrPingMapEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\rR\x05value:\x028\x01\x1aB\n" +
|
||||
"\x14PeerVirtAddrMapEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\rR\x05value:\x028\x01\x1a`\n" +
|
||||
"\x12PeerConfigMapEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x124\n" +
|
||||
"\x05value\x18\x02 \x01(\v2\x1e.wireguard.WireGuardPeerConfigR\x05value:\x028\x01\x1a8\n" +
|
||||
"\n" +
|
||||
"ExtraEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
@@ -1094,7 +1144,7 @@ func file_types_wg_proto_rawDescGZIP() []byte {
|
||||
return file_types_wg_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_types_wg_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
|
||||
var file_types_wg_proto_msgTypes = make([]protoimpl.MessageInfo, 16)
|
||||
var file_types_wg_proto_goTypes = []any{
|
||||
(*WireGuardPeerConfig)(nil), // 0: wireguard.WireGuardPeerConfig
|
||||
(*WireGuardConfig)(nil), // 1: wireguard.WireGuardConfig
|
||||
@@ -1108,7 +1158,10 @@ var file_types_wg_proto_goTypes = []any{
|
||||
(*WGDeviceRuntimeInfo)(nil), // 9: wireguard.WGDeviceRuntimeInfo
|
||||
nil, // 10: wireguard.WGPeerRuntimeInfo.ExtraEntry
|
||||
nil, // 11: wireguard.WGDeviceRuntimeInfo.PingMapEntry
|
||||
nil, // 12: wireguard.WGDeviceRuntimeInfo.ExtraEntry
|
||||
nil, // 12: wireguard.WGDeviceRuntimeInfo.VirtAddrPingMapEntry
|
||||
nil, // 13: wireguard.WGDeviceRuntimeInfo.PeerVirtAddrMapEntry
|
||||
nil, // 14: wireguard.WGDeviceRuntimeInfo.PeerConfigMapEntry
|
||||
nil, // 15: wireguard.WGDeviceRuntimeInfo.ExtraEntry
|
||||
}
|
||||
var file_types_wg_proto_depIdxs = []int32{
|
||||
2, // 0: wireguard.WireGuardPeerConfig.endpoint:type_name -> wireguard.Endpoint
|
||||
@@ -1121,12 +1174,16 @@ var file_types_wg_proto_depIdxs = []int32{
|
||||
10, // 7: wireguard.WGPeerRuntimeInfo.extra:type_name -> wireguard.WGPeerRuntimeInfo.ExtraEntry
|
||||
8, // 8: wireguard.WGDeviceRuntimeInfo.peers:type_name -> wireguard.WGPeerRuntimeInfo
|
||||
11, // 9: wireguard.WGDeviceRuntimeInfo.ping_map:type_name -> wireguard.WGDeviceRuntimeInfo.PingMapEntry
|
||||
12, // 10: wireguard.WGDeviceRuntimeInfo.extra:type_name -> wireguard.WGDeviceRuntimeInfo.ExtraEntry
|
||||
11, // [11:11] is the sub-list for method output_type
|
||||
11, // [11:11] is the sub-list for method input_type
|
||||
11, // [11:11] is the sub-list for extension type_name
|
||||
11, // [11:11] is the sub-list for extension extendee
|
||||
0, // [0:11] is the sub-list for field type_name
|
||||
12, // 10: wireguard.WGDeviceRuntimeInfo.virt_addr_ping_map:type_name -> wireguard.WGDeviceRuntimeInfo.VirtAddrPingMapEntry
|
||||
13, // 11: wireguard.WGDeviceRuntimeInfo.peer_virt_addr_map:type_name -> wireguard.WGDeviceRuntimeInfo.PeerVirtAddrMapEntry
|
||||
14, // 12: wireguard.WGDeviceRuntimeInfo.peer_config_map:type_name -> wireguard.WGDeviceRuntimeInfo.PeerConfigMapEntry
|
||||
15, // 13: wireguard.WGDeviceRuntimeInfo.extra:type_name -> wireguard.WGDeviceRuntimeInfo.ExtraEntry
|
||||
0, // 14: wireguard.WGDeviceRuntimeInfo.PeerConfigMapEntry.value:type_name -> wireguard.WireGuardPeerConfig
|
||||
15, // [15:15] is the sub-list for method output_type
|
||||
15, // [15:15] is the sub-list for method input_type
|
||||
15, // [15:15] is the sub-list for extension type_name
|
||||
15, // [15:15] is the sub-list for extension extendee
|
||||
0, // [0:15] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_types_wg_proto_init() }
|
||||
@@ -1140,7 +1197,7 @@ func file_types_wg_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_types_wg_proto_rawDesc), len(file_types_wg_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 13,
|
||||
NumMessages: 16,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
@@ -15,8 +14,6 @@ var (
|
||||
)
|
||||
|
||||
type MultiBind struct {
|
||||
opened atomic.Bool
|
||||
|
||||
transports []*Transport
|
||||
svcLogger *logrus.Entry
|
||||
}
|
||||
@@ -54,8 +51,6 @@ func (m *MultiBind) Close() error {
|
||||
}
|
||||
}
|
||||
|
||||
m.opened.Store(false)
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
@@ -64,14 +59,6 @@ func (m *MultiBind) Close() error {
|
||||
|
||||
// Open implements conn.Bind.
|
||||
func (m *MultiBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) {
|
||||
if m.opened.Load() {
|
||||
m.svcLogger.Debugf("multibind already opened, closing and reopening for port %d", port)
|
||||
if closeErr := m.Close(); closeErr != nil {
|
||||
m.svcLogger.WithError(closeErr).Warnf("failed to close multibind before reopening")
|
||||
}
|
||||
}
|
||||
m.opened.Store(true)
|
||||
|
||||
multiRecvFunc := []conn.ReceiveFunc{}
|
||||
|
||||
for _, t := range m.transports {
|
||||
|
||||
@@ -59,9 +59,7 @@ func ParseWGRunningInfo(raw string) (*pb.WGDeviceRuntimeInfo, error) {
|
||||
}
|
||||
case "endpoint":
|
||||
if cur != nil {
|
||||
host, port := parseEndpoint(v)
|
||||
cur.EndpointHost = host
|
||||
cur.EndpointPort = port
|
||||
cur.Endpoint = v
|
||||
}
|
||||
case "tx_bytes":
|
||||
if cur != nil {
|
||||
@@ -110,34 +108,3 @@ func ParseWGRunningInfo(raw string) (*pb.WGDeviceRuntimeInfo, error) {
|
||||
flushPeer()
|
||||
return dev, nil
|
||||
}
|
||||
|
||||
func parseEndpoint(v string) (string, uint32) {
|
||||
v = strings.TrimSpace(v)
|
||||
if v == "" {
|
||||
return "", 0
|
||||
}
|
||||
// IPv6 带 [] 的形式
|
||||
if strings.HasPrefix(v, "[") {
|
||||
rb := strings.IndexByte(v, ']')
|
||||
if rb > 1 && rb+1 < len(v) && v[rb+1] == ':' {
|
||||
host := v[1:rb]
|
||||
portStr := v[rb+2:]
|
||||
if p, err := strconv.ParseUint(portStr, 10, 32); err == nil {
|
||||
return host, uint32(p)
|
||||
}
|
||||
return host, 0
|
||||
}
|
||||
return v, 0
|
||||
}
|
||||
// IPv4 或域名 host:port
|
||||
idx := strings.LastIndexByte(v, ':')
|
||||
if idx <= 0 || idx+1 >= len(v) {
|
||||
return v, 0
|
||||
}
|
||||
host := v[:idx]
|
||||
portStr := v[idx+1:]
|
||||
if p, err := strconv.ParseUint(portStr, 10, 32); err == nil {
|
||||
return host, uint32(p)
|
||||
}
|
||||
return host, 0
|
||||
}
|
||||
|
||||
@@ -16,12 +16,14 @@ var (
|
||||
type networkTopologyCache struct {
|
||||
wireguardRuntimeInfoMap *utils.SyncMap[uint, *pb.WGDeviceRuntimeInfo] // wireguardId -> peerRuntimeInfo
|
||||
fromToLatencyMap *utils.SyncMap[string, uint32] // fromWGID::toWGID -> latencyMs
|
||||
virtAddrPingMap *utils.SyncMap[string, uint32] // virtAddr -> pingMs
|
||||
}
|
||||
|
||||
func NewNetworkTopologyCache() *networkTopologyCache {
|
||||
return &networkTopologyCache{
|
||||
wireguardRuntimeInfoMap: &utils.SyncMap[uint, *pb.WGDeviceRuntimeInfo]{},
|
||||
fromToLatencyMap: &utils.SyncMap[string, uint32]{},
|
||||
virtAddrPingMap: &utils.SyncMap[string, uint32]{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +36,9 @@ func (c *networkTopologyCache) SetRuntimeInfo(wireguardId uint, runtimeInfo *pb.
|
||||
for toWireGuardId, latencyMs := range runtimeInfo.GetPingMap() {
|
||||
c.fromToLatencyMap.Store(parseFromToLatencyKey(wireguardId, uint(toWireGuardId)), latencyMs)
|
||||
}
|
||||
for virtAddr, pingMs := range runtimeInfo.GetVirtAddrPingMap() {
|
||||
c.virtAddrPingMap.Store(virtAddr, pingMs)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *networkTopologyCache) DeleteRuntimeInfo(wireguardId uint) {
|
||||
|
||||
@@ -48,13 +48,21 @@ func (w *WSBind) BatchSize() int {
|
||||
|
||||
// Close implements conn.Bind.
|
||||
func (w *WSBind) Close() error {
|
||||
w.opened.Store(false)
|
||||
|
||||
for conn := range w.conns {
|
||||
conn.close()
|
||||
}
|
||||
w.conns = make(map[*WSConn]struct{})
|
||||
w.registerChan = make(chan *serverIncoming, defaultRegisterChanSize)
|
||||
w.incomingChan = make(chan *incomingPacket, defaultIncomingChanSize)
|
||||
w.opened.Store(false)
|
||||
|
||||
// 关闭旧的 channels 以释放阻塞的 goroutines
|
||||
if w.registerChan != nil {
|
||||
close(w.registerChan)
|
||||
}
|
||||
if w.incomingChan != nil {
|
||||
close(w.incomingChan)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -66,9 +74,11 @@ func (w *WSBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, e
|
||||
w.ctx.Logger().WithError(closeErr).Warnf("failed to close ws bind before reopening")
|
||||
}
|
||||
}
|
||||
w.opened.Store(true)
|
||||
|
||||
// 重新创建 channels(在 Close() 后它们已经被关闭)
|
||||
w.registerChan = make(chan *serverIncoming, defaultRegisterChanSize)
|
||||
w.incomingChan = make(chan *incomingPacket, defaultIncomingChanSize)
|
||||
w.opened.Store(true)
|
||||
|
||||
go w.serverLoop()
|
||||
return []conn.ReceiveFunc{w.recvFunc}, port, nil
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
@@ -46,8 +47,9 @@ var (
|
||||
type wireGuard struct {
|
||||
sync.RWMutex
|
||||
|
||||
ifce *defs.WireGuardConfig
|
||||
pingMap *utils.SyncMap[uint32, uint32] // ms
|
||||
ifce *defs.WireGuardConfig
|
||||
endpointPingMap *utils.SyncMap[uint32, uint32] // ms
|
||||
virtAddrPingMap *utils.SyncMap[string, uint32] // ms
|
||||
|
||||
wgDevice *device.Device
|
||||
tunDevice tun.Device
|
||||
@@ -81,13 +83,14 @@ func NewWireGuard(ctx *app.Context, ifce defs.WireGuardConfig, logger *logrus.En
|
||||
}
|
||||
|
||||
return &wireGuard{
|
||||
RWMutex: sync.RWMutex{},
|
||||
ifce: &cfg,
|
||||
ctx: svcCtx,
|
||||
cancel: cancel,
|
||||
svcLogger: logger,
|
||||
pingMap: &utils.SyncMap[uint32, uint32]{},
|
||||
useGvisorNet: useGvisorNet,
|
||||
RWMutex: sync.RWMutex{},
|
||||
ifce: &cfg,
|
||||
ctx: svcCtx,
|
||||
cancel: cancel,
|
||||
svcLogger: logger,
|
||||
endpointPingMap: &utils.SyncMap[uint32, uint32]{},
|
||||
useGvisorNet: useGvisorNet,
|
||||
virtAddrPingMap: &utils.SyncMap[string, uint32]{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -325,7 +328,9 @@ func (w *wireGuard) GetWGRuntimeInfo() (*pb.WGDeviceRuntimeInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
runtimeInfo.PingMap = w.pingMap.Export()
|
||||
runtimeInfo.PingMap = w.endpointPingMap.Export()
|
||||
runtimeInfo.VirtAddrPingMap = w.virtAddrPingMap.Export()
|
||||
|
||||
if w.useGvisorNet {
|
||||
runtimeInfo.InterfaceName = w.ifce.GetInterfaceName()
|
||||
} else {
|
||||
@@ -336,6 +341,29 @@ func (w *wireGuard) GetWGRuntimeInfo() (*pb.WGDeviceRuntimeInfo, error) {
|
||||
runtimeInfo.InterfaceName = link.Attrs().Name
|
||||
}
|
||||
|
||||
parsedPeers := w.ifce.GetParsedPeers()
|
||||
parsedPublicKeysPeerMap := make(map[string]*defs.WireGuardPeerConfig)
|
||||
for _, peer := range parsedPeers {
|
||||
parsedPublicKeysPeerMap[peer.HexPublicKey()] = peer
|
||||
}
|
||||
|
||||
runtimeInfo.PeerVirtAddrMap = make(map[string]uint32)
|
||||
for _, peer := range parsedPeers {
|
||||
runtimeInfo.PeerVirtAddrMap[peer.GetVirtualIp()] = peer.GetId()
|
||||
}
|
||||
|
||||
for _, peerRuntimeInfo := range runtimeInfo.GetPeers() {
|
||||
peerConfig, ok := parsedPublicKeysPeerMap[peerRuntimeInfo.PublicKey]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if runtimeInfo.PeerConfigMap == nil {
|
||||
runtimeInfo.PeerConfigMap = make(map[string]*pb.WireGuardPeerConfig)
|
||||
}
|
||||
runtimeInfo.PeerConfigMap[peerRuntimeInfo.PublicKey] = peerConfig.WireGuardPeerConfig
|
||||
peerRuntimeInfo.ClientId = peerConfig.GetClientId()
|
||||
}
|
||||
|
||||
return runtimeInfo, nil
|
||||
}
|
||||
|
||||
@@ -424,14 +452,6 @@ func (w *wireGuard) initWGDevice() error {
|
||||
|
||||
log.Debugf("TUN device '%s' (MTU %d) created successfully", w.ifce.GetInterfaceName(), w.ifce.GetInterfaceMtu())
|
||||
|
||||
// 在创建 WireGuard 设备之前,先打开 bind 并使用正确的端口,避免后续更新端口导致死锁
|
||||
log.Debugf("opening multibind with port %d before creating WireGuard device", w.ifce.GetListenPort())
|
||||
_, _, err = w.multiBind.Open(uint16(w.ifce.GetListenPort()))
|
||||
if err != nil {
|
||||
return errors.Join(fmt.Errorf("open multibind with port %d failed", w.ifce.GetListenPort()), err)
|
||||
}
|
||||
log.Debugf("multibind opened successfully with port %d", w.ifce.GetListenPort())
|
||||
|
||||
log.Debugf("start to create WireGuard device '%s'", w.ifce.GetInterfaceName())
|
||||
|
||||
w.wgDevice = device.NewDevice(w.tunDevice, w.multiBind, &device.Logger{
|
||||
@@ -460,9 +480,7 @@ func (w *wireGuard) applyPeerConfig() error {
|
||||
|
||||
log.Debugf("wgTypedPeerConfigs: %v", wgTypedPeerConfigs)
|
||||
|
||||
// 跳过 listen_port 设置,因为我们已经在 initWGDevice 中通过 bind.Open() 设置了端口
|
||||
// 在运行时通过 UAPI 更新 listen_port 会导致死锁
|
||||
uapiConfigString := generateUAPIConfigString(w.ifce, w.ifce.GetParsedPrivKey(), wgTypedPeerConfigs, !w.running, true)
|
||||
uapiConfigString := generateUAPIConfigString(w.ifce, w.ifce.GetParsedPrivKey(), wgTypedPeerConfigs, !w.running, false)
|
||||
|
||||
log.Debugf("uapiBuilder: %s", uapiConfigString)
|
||||
|
||||
@@ -674,33 +692,77 @@ func (w *wireGuard) pingPeers() {
|
||||
continue
|
||||
}
|
||||
|
||||
pinger, err := probing.NewPinger(addr)
|
||||
epPinger, err := probing.NewPinger(addr)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("failed to create pinger for %s", addr)
|
||||
return
|
||||
continue
|
||||
}
|
||||
|
||||
pinger.Count = 5
|
||||
epPinger.Count = 5
|
||||
|
||||
pinger.OnFinish = func(stats *probing.Statistics) {
|
||||
epPinger.OnFinish = func(stats *probing.Statistics) {
|
||||
// stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss
|
||||
// stats.MinRtt, stats.AvgRtt, stats.MaxRtt, stats.StdDevRtt
|
||||
if w.pingMap != nil {
|
||||
if w.endpointPingMap != nil {
|
||||
log.Tracef("ping stats for %s: %v", addr, stats)
|
||||
avgRttMs := uint32(stats.AvgRtt.Milliseconds())
|
||||
if avgRttMs == 0 { // 0 means bug
|
||||
avgRttMs = 1
|
||||
}
|
||||
w.pingMap.Store(peer.Id, avgRttMs)
|
||||
w.endpointPingMap.Store(peer.Id, avgRttMs)
|
||||
}
|
||||
}
|
||||
|
||||
pinger.OnRecv = func(pkt *probing.Packet) {
|
||||
epPinger.OnRecv = func(pkt *probing.Packet) {
|
||||
log.Tracef("recv from %s", pkt.IPAddr.String())
|
||||
}
|
||||
|
||||
epPinger.OnSendError = func(pkt *probing.Packet, err error) {
|
||||
log.WithError(err).Errorf("failed to send packet to %s", addr)
|
||||
w.endpointPingMap.Store(peer.Id, math.MaxUint32)
|
||||
}
|
||||
|
||||
waitGroup.Go(func() {
|
||||
if err := epPinger.Run(); err != nil {
|
||||
log.WithError(err).Errorf("failed to run pinger for %s", addr)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
for _, peer := range peers {
|
||||
if peer.VirtualIp == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
addr := peer.VirtualIp
|
||||
|
||||
virtAddrPinger, err := probing.NewPinger(addr)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("failed to create pinger for %s", addr)
|
||||
continue
|
||||
}
|
||||
|
||||
virtAddrPinger.Count = 5
|
||||
virtAddrPinger.OnFinish = func(stats *probing.Statistics) {
|
||||
log.Tracef("ping stats for %s: %v", addr, stats)
|
||||
avgRttMs := uint32(stats.AvgRtt.Milliseconds())
|
||||
if avgRttMs == 0 { // 0 means bug
|
||||
avgRttMs = 1
|
||||
}
|
||||
w.virtAddrPingMap.Store(addr, avgRttMs)
|
||||
}
|
||||
virtAddrPinger.OnSendError = func(pkt *probing.Packet, err error) {
|
||||
log.WithError(err).Errorf("failed to send packet to %s", addr)
|
||||
w.virtAddrPingMap.Store(addr, math.MaxUint32)
|
||||
}
|
||||
|
||||
virtAddrPinger.OnRecv = func(pkt *probing.Packet) {
|
||||
log.Tracef("recv from %s", pkt.IPAddr.String())
|
||||
}
|
||||
|
||||
waitGroup.Go(func() {
|
||||
if err := pinger.Run(); err != nil {
|
||||
if err := virtAddrPinger.Run(); err != nil {
|
||||
log.WithError(err).Errorf("failed to run pinger for %s", addr)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ const NetworkDetail: React.FC = () => {
|
||||
queryKey: ['getNetwork', networkId],
|
||||
queryFn: () => getNetwork(GetNetworkRequest.create({ id: networkId! })),
|
||||
enabled: !!networkId,
|
||||
refetchOnWindowFocus: false,
|
||||
})
|
||||
|
||||
const { topology, isFetching: topologyLoading, refetch: refetchTopology } = useNetworkTopology(networkId)
|
||||
|
||||
@@ -5,9 +5,10 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { RefreshCw } from 'lucide-react'
|
||||
import { ArrowUpRight, RefreshCw } from 'lucide-react'
|
||||
import type { WGDeviceRuntimeInfo, WGPeerRuntimeInfo } from '@/lib/pb/types_wg'
|
||||
import { formatBytes } from '@/lib/utils'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
export default function WireGuardRuntimeCard({
|
||||
runtime,
|
||||
@@ -25,7 +26,7 @@ export default function WireGuardRuntimeCard({
|
||||
<Card>
|
||||
<CardHeader className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
||||
<div>
|
||||
<CardTitle>{t('wg.runtime.title')}</CardTitle>
|
||||
<CardTitle>{runtime?.clientId ? runtime.clientId : t('wg.runtime.title')}</CardTitle>
|
||||
<p className="text-sm text-muted-foreground">{t('wg.runtime.subtitle')}</p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={onRefresh} disabled={loading}>
|
||||
@@ -41,8 +42,8 @@ export default function WireGuardRuntimeCard({
|
||||
loading={loading}
|
||||
/>
|
||||
<RuntimeStat
|
||||
title={t('wg.runtime.protocol')}
|
||||
value={runtime?.protocolVersion ?? '-'}
|
||||
title={t('wg.runtime.virt_ip')}
|
||||
value={runtime?.virtualIp ?? '-'}
|
||||
loading={loading}
|
||||
/>
|
||||
<RuntimeStat title={t('wg.runtime.peer_count')} value={peers.length} loading={loading} />
|
||||
@@ -58,7 +59,10 @@ export default function WireGuardRuntimeCard({
|
||||
{loading ? (
|
||||
<Skeleton className="h-12 w-full" />
|
||||
) : peers.length ? (
|
||||
peers.map((peer) => <PeerItem key={peer.publicKey} peer={peer} />)
|
||||
peers.sort((a, b) => a.clientId.localeCompare(b.clientId)).map((peer) => <PeerItem key={peer.publicKey} peer={peer}
|
||||
wireguardId={
|
||||
runtime?.peerConfigMap?.[peer.publicKey]?.id ?? 0
|
||||
} />)
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">{t('wg.runtime.peer_empty')}</p>
|
||||
)}
|
||||
@@ -80,21 +84,37 @@ function RuntimeStat({ title, value, loading }: { title: string; value: React.Re
|
||||
)
|
||||
}
|
||||
|
||||
function PeerItem({ peer }: { peer: WGPeerRuntimeInfo }) {
|
||||
function PeerItem({ peer, wireguardId }: { peer: WGPeerRuntimeInfo; wireguardId: number }) {
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
const lastHandshake = peer.lastHandshakeTimeSec ? new Date(Number(peer.lastHandshakeTimeSec) * 1000) : undefined
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 rounded-md border p-3">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="font-mono text-sm truncate max-w-[240px]" title={peer.publicKey}>
|
||||
{peer.publicKey}
|
||||
{peer.clientId || peer.publicKey || t('wg.runtime.peer_unknown')}
|
||||
</span>
|
||||
<Badge variant="outline">{peer.clientId || t('wg.runtime.peer_unknown')}</Badge>
|
||||
<Badge variant="outline">
|
||||
<p className='font-mono text-xs truncate max-w-[240px] text-nowrap w-fit'>{peer.publicKey || t('wg.runtime.peer_unknown')}</p>
|
||||
</Badge>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6 rounded-full"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
router.push({ pathname: '/wg/wireguard-detail', query: { id: wireguardId } })
|
||||
}}
|
||||
aria-label={t('wg.wireguardActions.view')}
|
||||
>
|
||||
<ArrowUpRight className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid gap-2 text-xs text-muted-foreground sm:grid-cols-2">
|
||||
<div>
|
||||
<span className="font-medium text-foreground">{t('wg.runtime.peer_endpoint')}:</span> {peer.endpointHost}:{peer.endpointPort ?? '-'}
|
||||
<span className="font-medium text-foreground">{t('wg.runtime.peer_endpoint')}:</span> {peer.endpoint ?? '-'}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium text-foreground">{t('wg.runtime.peer_last_handshake')}:</span>{' '}
|
||||
@@ -106,6 +126,18 @@ function PeerItem({ peer }: { peer: WGPeerRuntimeInfo }) {
|
||||
<div>
|
||||
<span className="font-medium text-foreground">RX:</span> {formatBytes(peer.rxBytes ?? 0)}
|
||||
</div>
|
||||
<div className='flex flex-row gap-2'>
|
||||
<span className="font-medium text-foreground">Route</span>
|
||||
<div className='flex flex-wrap items-center gap-2'>
|
||||
{
|
||||
peer.allowedIps.map((ip) => (
|
||||
<Badge key={ip} variant="outline">
|
||||
<span className="font-mono text-xs truncate max-w-[240px] text-nowrap w-fit">{ip}</span>
|
||||
</Badge>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -56,6 +56,10 @@ export interface WireGuardPeerConfig {
|
||||
* @generated from protobuf field: repeated string tags = 10;
|
||||
*/
|
||||
tags: string[]; // 标签
|
||||
/**
|
||||
* @generated from protobuf field: string virtual_ip = 11;
|
||||
*/
|
||||
virtualIp: string; // 节点虚拟 IP
|
||||
}
|
||||
/**
|
||||
* WireGuardConfig wg 配置
|
||||
@@ -279,14 +283,9 @@ export interface WGPeerRuntimeInfo {
|
||||
*/
|
||||
allowedIps: string[];
|
||||
/**
|
||||
* @generated from protobuf field: string endpoint_host = 4;
|
||||
*/
|
||||
endpointHost: string;
|
||||
/**
|
||||
* @generated from protobuf field: uint32 endpoint_port = 5;
|
||||
*/
|
||||
endpointPort: number;
|
||||
/**
|
||||
* string endpoint_host = 4; // 不再使用
|
||||
* uint32 endpoint_port = 5; // 不再使用
|
||||
*
|
||||
* @generated from protobuf field: uint64 tx_bytes = 6;
|
||||
*/
|
||||
txBytes: bigint;
|
||||
@@ -310,6 +309,10 @@ export interface WGPeerRuntimeInfo {
|
||||
* @generated from protobuf field: string client_id = 11;
|
||||
*/
|
||||
clientId: string;
|
||||
/**
|
||||
* @generated from protobuf field: string endpoint = 12;
|
||||
*/
|
||||
endpoint: string;
|
||||
/**
|
||||
* @generated from protobuf field: map<string, string> extra = 100;
|
||||
*/
|
||||
@@ -355,6 +358,28 @@ export interface WGDeviceRuntimeInfo {
|
||||
* @generated from protobuf field: string interface_name = 8;
|
||||
*/
|
||||
interfaceName: string;
|
||||
/**
|
||||
* @generated from protobuf field: map<string, uint32> virt_addr_ping_map = 9;
|
||||
*/
|
||||
virtAddrPingMap: {
|
||||
[key: string]: number;
|
||||
}; // to peer virtual address ping
|
||||
/**
|
||||
* @generated from protobuf field: map<string, uint32> peer_virt_addr_map = 10;
|
||||
*/
|
||||
peerVirtAddrMap: {
|
||||
[key: string]: number;
|
||||
}; // to peer virtual address map
|
||||
/**
|
||||
* @generated from protobuf field: map<string, wireguard.WireGuardPeerConfig> peer_config_map = 11;
|
||||
*/
|
||||
peerConfigMap: {
|
||||
[key: string]: WireGuardPeerConfig;
|
||||
}; // to peer config map
|
||||
/**
|
||||
* @generated from protobuf field: string virtual_ip = 12;
|
||||
*/
|
||||
virtualIp: string; // 节点虚拟 IP
|
||||
/**
|
||||
* @generated from protobuf field: map<string, string> extra = 100;
|
||||
*/
|
||||
@@ -375,7 +400,8 @@ class WireGuardPeerConfig$Type extends MessageType<WireGuardPeerConfig> {
|
||||
{ no: 7, name: "allowed_ips", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ },
|
||||
{ no: 8, name: "endpoint", kind: "message", T: () => Endpoint },
|
||||
{ no: 9, name: "persistent_keepalive", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
|
||||
{ no: 10, name: "tags", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ }
|
||||
{ no: 10, name: "tags", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ },
|
||||
{ no: 11, name: "virtual_ip", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<WireGuardPeerConfig>): WireGuardPeerConfig {
|
||||
@@ -389,6 +415,7 @@ class WireGuardPeerConfig$Type extends MessageType<WireGuardPeerConfig> {
|
||||
message.allowedIps = [];
|
||||
message.persistentKeepalive = 0;
|
||||
message.tags = [];
|
||||
message.virtualIp = "";
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<WireGuardPeerConfig>(this, message, value);
|
||||
return message;
|
||||
@@ -428,6 +455,9 @@ class WireGuardPeerConfig$Type extends MessageType<WireGuardPeerConfig> {
|
||||
case /* repeated string tags */ 10:
|
||||
message.tags.push(reader.string());
|
||||
break;
|
||||
case /* string virtual_ip */ 11:
|
||||
message.virtualIp = reader.string();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
@@ -470,6 +500,9 @@ class WireGuardPeerConfig$Type extends MessageType<WireGuardPeerConfig> {
|
||||
/* repeated string tags = 10; */
|
||||
for (let i = 0; i < message.tags.length; i++)
|
||||
writer.tag(10, WireType.LengthDelimited).string(message.tags[i]);
|
||||
/* string virtual_ip = 11; */
|
||||
if (message.virtualIp !== "")
|
||||
writer.tag(11, WireType.LengthDelimited).string(message.virtualIp);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
@@ -1094,14 +1127,13 @@ class WGPeerRuntimeInfo$Type extends MessageType<WGPeerRuntimeInfo> {
|
||||
{ no: 1, name: "public_key", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||
{ no: 2, name: "preshared_key", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||
{ no: 3, name: "allowed_ips", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ },
|
||||
{ no: 4, name: "endpoint_host", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||
{ no: 5, name: "endpoint_port", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
|
||||
{ no: 6, name: "tx_bytes", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ },
|
||||
{ no: 7, name: "rx_bytes", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ },
|
||||
{ no: 8, name: "persistent_keepalive_interval", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
|
||||
{ no: 9, name: "last_handshake_time_nsec", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ },
|
||||
{ no: 10, name: "last_handshake_time_sec", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ },
|
||||
{ no: 11, name: "client_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||
{ no: 12, name: "endpoint", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||
{ no: 100, name: "extra", kind: "map", K: 9 /*ScalarType.STRING*/, V: { kind: "scalar", T: 9 /*ScalarType.STRING*/ } }
|
||||
]);
|
||||
}
|
||||
@@ -1110,14 +1142,13 @@ class WGPeerRuntimeInfo$Type extends MessageType<WGPeerRuntimeInfo> {
|
||||
message.publicKey = "";
|
||||
message.presharedKey = "";
|
||||
message.allowedIps = [];
|
||||
message.endpointHost = "";
|
||||
message.endpointPort = 0;
|
||||
message.txBytes = 0n;
|
||||
message.rxBytes = 0n;
|
||||
message.persistentKeepaliveInterval = 0;
|
||||
message.lastHandshakeTimeNsec = 0n;
|
||||
message.lastHandshakeTimeSec = 0n;
|
||||
message.clientId = "";
|
||||
message.endpoint = "";
|
||||
message.extra = {};
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<WGPeerRuntimeInfo>(this, message, value);
|
||||
@@ -1137,12 +1168,6 @@ class WGPeerRuntimeInfo$Type extends MessageType<WGPeerRuntimeInfo> {
|
||||
case /* repeated string allowed_ips */ 3:
|
||||
message.allowedIps.push(reader.string());
|
||||
break;
|
||||
case /* string endpoint_host */ 4:
|
||||
message.endpointHost = reader.string();
|
||||
break;
|
||||
case /* uint32 endpoint_port */ 5:
|
||||
message.endpointPort = reader.uint32();
|
||||
break;
|
||||
case /* uint64 tx_bytes */ 6:
|
||||
message.txBytes = reader.uint64().toBigInt();
|
||||
break;
|
||||
@@ -1161,6 +1186,9 @@ class WGPeerRuntimeInfo$Type extends MessageType<WGPeerRuntimeInfo> {
|
||||
case /* string client_id */ 11:
|
||||
message.clientId = reader.string();
|
||||
break;
|
||||
case /* string endpoint */ 12:
|
||||
message.endpoint = reader.string();
|
||||
break;
|
||||
case /* map<string, string> extra */ 100:
|
||||
this.binaryReadMap100(message.extra, reader, options);
|
||||
break;
|
||||
@@ -1201,12 +1229,6 @@ class WGPeerRuntimeInfo$Type extends MessageType<WGPeerRuntimeInfo> {
|
||||
/* repeated string allowed_ips = 3; */
|
||||
for (let i = 0; i < message.allowedIps.length; i++)
|
||||
writer.tag(3, WireType.LengthDelimited).string(message.allowedIps[i]);
|
||||
/* string endpoint_host = 4; */
|
||||
if (message.endpointHost !== "")
|
||||
writer.tag(4, WireType.LengthDelimited).string(message.endpointHost);
|
||||
/* uint32 endpoint_port = 5; */
|
||||
if (message.endpointPort !== 0)
|
||||
writer.tag(5, WireType.Varint).uint32(message.endpointPort);
|
||||
/* uint64 tx_bytes = 6; */
|
||||
if (message.txBytes !== 0n)
|
||||
writer.tag(6, WireType.Varint).uint64(message.txBytes);
|
||||
@@ -1225,6 +1247,9 @@ class WGPeerRuntimeInfo$Type extends MessageType<WGPeerRuntimeInfo> {
|
||||
/* string client_id = 11; */
|
||||
if (message.clientId !== "")
|
||||
writer.tag(11, WireType.LengthDelimited).string(message.clientId);
|
||||
/* string endpoint = 12; */
|
||||
if (message.endpoint !== "")
|
||||
writer.tag(12, WireType.LengthDelimited).string(message.endpoint);
|
||||
/* map<string, string> extra = 100; */
|
||||
for (let k of globalThis.Object.keys(message.extra))
|
||||
writer.tag(100, WireType.LengthDelimited).fork().tag(1, WireType.LengthDelimited).string(k).tag(2, WireType.LengthDelimited).string(message.extra[k]).join();
|
||||
@@ -1250,6 +1275,10 @@ class WGDeviceRuntimeInfo$Type extends MessageType<WGDeviceRuntimeInfo> {
|
||||
{ no: 6, name: "client_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||
{ no: 7, name: "ping_map", kind: "map", K: 13 /*ScalarType.UINT32*/, V: { kind: "scalar", T: 13 /*ScalarType.UINT32*/ } },
|
||||
{ no: 8, name: "interface_name", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||
{ no: 9, name: "virt_addr_ping_map", kind: "map", K: 9 /*ScalarType.STRING*/, V: { kind: "scalar", T: 13 /*ScalarType.UINT32*/ } },
|
||||
{ no: 10, name: "peer_virt_addr_map", kind: "map", K: 9 /*ScalarType.STRING*/, V: { kind: "scalar", T: 13 /*ScalarType.UINT32*/ } },
|
||||
{ no: 11, name: "peer_config_map", kind: "map", K: 9 /*ScalarType.STRING*/, V: { kind: "message", T: () => WireGuardPeerConfig } },
|
||||
{ no: 12, name: "virtual_ip", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
||||
{ no: 100, name: "extra", kind: "map", K: 9 /*ScalarType.STRING*/, V: { kind: "scalar", T: 9 /*ScalarType.STRING*/ } }
|
||||
]);
|
||||
}
|
||||
@@ -1263,6 +1292,10 @@ class WGDeviceRuntimeInfo$Type extends MessageType<WGDeviceRuntimeInfo> {
|
||||
message.clientId = "";
|
||||
message.pingMap = {};
|
||||
message.interfaceName = "";
|
||||
message.virtAddrPingMap = {};
|
||||
message.peerVirtAddrMap = {};
|
||||
message.peerConfigMap = {};
|
||||
message.virtualIp = "";
|
||||
message.extra = {};
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<WGDeviceRuntimeInfo>(this, message, value);
|
||||
@@ -1297,6 +1330,18 @@ class WGDeviceRuntimeInfo$Type extends MessageType<WGDeviceRuntimeInfo> {
|
||||
case /* string interface_name */ 8:
|
||||
message.interfaceName = reader.string();
|
||||
break;
|
||||
case /* map<string, uint32> virt_addr_ping_map */ 9:
|
||||
this.binaryReadMap9(message.virtAddrPingMap, reader, options);
|
||||
break;
|
||||
case /* map<string, uint32> peer_virt_addr_map */ 10:
|
||||
this.binaryReadMap10(message.peerVirtAddrMap, reader, options);
|
||||
break;
|
||||
case /* map<string, wireguard.WireGuardPeerConfig> peer_config_map */ 11:
|
||||
this.binaryReadMap11(message.peerConfigMap, reader, options);
|
||||
break;
|
||||
case /* string virtual_ip */ 12:
|
||||
message.virtualIp = reader.string();
|
||||
break;
|
||||
case /* map<string, string> extra */ 100:
|
||||
this.binaryReadMap100(message.extra, reader, options);
|
||||
break;
|
||||
@@ -1327,6 +1372,54 @@ class WGDeviceRuntimeInfo$Type extends MessageType<WGDeviceRuntimeInfo> {
|
||||
}
|
||||
map[key ?? 0] = val ?? 0;
|
||||
}
|
||||
private binaryReadMap9(map: WGDeviceRuntimeInfo["virtAddrPingMap"], reader: IBinaryReader, options: BinaryReadOptions): void {
|
||||
let len = reader.uint32(), end = reader.pos + len, key: keyof WGDeviceRuntimeInfo["virtAddrPingMap"] | undefined, val: WGDeviceRuntimeInfo["virtAddrPingMap"][any] | undefined;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case 1:
|
||||
key = reader.string();
|
||||
break;
|
||||
case 2:
|
||||
val = reader.uint32();
|
||||
break;
|
||||
default: throw new globalThis.Error("unknown map entry field for field wireguard.WGDeviceRuntimeInfo.virt_addr_ping_map");
|
||||
}
|
||||
}
|
||||
map[key ?? ""] = val ?? 0;
|
||||
}
|
||||
private binaryReadMap10(map: WGDeviceRuntimeInfo["peerVirtAddrMap"], reader: IBinaryReader, options: BinaryReadOptions): void {
|
||||
let len = reader.uint32(), end = reader.pos + len, key: keyof WGDeviceRuntimeInfo["peerVirtAddrMap"] | undefined, val: WGDeviceRuntimeInfo["peerVirtAddrMap"][any] | undefined;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case 1:
|
||||
key = reader.string();
|
||||
break;
|
||||
case 2:
|
||||
val = reader.uint32();
|
||||
break;
|
||||
default: throw new globalThis.Error("unknown map entry field for field wireguard.WGDeviceRuntimeInfo.peer_virt_addr_map");
|
||||
}
|
||||
}
|
||||
map[key ?? ""] = val ?? 0;
|
||||
}
|
||||
private binaryReadMap11(map: WGDeviceRuntimeInfo["peerConfigMap"], reader: IBinaryReader, options: BinaryReadOptions): void {
|
||||
let len = reader.uint32(), end = reader.pos + len, key: keyof WGDeviceRuntimeInfo["peerConfigMap"] | undefined, val: WGDeviceRuntimeInfo["peerConfigMap"][any] | undefined;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case 1:
|
||||
key = reader.string();
|
||||
break;
|
||||
case 2:
|
||||
val = WireGuardPeerConfig.internalBinaryRead(reader, reader.uint32(), options);
|
||||
break;
|
||||
default: throw new globalThis.Error("unknown map entry field for field wireguard.WGDeviceRuntimeInfo.peer_config_map");
|
||||
}
|
||||
}
|
||||
map[key ?? ""] = val ?? WireGuardPeerConfig.create();
|
||||
}
|
||||
private binaryReadMap100(map: WGDeviceRuntimeInfo["extra"], reader: IBinaryReader, options: BinaryReadOptions): void {
|
||||
let len = reader.uint32(), end = reader.pos + len, key: keyof WGDeviceRuntimeInfo["extra"] | undefined, val: WGDeviceRuntimeInfo["extra"][any] | undefined;
|
||||
while (reader.pos < end) {
|
||||
@@ -1368,6 +1461,22 @@ class WGDeviceRuntimeInfo$Type extends MessageType<WGDeviceRuntimeInfo> {
|
||||
/* string interface_name = 8; */
|
||||
if (message.interfaceName !== "")
|
||||
writer.tag(8, WireType.LengthDelimited).string(message.interfaceName);
|
||||
/* map<string, uint32> virt_addr_ping_map = 9; */
|
||||
for (let k of globalThis.Object.keys(message.virtAddrPingMap))
|
||||
writer.tag(9, WireType.LengthDelimited).fork().tag(1, WireType.LengthDelimited).string(k).tag(2, WireType.Varint).uint32(message.virtAddrPingMap[k]).join();
|
||||
/* map<string, uint32> peer_virt_addr_map = 10; */
|
||||
for (let k of globalThis.Object.keys(message.peerVirtAddrMap))
|
||||
writer.tag(10, WireType.LengthDelimited).fork().tag(1, WireType.LengthDelimited).string(k).tag(2, WireType.Varint).uint32(message.peerVirtAddrMap[k]).join();
|
||||
/* map<string, wireguard.WireGuardPeerConfig> peer_config_map = 11; */
|
||||
for (let k of globalThis.Object.keys(message.peerConfigMap)) {
|
||||
writer.tag(11, WireType.LengthDelimited).fork().tag(1, WireType.LengthDelimited).string(k);
|
||||
writer.tag(2, WireType.LengthDelimited).fork();
|
||||
WireGuardPeerConfig.internalBinaryWrite(message.peerConfigMap[k], writer, options);
|
||||
writer.join().join();
|
||||
}
|
||||
/* string virtual_ip = 12; */
|
||||
if (message.virtualIp !== "")
|
||||
writer.tag(12, WireType.LengthDelimited).string(message.virtualIp);
|
||||
/* map<string, string> extra = 100; */
|
||||
for (let k of globalThis.Object.keys(message.extra))
|
||||
writer.tag(100, WireType.LengthDelimited).fork().tag(1, WireType.LengthDelimited).string(k).tag(2, WireType.LengthDelimited).string(message.extra[k]).join();
|
||||
|
||||
Reference in New Issue
Block a user