mirror of https://github.com/jetkvm/kvm.git
fix: do not apply IPv6 DHCP lease if it's from udhcpc
This commit is contained in:
parent
b84aa3822d
commit
52ddc9ebe5
|
|
@ -727,6 +727,20 @@ func (im *InterfaceManager) ReconcileLinkAddrs(addrs []types.IPAddress, family i
|
||||||
|
|
||||||
// applyDHCPLease applies DHCP lease configuration using ReconcileLinkAddrs
|
// applyDHCPLease applies DHCP lease configuration using ReconcileLinkAddrs
|
||||||
func (im *InterfaceManager) applyDHCPLease(lease *types.DHCPLease) error {
|
func (im *InterfaceManager) applyDHCPLease(lease *types.DHCPLease) error {
|
||||||
|
if lease == nil {
|
||||||
|
return fmt.Errorf("DHCP lease is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if lease.DHCPClient != "jetdhcpc" {
|
||||||
|
im.logger.Warn().Str("dhcp_client", lease.DHCPClient).Msg("ignoring DHCP lease, not implemented yet")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if lease.IsIPv6() {
|
||||||
|
im.logger.Warn().Msg("ignoring IPv6 DHCP lease, not implemented yet")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Convert DHCP lease to IPv4Config
|
// Convert DHCP lease to IPv4Config
|
||||||
ipv4Config := im.convertDHCPLeaseToIPv4Config(lease)
|
ipv4Config := im.convertDHCPLeaseToIPv4Config(lease)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,17 @@ package jetdhcpc
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jetkvm/kvm/internal/sync"
|
"github.com/jetkvm/kvm/internal/sync"
|
||||||
|
"github.com/jetkvm/kvm/pkg/nmlite/link"
|
||||||
|
|
||||||
"github.com/go-co-op/gocron/v2"
|
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
"github.com/insomniacslk/dhcp/dhcpv6"
|
"github.com/insomniacslk/dhcp/dhcpv6"
|
||||||
"github.com/jetkvm/kvm/internal/network/types"
|
"github.com/jetkvm/kvm/internal/network/types"
|
||||||
"github.com/jetkvm/kvm/pkg/nmlite/link"
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -83,8 +81,10 @@ type Config struct {
|
||||||
UpdateResolvConf func([]string) error
|
UpdateResolvConf func([]string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Client is a DHCP client.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
types.DHCPClient
|
types.DHCPClient
|
||||||
|
|
||||||
ifaces []string
|
ifaces []string
|
||||||
cfg Config
|
cfg Config
|
||||||
l *zerolog.Logger
|
l *zerolog.Logger
|
||||||
|
|
@ -101,24 +101,28 @@ type Client struct {
|
||||||
lease4Mu sync.Mutex
|
lease4Mu sync.Mutex
|
||||||
lease6Mu sync.Mutex
|
lease6Mu sync.Mutex
|
||||||
|
|
||||||
scheduler gocron.Scheduler
|
timer4 *time.Timer
|
||||||
stateDir string
|
timer6 *time.Timer
|
||||||
|
stateDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultTimerDuration = 1 * time.Second
|
||||||
|
defaultLinkUpTimeout = 30 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
// NewClient creates a new DHCP client for the given interface.
|
// NewClient creates a new DHCP client for the given interface.
|
||||||
func NewClient(ctx context.Context, ifaces []string, c *Config, l *zerolog.Logger) (*Client, error) {
|
func NewClient(ctx context.Context, ifaces []string, c *Config, l *zerolog.Logger) (*Client, error) {
|
||||||
scheduler, err := gocron.NewScheduler()
|
timer4 := time.NewTimer(defaultTimerDuration)
|
||||||
if err != nil {
|
timer6 := time.NewTimer(defaultTimerDuration)
|
||||||
return nil, fmt.Errorf("failed to create scheduler: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := *c
|
cfg := *c
|
||||||
if cfg.LinkUpTimeout == 0 {
|
if cfg.LinkUpTimeout == 0 {
|
||||||
cfg.LinkUpTimeout = 30 * time.Second
|
cfg.LinkUpTimeout = defaultLinkUpTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Timeout == 0 {
|
if cfg.Timeout == 0 {
|
||||||
cfg.Timeout = 30 * time.Second
|
cfg.Timeout = defaultLinkUpTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Retries == 0 {
|
if cfg.Retries == 0 {
|
||||||
|
|
@ -126,12 +130,11 @@ func NewClient(ctx context.Context, ifaces []string, c *Config, l *zerolog.Logge
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
ifaces: ifaces,
|
ifaces: ifaces,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
l: l,
|
l: l,
|
||||||
scheduler: scheduler,
|
stateDir: "/run/jetkvm-dhcp",
|
||||||
stateDir: "/run/jetkvm-dhcp",
|
|
||||||
|
|
||||||
currentLease4: nil,
|
currentLease4: nil,
|
||||||
currentLease6: nil,
|
currentLease6: nil,
|
||||||
|
|
@ -141,9 +144,45 @@ func NewClient(ctx context.Context, ifaces []string, c *Config, l *zerolog.Logge
|
||||||
|
|
||||||
mu: sync.Mutex{},
|
mu: sync.Mutex{},
|
||||||
cfgMu: sync.Mutex{},
|
cfgMu: sync.Mutex{},
|
||||||
|
|
||||||
|
timer4: timer4,
|
||||||
|
timer6: timer6,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resetTimer(t *time.Timer, l *zerolog.Logger) {
|
||||||
|
l.Debug().Dur("delay", defaultTimerDuration).Msg("will retry later")
|
||||||
|
t.Reset(defaultTimerDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) requestLoop(t *time.Timer, family int, ifname string) {
|
||||||
|
for range t.C {
|
||||||
|
if _, err := c.ensureInterfaceUp(ifname); err != nil {
|
||||||
|
c.l.Error().Err(err).Msg("failed to ensure interface up")
|
||||||
|
resetTimer(t, c.l)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
lease *Lease
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
switch family {
|
||||||
|
case link.AfInet:
|
||||||
|
lease, err = c.requestLease4(ifname)
|
||||||
|
case link.AfInet6:
|
||||||
|
lease, err = c.requestLease6(ifname)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.l.Error().Err(err).Msg("failed to request lease")
|
||||||
|
resetTimer(t, c.l)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
c.handleLeaseChange(lease)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) ensureInterfaceUp(ifname string) (*link.Link, error) {
|
func (c *Client) ensureInterfaceUp(ifname string) (*link.Link, error) {
|
||||||
nlm := link.GetNetlinkManager()
|
nlm := link.GetNetlinkManager()
|
||||||
iface, err := nlm.GetLinkByName(ifname)
|
iface, err := nlm.GetLinkByName(ifname)
|
||||||
|
|
@ -153,76 +192,77 @@ func (c *Client) ensureInterfaceUp(ifname string) (*link.Link, error) {
|
||||||
return nlm.EnsureInterfaceUpWithTimeout(c.ctx, iface, c.cfg.LinkUpTimeout)
|
return nlm.EnsureInterfaceUpWithTimeout(c.ctx, iface, c.cfg.LinkUpTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) sendInitialRequests() chan any {
|
// func (c *Client) sendInitialRequests() chan any {
|
||||||
return c.sendRequests(c.cfg.IPv4, c.cfg.IPv6)
|
// return c.sendRequests(c.cfg.IPv4, c.cfg.IPv6)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (c *Client) sendRequestsFamily(
|
// func (c *Client) sendRequestsFamily(
|
||||||
family int,
|
// family int,
|
||||||
wg *sync.WaitGroup,
|
// wg *sync.WaitGroup,
|
||||||
r *chan any,
|
// r *chan any,
|
||||||
l *zerolog.Logger,
|
// l *zerolog.Logger,
|
||||||
iface *link.Link,
|
// iface *link.Link,
|
||||||
) {
|
// ) {
|
||||||
wg.Add(1)
|
// wg.Add(1)
|
||||||
go func(iface *link.Link) {
|
// go func(iface *link.Link) {
|
||||||
defer wg.Done()
|
// defer wg.Done()
|
||||||
var (
|
// var (
|
||||||
lease *Lease
|
// lease *Lease
|
||||||
err error
|
// err error
|
||||||
)
|
// )
|
||||||
switch family {
|
// switch family {
|
||||||
case link.AfInet:
|
// case link.AfInet:
|
||||||
lease, err = c.requestLease4(iface)
|
// lease, err = c.requestLease4(iface)
|
||||||
case link.AfInet6:
|
// case link.AfInet6:
|
||||||
lease, err = c.requestLease6(iface)
|
// lease, err = c.requestLease6(iface)
|
||||||
}
|
// }
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
l.Error().Err(err).Msg("Could not get lease")
|
// l.Error().Err(err).Msg("Could not get lease")
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
(*r) <- lease
|
// (*r) <- lease
|
||||||
}(iface)
|
// }(iface)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (c *Client) sendRequests(ipv4, ipv6 bool) chan any {
|
// func (c *Client) sendRequests(ipv4, ipv6 bool) chan any {
|
||||||
c.mu.Lock()
|
// c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
// defer c.mu.Unlock()
|
||||||
|
|
||||||
// Yeah, this is a hack, until we can cancel all leases in progress.
|
// // Yeah, this is a hack, until we can cancel all leases in progress.
|
||||||
r := make(chan any, 3*len(c.ifaces))
|
// r := make(chan any, 3*len(c.ifaces))
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
// var wg sync.WaitGroup
|
||||||
for _, iface := range c.ifaces {
|
// for _, iface := range c.ifaces {
|
||||||
wg.Add(1)
|
// wg.Add(1)
|
||||||
go func(ifname string) {
|
// go func(ifname string) {
|
||||||
defer wg.Done()
|
// defer wg.Done()
|
||||||
|
|
||||||
l := c.l.With().Str("interface", ifname).Logger()
|
// l := c.l.With().Str("interface", ifname).Logger()
|
||||||
|
|
||||||
iface, err := c.ensureInterfaceUp(ifname)
|
// iface, err := c.ensureInterfaceUp(ifname)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
l.Error().Err(err).Msg("Could not bring up interface")
|
// l.Error().Err(err).Msg("Could not bring up interface")
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
if ipv4 {
|
// if ipv4 {
|
||||||
c.sendRequestsFamily(link.AfInet, &wg, &r, &l, iface)
|
// c.sendRequestsFamily(link.AfInet, &wg, &r, &l, iface)
|
||||||
}
|
// }
|
||||||
|
|
||||||
if ipv6 {
|
// if ipv6 {
|
||||||
c.sendRequestsFamily(link.AfInet6, &wg, &r, &l, iface)
|
// c.sendRequestsFamily(link.AfInet6, &wg, &r, &l, iface)
|
||||||
}
|
// }
|
||||||
}(iface)
|
// }(iface)
|
||||||
}
|
// }
|
||||||
|
|
||||||
go func() {
|
// go func() {
|
||||||
wg.Wait()
|
// wg.Wait()
|
||||||
close(r)
|
// close(r)
|
||||||
}()
|
// }()
|
||||||
return r
|
// return r
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
// Lease4 returns the current IPv4 lease
|
||||||
func (c *Client) Lease4() *types.DHCPLease {
|
func (c *Client) Lease4() *types.DHCPLease {
|
||||||
c.lease4Mu.Lock()
|
c.lease4Mu.Lock()
|
||||||
defer c.lease4Mu.Unlock()
|
defer c.lease4Mu.Unlock()
|
||||||
|
|
@ -234,6 +274,7 @@ func (c *Client) Lease4() *types.DHCPLease {
|
||||||
return c.currentLease4.ToDHCPLease()
|
return c.currentLease4.ToDHCPLease()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lease6 returns the current IPv6 lease
|
||||||
func (c *Client) Lease6() *types.DHCPLease {
|
func (c *Client) Lease6() *types.DHCPLease {
|
||||||
c.lease6Mu.Lock()
|
c.lease6Mu.Lock()
|
||||||
defer c.lease6Mu.Unlock()
|
defer c.lease6Mu.Unlock()
|
||||||
|
|
@ -245,6 +286,7 @@ func (c *Client) Lease6() *types.DHCPLease {
|
||||||
return c.currentLease6.ToDHCPLease()
|
return c.currentLease6.ToDHCPLease()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Domain returns the current domain
|
||||||
func (c *Client) Domain() string {
|
func (c *Client) Domain() string {
|
||||||
c.lease4Mu.Lock()
|
c.lease4Mu.Lock()
|
||||||
defer c.lease4Mu.Unlock()
|
defer c.lease4Mu.Unlock()
|
||||||
|
|
@ -263,50 +305,23 @@ func (c *Client) Domain() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleLeaseChange handles lease changes
|
||||||
func (c *Client) handleLeaseChange(lease *Lease) {
|
func (c *Client) handleLeaseChange(lease *Lease) {
|
||||||
// do not use defer here, because we need to unlock the mutex before returning
|
// do not use defer here, because we need to unlock the mutex before returning
|
||||||
|
|
||||||
ipv4 := lease.p4 != nil
|
ipv4 := lease.p4 != nil
|
||||||
version := "ipv4"
|
|
||||||
|
|
||||||
if ipv4 {
|
if ipv4 {
|
||||||
c.lease4Mu.Lock()
|
c.lease4Mu.Lock()
|
||||||
c.currentLease4 = lease
|
c.currentLease4 = lease
|
||||||
|
c.lease4Mu.Unlock()
|
||||||
} else {
|
} else {
|
||||||
version = "ipv6"
|
|
||||||
c.lease6Mu.Lock()
|
c.lease6Mu.Lock()
|
||||||
c.currentLease6 = lease
|
c.currentLease6 = lease
|
||||||
}
|
c.lease6Mu.Unlock()
|
||||||
|
|
||||||
// clear all current jobs with the same tags
|
|
||||||
// c.scheduler.RemoveByTags(version)
|
|
||||||
|
|
||||||
// add scheduler job to renew the lease
|
|
||||||
if lease.RenewalTime > 0 {
|
|
||||||
c.scheduler.NewJob(
|
|
||||||
gocron.DurationJob(time.Duration(lease.RenewalTime)*time.Second),
|
|
||||||
gocron.NewTask(func() {
|
|
||||||
c.l.Info().Msg("renewing lease")
|
|
||||||
for lease := range c.sendRequests(ipv4, !ipv4) {
|
|
||||||
if lease, ok := lease.(*Lease); ok {
|
|
||||||
c.handleLeaseChange(lease)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
gocron.WithName(fmt.Sprintf("renew-%s", version)),
|
|
||||||
gocron.WithSingletonMode(gocron.LimitModeWait),
|
|
||||||
gocron.WithTags(version),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.apply()
|
c.apply()
|
||||||
|
|
||||||
if ipv4 {
|
|
||||||
c.lease4Mu.Unlock()
|
|
||||||
} else {
|
|
||||||
c.lease6Mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: handle lease expiration
|
// TODO: handle lease expiration
|
||||||
if c.cfg.OnLease4Change != nil && ipv4 {
|
if c.cfg.OnLease4Change != nil && ipv4 {
|
||||||
c.cfg.OnLease4Change(lease.ToDHCPLease())
|
c.cfg.OnLease4Change(lease.ToDHCPLease())
|
||||||
|
|
@ -317,14 +332,23 @@ func (c *Client) handleLeaseChange(lease *Lease) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) renew() {
|
func (c *Client) doRenewLoop() {
|
||||||
for lease := range c.sendRequests(c.cfg.IPv4, c.cfg.IPv6) {
|
timer := time.NewTimer(time.Duration(c.currentLease4.RenewalTime) * time.Second)
|
||||||
if lease, ok := lease.(*Lease); ok {
|
defer timer.Stop()
|
||||||
c.handleLeaseChange(lease)
|
|
||||||
}
|
for range timer.C {
|
||||||
|
c.renew()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) renew() {
|
||||||
|
// for lease := range c.sendRequests(c.cfg.IPv4, c.cfg.IPv6) {
|
||||||
|
// if lease, ok := lease.(*Lease); ok {
|
||||||
|
// c.handleLeaseChange(lease)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) Renew() error {
|
func (c *Client) Renew() error {
|
||||||
go c.renew()
|
go c.renew()
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -350,17 +374,29 @@ func (c *Client) SetIPv4(ipv4 bool) {
|
||||||
c.lease4Mu.Lock()
|
c.lease4Mu.Lock()
|
||||||
c.currentLease4 = nil
|
c.currentLease4 = nil
|
||||||
c.lease4Mu.Unlock()
|
c.lease4Mu.Unlock()
|
||||||
c.scheduler.RemoveByTags("ipv4")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.sendRequests(ipv4, c.cfg.IPv6)
|
c.timer4.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) SetIPv6(ipv6 bool) {
|
func (c *Client) SetIPv6(ipv6 bool) {
|
||||||
c.cfgMu.Lock()
|
c.cfgMu.Lock()
|
||||||
defer c.cfgMu.Unlock()
|
defer c.cfgMu.Unlock()
|
||||||
|
|
||||||
|
currentIPv6 := c.cfg.IPv6
|
||||||
c.cfg.IPv6 = ipv6
|
c.cfg.IPv6 = ipv6
|
||||||
|
|
||||||
|
if currentIPv6 == ipv6 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ipv6 {
|
||||||
|
c.lease6Mu.Lock()
|
||||||
|
c.currentLease6 = nil
|
||||||
|
c.lease4Mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.timer6.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Start() error {
|
func (c *Client) Start() error {
|
||||||
|
|
@ -368,15 +404,14 @@ func (c *Client) Start() error {
|
||||||
c.l.Warn().Err(err).Msg("failed to kill udhcpc processes, continuing anyway")
|
c.l.Warn().Err(err).Msg("failed to kill udhcpc processes, continuing anyway")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.scheduler.Start()
|
for _, iface := range c.ifaces {
|
||||||
|
if c.cfg.IPv4 {
|
||||||
go func() {
|
go c.requestLoop(c.timer4, link.AfInet, iface)
|
||||||
for lease := range c.sendInitialRequests() {
|
|
||||||
if lease, ok := lease.(*Lease); ok {
|
|
||||||
c.handleLeaseChange(lease)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
if c.cfg.IPv6 {
|
||||||
|
go c.requestLoop(c.timer6, link.AfInet6, iface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,12 @@ import (
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) requestLease4(iface netlink.Link) (*Lease, error) {
|
func (c *Client) requestLease4(ifname string) (*Lease, error) {
|
||||||
ifname := iface.Attrs().Name
|
iface, err := netlink.LinkByName(ifname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
l := c.l.With().Str("interface", ifname).Logger()
|
l := c.l.With().Str("interface", ifname).Logger()
|
||||||
|
|
||||||
mods := []nclient4.ClientOpt{
|
mods := []nclient4.ClientOpt{
|
||||||
|
|
|
||||||
|
|
@ -55,10 +55,14 @@ func isIPv6RouteReady(serverAddr net.IP) waitForCondition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) requestLease6(iface netlink.Link) (*Lease, error) {
|
func (c *Client) requestLease6(ifname string) (*Lease, error) {
|
||||||
ifname := iface.Attrs().Name
|
|
||||||
l := c.l.With().Str("interface", ifname).Logger()
|
l := c.l.With().Str("interface", ifname).Logger()
|
||||||
|
|
||||||
|
iface, err := netlink.LinkByName(ifname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
clientPort := dhcpv6.DefaultClientPort
|
clientPort := dhcpv6.DefaultClientPort
|
||||||
if c.cfg.V6ClientPort != nil {
|
if c.cfg.V6ClientPort != nil {
|
||||||
clientPort = *c.cfg.V6ClientPort
|
clientPort = *c.cfg.V6ClientPort
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue