Merge branch 'main' into embedded-vnc

This commit is contained in:
Viktor Liu
2026-08-27 16:01:14 +02:00
482 changed files with 34604 additions and 6503 deletions

View File

@@ -0,0 +1,18 @@
package nmdata
import "time"
// AccountSettingsInfo is the slim twin of types.AccountSettingsInfo.
type AccountSettingsInfo struct {
PeerLoginExpirationEnabled bool
PeerLoginExpiration time.Duration
PeerInactivityExpirationEnabled bool
PeerInactivityExpiration time.Duration
DNSDomain string
IPv6EnabledGroups []string
RoutingPeerDNSResolutionEnabled bool
LazyConnectionEnabled bool
AutoUpdateVersion string
AutoUpdateAlways bool
MetricsPushEnabled bool
}

View File

@@ -0,0 +1,18 @@
package nmdata
// SimpleRecord is the slim twin of dns.SimpleRecord.
type SimpleRecord struct {
Name string
Type int
Class string
TTL int
RData string
}
// CustomZone is the slim twin of dns.CustomZone.
type CustomZone struct {
Domain string
Records []SimpleRecord
SearchDomainDisabled bool
NonAuthoritative bool
}

View File

@@ -0,0 +1,6 @@
package nmdata
// DNSSettings is the slim twin of types.DNSSettings.
type DNSSettings struct {
DisabledManagementGroups []string
}

View File

@@ -0,0 +1,30 @@
package nmdata
import "slices"
// GroupAllName is the reserved name of the default group that contains every
// peer in an account.
const GroupAllName = "All"
// Group is the slim twin of types.Group.
type Group struct {
ID string
Name string
PublicID string
Peers []string
Resources []Resource
}
func (g *Group) IsGroupAll() bool {
return g.Name == GroupAllName
}
func (g *Group) Copy() *Group {
return &Group{
ID: g.ID,
Name: g.Name,
PublicID: g.PublicID,
Peers: slices.Clone(g.Peers),
Resources: slices.Clone(g.Resources),
}
}

View File

@@ -0,0 +1,84 @@
package nmdata
import (
"reflect"
"testing"
)
// TestGroupCopy_AllFieldsCopied fills every Group field with a unique non-zero
// value derived from its field path, so a field added to Group but forgotten
// in Copy fails here by name without the test needing an update. The unique
// per-path values also catch fields swapped inside Copy.
func TestGroupCopy_AllFieldsCopied(t *testing.T) {
src := &Group{}
seed := 0
fillValue(t, reflect.ValueOf(src).Elem(), "Group", &seed)
copied := src.Copy()
srcV := reflect.ValueOf(src).Elem()
copiedV := reflect.ValueOf(copied).Elem()
for i := 0; i < srcV.NumField(); i++ {
name := srcV.Type().Field(i).Name
if !reflect.DeepEqual(srcV.Field(i).Interface(), copiedV.Field(i).Interface()) {
t.Errorf("field %s not copied: src=%#v copy=%#v",
name, srcV.Field(i).Interface(), copiedV.Field(i).Interface())
}
}
for i := 0; i < srcV.NumField(); i++ {
f := srcV.Field(i)
if f.Kind() != reflect.Slice || f.Len() == 0 {
continue
}
name := srcV.Type().Field(i).Name
fillValue(t, f.Index(0), name+"-mutated", &seed)
if reflect.DeepEqual(f.Interface(), copiedV.Field(i).Interface()) {
t.Errorf("field %s shares memory with the copy", name)
}
}
}
// fillValue sets v to a deterministic non-zero value derived from its field
// path. Kinds it does not handle fail the test loudly, so the filler is
// extended together with the struct instead of silently under-testing new
// fields.
func fillValue(t *testing.T, v reflect.Value, path string, seed *int) {
t.Helper()
switch v.Kind() {
case reflect.String:
v.SetString(path)
case reflect.Bool:
v.SetBool(true)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
*seed++
v.SetInt(int64(*seed))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
*seed++
v.SetUint(uint64(*seed))
case reflect.Float32, reflect.Float64:
*seed++
v.SetFloat(float64(*seed))
case reflect.Slice:
s := reflect.MakeSlice(v.Type(), 2, 2)
fillValue(t, s.Index(0), path+"[0]", seed)
fillValue(t, s.Index(1), path+"[1]", seed)
v.Set(s)
case reflect.Struct:
settable := 0
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
if !f.CanSet() {
continue
}
settable++
fillValue(t, f, path+"."+v.Type().Field(i).Name, seed)
}
if settable == 0 {
t.Fatalf("struct %s at %s has no settable fields — extend fillValue to construct it", v.Type(), path)
}
default:
t.Fatalf("unsupported kind %s at %s — extend fillValue", v.Kind(), path)
}
}

View File

@@ -0,0 +1,24 @@
package nmdata
import "net/netip"
// NameServerGroup is the slim twin of dns.NameServerGroup.
type NameServerGroup struct {
ID string
PublicID string
Name string
Description string
NameServers []NameServer
Groups []string
Primary bool
Domains []string
Enabled bool
SearchDomainsEnabled bool
}
// NameServer is the slim twin of dns.NameServer.
type NameServer struct {
IP netip.Addr
NSType int
Port int
}

View File

@@ -0,0 +1,16 @@
package nmdata
import "net"
// Network is the slim twin of types.Network.
type Network struct {
Identifier string
Net net.IPNet
NetV6 net.IPNet
Dns string
Serial int64
}
func (n *Network) CurrentSerial() uint64 {
return uint64(n.Serial)
}

View File

@@ -0,0 +1,18 @@
package nmdata
import "net/netip"
// NetworkResource is the slim twin of resources/types.NetworkResource.
type NetworkResource struct {
ID string
NetworkID string
AccountID string
PublicID string
Name string
Description string
Type string
Address string // TODO: isn't persisted in the DB
Domain string
Prefix netip.Prefix
Enabled bool
}

View File

@@ -0,0 +1,10 @@
package nmdata
// NetworkRouter is the slim twin of routers/types.NetworkRouter.
type NetworkRouter struct {
PublicID string
PeerGroups []string
Masquerade bool
Metric int
Enabled bool
}

View File

@@ -0,0 +1,129 @@
package nmdata
import (
"net"
"net/netip"
"slices"
"time"
)
// Peer capability constants mirror the proto enum values.
const (
PeerCapabilitySourcePrefixes int32 = 1
PeerCapabilityIPv6Overlay int32 = 2
PeerCapabilityComponentNetworkMap int32 = 3
)
// Peer is the slim twin of peer.Peer.
type Peer struct {
ID string
Key string
SSHKey string
DNSLabel string
UserID string
SSHEnabled bool
LoginExpirationEnabled bool
LastLogin *time.Time
IP netip.Addr
IPv6 netip.Addr
RequiresApproval bool
ExtraDNSLabels []string
Meta PeerSystemMeta
ProxyMeta ProxyMeta
Location PeerLocation
}
// ProxyMeta is the slim twin of peer.ProxyMeta.
type ProxyMeta struct {
Embedded bool
Cluster string
}
// PeerSystemMeta is the slim twin of peer.PeerSystemMeta.
type PeerSystemMeta struct {
WtVersion string
GoOS string
OSVersion string
KernelVersion string
NetworkAddresses []NetworkAddress
Files []File
Capabilities []int32
Flags Flags
SyncMessageVersion int
}
// Flags is the slim twin of peer.Flags.
type Flags struct {
ServerSSHAllowed bool
DisableIPv6 bool
}
// NetworkAddress is the slim twin of peer.NetworkAddress.
type NetworkAddress struct {
NetIP netip.Prefix
}
// File is the slim twin of peer.File.
type File struct {
Path string
ProcessIsRunning bool
}
// PeerLocation is the slim twin of peer.Location.
type PeerLocation struct {
CountryCode string
CityName string
ConnectionIP net.IP
}
func (p *Peer) HasCapability(capability int32) bool {
return slices.Contains(p.Meta.Capabilities, capability)
}
func (p *Peer) SupportsIPv6() bool {
return !p.Meta.Flags.DisableIPv6 && p.HasCapability(PeerCapabilityIPv6Overlay)
}
func (p *Peer) SupportsSourcePrefixes() bool {
return p.HasCapability(PeerCapabilitySourcePrefixes)
}
func (p *Peer) AddedWithSSOLogin() bool {
return p.UserID != ""
}
func (p *Peer) FQDN(dnsDomain string) string {
if dnsDomain == "" {
return ""
}
return p.DNSLabel + "." + dnsDomain
}
func (p *Peer) GetLastLogin() time.Time {
if p.LastLogin != nil {
return *p.LastLogin
}
return time.Time{}
}
// SessionExpiresAt mirrors peer.Peer.SessionExpiresAt.
func (p *Peer) SessionExpiresAt(accountExpirationEnabled bool, expiresIn time.Duration) time.Time {
if !accountExpirationEnabled || !p.AddedWithSSOLogin() || !p.LoginExpirationEnabled {
return time.Time{}
}
last := p.GetLastLogin()
if last.IsZero() {
return time.Time{}
}
return last.Add(expiresIn).UTC()
}
func (p *Peer) LoginExpired(expiresIn time.Duration) (bool, time.Duration) {
if !p.AddedWithSSOLogin() || !p.LoginExpirationEnabled {
return false, 0
}
expiresAt := p.GetLastLogin().Add(expiresIn)
now := time.Now()
timeLeft := expiresAt.Sub(now)
return timeLeft <= 0, timeLeft
}

View File

@@ -0,0 +1,98 @@
package nmdata
const (
policyRuleProtocolALL = "all"
policyRuleProtocolTCP = "tcp"
defaultSSHPortString = "22"
nativeSSHPortString = "22022"
defaultSSHPortNumber uint16 = 22
nativeSSHPortNumber uint16 = 22022
)
// Policy is the slim twin of types.Policy.
type Policy struct {
ID string
PublicID string
Enabled bool
SourcePostureChecks []string
Rules []*PolicyRule
}
// PolicyRule is the slim twin of types.PolicyRule.
type PolicyRule struct {
ID string
PolicyID string
Enabled bool
Action string
Protocol string
Bidirectional bool
Sources []string
Destinations []string
SourceResource Resource
DestinationResource Resource
Ports []string
PortRanges []RulePortRange
AuthorizedGroups map[string][]string
AuthorizedUser string
SessionPubKey string
SessionDisplayName string
}
// RulePortRange is the slim twin of types.RulePortRange.
type RulePortRange struct {
Start uint16
End uint16
}
// Resource is the slim twin of types.Resource.
type Resource struct {
ID string
Type string
}
func (p *Policy) SourceGroups() []string {
if len(p.Rules) == 1 && p.Rules[0] != nil {
return p.Rules[0].Sources
}
groups := make(map[string]struct{}, len(p.Rules))
for _, rule := range p.Rules {
if rule == nil {
continue
}
for _, source := range rule.Sources {
groups[source] = struct{}{}
}
}
groupIDs := make([]string, 0, len(groups))
for groupID := range groups {
groupIDs = append(groupIDs, groupID)
}
return groupIDs
}
// PolicyRuleImpliesLegacySSH is the twin-typed sibling of types.PolicyRuleImpliesLegacySSH.
func PolicyRuleImpliesLegacySSH(rule *PolicyRule) bool {
return rule.Protocol == policyRuleProtocolALL ||
(rule.Protocol == policyRuleProtocolTCP && (portsIncludesSSH(rule.Ports) || portRangeIncludesSSH(rule.PortRanges)))
}
func portRangeIncludesSSH(portRanges []RulePortRange) bool {
for _, pr := range portRanges {
if (pr.Start <= defaultSSHPortNumber && pr.End >= defaultSSHPortNumber) || (pr.Start <= nativeSSHPortNumber && pr.End >= nativeSSHPortNumber) {
return true
}
}
return false
}
func portsIncludesSSH(ports []string) bool {
for _, port := range ports {
if port == defaultSSHPortString || port == nativeSSHPortString {
return true
}
}
return false
}

View File

@@ -0,0 +1,67 @@
package nmdata
const (
checkActionAllow = "allow"
checkActionDeny = "deny"
)
// PostureChecks is the slim twin of posture.Checks.
type PostureChecks struct {
ID string
Checks ChecksDefinition
}
// ChecksDefinition is the slim twin of posture.ChecksDefinition.
type ChecksDefinition struct {
NBVersionCheck *NBVersionCheck
OSVersionCheck *OSVersionCheck
GeoLocationCheck *GeoLocationCheck
PeerNetworkRangeCheck *PeerNetworkRangeCheck
ProcessCheck *ProcessCheck
}
// Check is the slim twin of posture.Check. It is sealed: only the check types
// in this package implement it.
type Check interface {
check(peer *Peer) (bool, error)
}
// Passes reports whether the peer satisfies every check in this bundle. It
// mirrors the server posture path: a check returning (false, _) — including on
// an evaluation error — fails the bundle.
func (pc *PostureChecks) Passes(peer *Peer) bool {
return PassesChecks(pc.GetChecks(), peer)
}
// PassesChecks is Passes over an already built check set, for callers that
// evaluate many peers against the same bundle.
func PassesChecks(checks []Check, peer *Peer) bool {
for _, c := range checks {
valid, _ := c.check(peer)
if !valid {
return false
}
}
return true
}
// GetChecks returns the initialized checks in the same order as posture.Checks.GetChecks.
func (pc *PostureChecks) GetChecks() []Check {
var checks []Check
if pc.Checks.NBVersionCheck != nil {
checks = append(checks, pc.Checks.NBVersionCheck)
}
if pc.Checks.OSVersionCheck != nil {
checks = append(checks, pc.Checks.OSVersionCheck)
}
if pc.Checks.GeoLocationCheck != nil {
checks = append(checks, pc.Checks.GeoLocationCheck)
}
if pc.Checks.PeerNetworkRangeCheck != nil {
checks = append(checks, pc.Checks.PeerNetworkRangeCheck)
}
if pc.Checks.ProcessCheck != nil {
checks = append(checks, pc.Checks.ProcessCheck)
}
return checks
}

View File

@@ -0,0 +1,45 @@
package nmdata
import "fmt"
// GeoLocation is the slim twin of posture.Location.
type GeoLocation struct {
CountryCode string
CityName string
}
// GeoLocationCheck is the slim twin of posture.GeoLocationCheck.
type GeoLocationCheck struct {
Locations []GeoLocation
Action string
}
func (g *GeoLocationCheck) check(peer *Peer) (bool, error) {
if peer.Location.CountryCode == "" && peer.Location.CityName == "" {
return false, fmt.Errorf("peer's location is not set")
}
for _, loc := range g.Locations {
if loc.CountryCode == peer.Location.CountryCode {
if loc.CityName == "" || loc.CityName == peer.Location.CityName {
switch g.Action {
case checkActionDeny:
return false, nil
case checkActionAllow:
return true, nil
default:
return false, fmt.Errorf("invalid geo location action: %s", g.Action)
}
}
}
}
if g.Action == checkActionDeny {
return true, nil
}
if g.Action == checkActionAllow {
return false, nil
}
return false, fmt.Errorf("invalid geo location action: %s", g.Action)
}

View File

@@ -0,0 +1,38 @@
package nmdata
import (
"strings"
"github.com/hashicorp/go-version"
)
// NBVersionCheck is the slim twin of posture.NBVersionCheck.
type NBVersionCheck struct {
MinVersion string
}
func (n *NBVersionCheck) check(peer *Peer) (bool, error) {
return meetsMinVersion(n.MinVersion, peer.Meta.WtVersion)
}
func meetsMinVersion(minVer, peerVer string) (bool, error) {
peerVer = sanitizeVersion(peerVer)
minVer = sanitizeVersion(minVer)
peerNBVer, err := version.NewVersion(peerVer)
if err != nil {
return false, err
}
constraints, err := version.NewConstraint(">= " + minVer)
if err != nil {
return false, err
}
return constraints.Check(peerNBVer), nil
}
func sanitizeVersion(v string) string {
parts := strings.Split(v, "-")
return parts[0]
}

View File

@@ -0,0 +1,62 @@
package nmdata
import (
"fmt"
"net/netip"
)
// PeerNetworkRangeCheck is the slim twin of posture.PeerNetworkRangeCheck.
type PeerNetworkRangeCheck struct {
Action string
Ranges []netip.Prefix
}
func (p *PeerNetworkRangeCheck) check(peer *Peer) (bool, error) {
peerPrefixes := make([]netip.Prefix, 0, len(peer.Meta.NetworkAddresses)+1)
for _, peerNetAddr := range peer.Meta.NetworkAddresses {
peerPrefixes = append(peerPrefixes, peerNetAddr.NetIP)
}
if connIP := peer.Location.ConnectionIP; len(connIP) > 0 {
if addr, ok := netip.AddrFromSlice(connIP); ok {
addr = addr.Unmap()
peerPrefixes = append(peerPrefixes, netip.PrefixFrom(addr, addr.BitLen()))
}
}
if len(peerPrefixes) == 0 {
return false, fmt.Errorf("peer's does not contain peer network range addresses")
}
for _, peerPrefix := range peerPrefixes {
for _, rangePrefix := range p.Ranges {
if !prefixContains(rangePrefix, peerPrefix) {
continue
}
switch p.Action {
case checkActionDeny:
return false, nil
case checkActionAllow:
return true, nil
default:
return false, fmt.Errorf("invalid peer network range check action: %s", p.Action)
}
}
}
if p.Action == checkActionDeny {
return true, nil
}
if p.Action == checkActionAllow {
return false, nil
}
return false, fmt.Errorf("invalid peer network range check action: %s", p.Action)
}
func prefixContains(outer, inner netip.Prefix) bool {
outer = outer.Masked()
inner = inner.Masked()
return outer.Bits() <= inner.Bits() &&
outer.Addr().BitLen() == inner.Addr().BitLen() &&
outer.Contains(inner.Addr())
}

View File

@@ -0,0 +1,79 @@
package nmdata
import (
"strings"
"github.com/hashicorp/go-version"
)
// MinVersionCheck is the slim twin of posture.MinVersionCheck.
type MinVersionCheck struct {
MinVersion string
}
// MinKernelVersionCheck is the slim twin of posture.MinKernelVersionCheck.
type MinKernelVersionCheck struct {
MinKernelVersion string
}
// OSVersionCheck is the slim twin of posture.OSVersionCheck.
type OSVersionCheck struct {
Android *MinVersionCheck
Darwin *MinVersionCheck
Ios *MinVersionCheck
Linux *MinKernelVersionCheck
Windows *MinKernelVersionCheck
}
func (c *OSVersionCheck) check(peer *Peer) (bool, error) {
switch peer.Meta.GoOS {
case "android":
return checkMinVersion(peer.Meta.OSVersion, c.Android)
case "darwin":
return checkMinVersion(peer.Meta.OSVersion, c.Darwin)
case "ios":
return checkMinVersion(peer.Meta.OSVersion, c.Ios)
case "linux":
kernelVersion := strings.Split(peer.Meta.KernelVersion, "-")[0]
return checkMinKernelVersion(kernelVersion, c.Linux)
case "windows":
return checkMinKernelVersion(peer.Meta.KernelVersion, c.Windows)
}
return true, nil
}
func checkMinVersion(peerVersion string, check *MinVersionCheck) (bool, error) {
if check == nil {
return false, nil
}
peerNBVersion, err := version.NewVersion(peerVersion)
if err != nil {
return false, err
}
constraints, err := version.NewConstraint(">= " + check.MinVersion)
if err != nil {
return false, err
}
return constraints.Check(peerNBVersion), nil
}
func checkMinKernelVersion(peerVersion string, check *MinKernelVersionCheck) (bool, error) {
if check == nil {
return false, nil
}
peerNBVersion, err := version.NewVersion(peerVersion)
if err != nil {
return false, err
}
constraints, err := version.NewConstraint(">= " + check.MinKernelVersion)
if err != nil {
return false, err
}
return constraints.Check(peerNBVersion), nil
}

View File

@@ -0,0 +1,56 @@
package nmdata
import (
"fmt"
"slices"
)
// Process is the slim twin of posture.Process.
type Process struct {
LinuxPath string
MacPath string
WindowsPath string
}
// ProcessCheck is the slim twin of posture.ProcessCheck.
type ProcessCheck struct {
Processes []Process
}
func (p *ProcessCheck) check(peer *Peer) (bool, error) {
peerActiveProcesses := extractPeerActiveProcesses(peer.Meta.Files)
var pathSelector func(Process) string
switch peer.Meta.GoOS {
case "linux":
pathSelector = func(process Process) string { return process.LinuxPath }
case "darwin":
pathSelector = func(process Process) string { return process.MacPath }
case "windows":
pathSelector = func(process Process) string { return process.WindowsPath }
default:
return false, fmt.Errorf("unsupported peer's operating system: %s", peer.Meta.GoOS)
}
return p.areAllProcessesRunning(peerActiveProcesses, pathSelector), nil
}
func (p *ProcessCheck) areAllProcessesRunning(activeProcesses []string, pathSelector func(Process) string) bool {
for _, process := range p.Processes {
path := pathSelector(process)
if path == "" || !slices.Contains(activeProcesses, path) {
return false
}
}
return true
}
func extractPeerActiveProcesses(files []File) []string {
activeProcesses := make([]string, 0, len(files))
for _, file := range files {
if file.ProcessIsRunning {
activeProcesses = append(activeProcesses, file.Path)
}
}
return activeProcesses
}

View File

@@ -0,0 +1,108 @@
package nmdata
import (
"net/netip"
"slices"
"strings"
"github.com/netbirdio/netbird/shared/management/domain"
)
// NetworkType mirrors route.NetworkType iota values.
const (
NetworkTypeInvalid = 0
NetworkTypeIPv4 = 1
NetworkTypeIPv6 = 2
NetworkTypeDomain = 3
haSeparator = "|"
)
// Route is the slim twin of route.Route.
type Route struct {
ID string
AccountID string
PublicID string
Network netip.Prefix
Domains domain.List
KeepRoute bool
NetID string
Description string
Peer string
PeerID string
PeerGroups []string
NetworkType int
Masquerade bool
Metric int
Enabled bool
Groups []string
AccessControlGroups []string
SkipAutoApply bool
}
func (r *Route) Equal(other *Route) bool {
if r == nil && other == nil {
return true
} else if r == nil || other == nil {
return false
}
return other.ID == r.ID &&
other.Description == r.Description &&
other.NetID == r.NetID &&
other.Network == r.Network &&
slices.Equal(r.Domains, other.Domains) &&
other.KeepRoute == r.KeepRoute &&
other.NetworkType == r.NetworkType &&
other.Peer == r.Peer &&
other.PeerID == r.PeerID &&
other.Metric == r.Metric &&
other.Masquerade == r.Masquerade &&
other.Enabled == r.Enabled &&
slices.Equal(r.Groups, other.Groups) &&
slices.Equal(r.PeerGroups, other.PeerGroups) &&
slices.Equal(r.AccessControlGroups, other.AccessControlGroups) &&
other.SkipAutoApply == r.SkipAutoApply
}
func (r *Route) IsDynamic() bool {
return r.NetworkType == NetworkTypeDomain
}
func (r *Route) NetString() string {
if r.IsDynamic() && r.Domains != nil {
return r.Domains.SafeString()
}
return r.Network.String()
}
func (r *Route) GetHAUniqueID() string {
return r.NetID + haSeparator + r.NetString()
}
func (r *Route) GetResourceID() string {
return strings.Split(r.ID, ":")[0]
}
func (r *Route) Copy() *Route {
return &Route{
ID: r.ID,
AccountID: r.AccountID,
PublicID: r.PublicID,
Network: r.Network,
Domains: slices.Clone(r.Domains),
KeepRoute: r.KeepRoute,
NetID: r.NetID,
Description: r.Description,
Peer: r.Peer,
PeerID: r.PeerID,
PeerGroups: slices.Clone(r.PeerGroups),
NetworkType: r.NetworkType,
Masquerade: r.Masquerade,
Metric: r.Metric,
Enabled: r.Enabled,
Groups: slices.Clone(r.Groups),
AccessControlGroups: slices.Clone(r.AccessControlGroups),
SkipAutoApply: r.SkipAutoApply,
}
}

View File

@@ -0,0 +1,25 @@
package nmdata
// Service is the slim twin of the reverse-proxy service.Service. It carries
// only the state proxy-policy injection reads: the persisted reverse-proxy
// services and the in-memory ones synthesised from agent-network state, which
// are never written to the database.
type Service struct {
ID string
Enabled bool
Private bool
Mode string
ProxyCluster string
AccessGroups []string
Targets []*ServiceTarget
}
// ServiceTarget is the slim twin of service.Target.
type ServiceTarget struct {
Enabled bool
Path string
Port uint16
Protocol string
TargetID string
TargetType string
}