Pull request: dhcpd imp code
Merge in DNS/adguard-home from dhcpd-imp-code to master Squashed commit of the following: commit 413403c169bd3f6b5f2ed63b783d078dbb15e054 Merge: eed1838500fec990bc
Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Fri May 26 12:46:25 2023 +0300 Merge remote-tracking branch 'origin/master' into dhcpd-imp-code commit eed1838502add1e16e5d3ada03778f21913fd5e5 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Fri May 26 12:16:46 2023 +0300 dhcpd: imp docs commit fa4fe036f7b1f2b49201bf0b5b904f99989082f0 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Thu May 25 11:32:34 2023 +0300 all: lint script commit a4022b3d4bbfa709e5096397bbe64ea406c8a366 Merge: e08ff3a26cbc7985e7
Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Thu May 25 11:29:57 2023 +0300 Merge remote-tracking branch 'origin/master' into dhcpd-imp-code # Conflicts: # scripts/make/go-lint.sh commit e08ff3a26414e201d6e75608363db941fa2f5b39 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Wed May 24 15:43:11 2023 +0300 dhcpd: imp code commit 970b538f8ea94d3732d77bfb648e402a1d28ab06 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Wed May 24 15:40:36 2023 +0300 dhcpd: imp code commit 0e5916ddd7514af983e8557080d55d6aeb6fbbc0 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Wed May 24 15:37:17 2023 +0300 dhcpd: imp code commit e06a6c6031b232e76ae2be3e3b8fe1a2ffa715e0 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue May 23 16:40:09 2023 +0300 dhcpd: imp code commit eed4ff10ff1b29c71d70fb7978706efde89afee1 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue May 23 15:45:06 2023 +0300 all: lint script commit 87f84ace5f6f34dbc28befa8257d1d2492c5e0a4 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue May 23 15:44:23 2023 +0300 dhcpd: imp code commit a54c9929d51de1f1e6807d650fd08dd80ddbf147 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue May 23 14:29:42 2023 +0300 dhcpd: imp code commit 1bbea342f7f55587724aa9a29d9657e5ce75f5d8 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue May 23 14:12:09 2023 +0300 dhcpd: imp code commit 48fb4eff73683e799ddb3076afdcf7b067ca24b6 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue May 23 13:57:59 2023 +0300 dhcpd: imp code commit f6cd7fcb8d4c1c815a20875d777ea1eca2c8ea89 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue May 23 13:17:54 2023 +0300 dhcpd: imp code commit 2b91dc25bbaa16dba6d9389a4e2224cf91eb4554 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Tue May 23 12:57:46 2023 +0300 dhcpd: imp code commit 34f5dd58764080f6202fc9a1abd751a15dbf7090 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon May 22 15:31:39 2023 +0300 dhcpd: imp code commit 12ef0d225064a1b78adf7b2cfca21a8dba92ca8e Merge: 6b62a766524b41100c
Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon May 22 13:03:41 2023 +0300 Merge remote-tracking branch 'origin/master' into dhcpd-imp-code commit 6b62a7665720b85398d65a1926518a63e6bb6403 Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon May 22 12:55:43 2023 +0300 dhcpd: imp code commit 18c5cdf0480fac7711282027a64d58704c75af5f Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon May 22 12:48:30 2023 +0300 dhcpd: imp code commit e7c1f4324cba3fe86cf56df6b971791a5a8790de Author: Dimitry Kolyshev <dkolyshev@adguard.com> Date: Mon May 22 12:37:15 2023 +0300 dhcpd: imp code ... and 1 more commit
This commit is contained in:
parent
0fec990bcf
commit
ea8d634f65
|
@ -1,46 +1,60 @@
|
|||
# DHCP server
|
||||
# Testing DHCP Server
|
||||
|
||||
Contents:
|
||||
* [Test setup with Virtual Box](#vbox)
|
||||
* [Test setup with Virtual Box](#vbox)
|
||||
* [Quick test with DHCPTest](#dhcptest)
|
||||
|
||||
<a id="vbox"></a>
|
||||
## Test setup with Virtual Box
|
||||
## <a href="#vbox" id="vbox" name="vbox">Test setup with Virtual Box</a>
|
||||
|
||||
To set up a test environment for DHCP server you need:
|
||||
### Prerequisites
|
||||
|
||||
* Linux host machine
|
||||
* Virtual Box
|
||||
* Virtual machine (guest OS doesn't matter)
|
||||
To set up a test environment for DHCP server you will need:
|
||||
|
||||
### Configure client
|
||||
* Linux AG Home host machine (Virtual).
|
||||
* Virtual Box.
|
||||
* Virtual machine (guest OS doesn't matter).
|
||||
|
||||
1. Install Virtual Box and run the following command to create a Host-Only network:
|
||||
### Configure Virtual Box
|
||||
|
||||
$ VBoxManage hostonlyif create
|
||||
1. Install Virtual Box and run the following command to create a Host-Only
|
||||
network:
|
||||
|
||||
You can check its status by `ip a` command.
|
||||
```sh
|
||||
$ VBoxManage hostonlyif create
|
||||
```
|
||||
|
||||
You can check its status by `ip a` command.
|
||||
|
||||
You can also set up Host-Only network using Virtual Box menu:
|
||||
You can also set up Host-Only network using Virtual Box menu:
|
||||
|
||||
```
|
||||
File -> Host Network Manager...
|
||||
```
|
||||
|
||||
File -> Host Network Manager...
|
||||
2. Create your virtual machine and set up its network:
|
||||
|
||||
2. Create your virtual machine and set up its network:
|
||||
```
|
||||
VM Settings -> Network -> Host-only Adapter
|
||||
```
|
||||
|
||||
VM Settings -> Network -> Host-only Adapter
|
||||
3. Start your VM, install an OS. Configure your network interface to use
|
||||
DHCP and the OS should ask for a IP address from our DHCP server.
|
||||
|
||||
3. Start your VM, install an OS. Configure your network interface to use DHCP and the OS should ask for a IP address from our DHCP server.
|
||||
4. To see the current IP addresses on client OS you can use `ip a` command on
|
||||
Linux or `ipconfig` on Windows.
|
||||
|
||||
4. To see the current IP address on client OS you can use `ip a` command on Linux or `ipconfig` on Windows.
|
||||
5. To force the client OS to request an IP from DHCP server again, you can
|
||||
use `dhclient` on Linux or `ipconfig /release` on Windows.
|
||||
|
||||
5. To force the client OS to request an IP from DHCP server again, you can use `dhclient` on Linux or `ipconfig /release` on Windows.
|
||||
### Configure server
|
||||
|
||||
### Configure server
|
||||
1. Edit server configuration file `AdGuardHome.yaml`, for example:
|
||||
|
||||
1. Edit server configuration file 'AdGuardHome.yaml', for example:
|
||||
|
||||
dhcp:
|
||||
```yaml
|
||||
dhcp:
|
||||
enabled: true
|
||||
interface_name: vboxnet0
|
||||
local_domain_name: lan
|
||||
dhcpv4:
|
||||
gateway_ip: 192.168.56.1
|
||||
subnet_mask: 255.255.255.0
|
||||
|
@ -54,11 +68,29 @@ To set up a test environment for DHCP server you need:
|
|||
lease_duration: 86400
|
||||
ra_slaac_only: false
|
||||
ra_allow_slaac: false
|
||||
```
|
||||
|
||||
2. Start the server
|
||||
2. Start the server
|
||||
|
||||
./AdGuardHome
|
||||
```sh
|
||||
./AdGuardHome -v
|
||||
```
|
||||
|
||||
There should be a message in log which shows that DHCP server is ready:
|
||||
There should be a message in log which shows that DHCP server is ready:
|
||||
|
||||
[info] DHCP: listening on 0.0.0.0:67
|
||||
```
|
||||
[info] DHCP: listening on 0.0.0.0:67
|
||||
```
|
||||
|
||||
## <a href="#dhcptest" id="dhcptest" name="dhcptest">Quick test with DHCPTest utility</a>
|
||||
|
||||
### Prerequisites
|
||||
|
||||
* [DHCP test utility][dhcptest-gh].
|
||||
|
||||
### Quick test
|
||||
|
||||
The DHCP server could be tested for DISCOVER-OFFER packets with in
|
||||
interactive mode.
|
||||
|
||||
[dhcptest-gh]: https://github.com/CyberShadow/dhcptest
|
||||
|
|
|
@ -239,36 +239,16 @@ func Create(conf *ServerConfig) (s *server, err error) {
|
|||
// [aghhttp.RegisterFunc].
|
||||
s.registerHandlers()
|
||||
|
||||
v4conf := conf.Conf4
|
||||
v4conf.InterfaceName = s.conf.InterfaceName
|
||||
v4conf.notify = s.onNotify
|
||||
v4conf.Enabled = s.conf.Enabled && v4conf.RangeStart.IsValid()
|
||||
|
||||
s.srv4, err = v4Create(&v4conf)
|
||||
v4Enabled, v6Enabled, err := s.setServers(conf)
|
||||
if err != nil {
|
||||
if v4conf.Enabled {
|
||||
return nil, fmt.Errorf("creating dhcpv4 srv: %w", err)
|
||||
}
|
||||
|
||||
log.Debug("dhcpd: warning: creating dhcpv4 srv: %s", err)
|
||||
}
|
||||
|
||||
v6conf := conf.Conf6
|
||||
v6conf.Enabled = s.conf.Enabled
|
||||
if len(v6conf.RangeStart) == 0 {
|
||||
v6conf.Enabled = false
|
||||
}
|
||||
v6conf.InterfaceName = s.conf.InterfaceName
|
||||
v6conf.notify = s.onNotify
|
||||
s.srv6, err = v6Create(v6conf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating dhcpv6 srv: %w", err)
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.conf.Conf4 = conf.Conf4
|
||||
s.conf.Conf6 = conf.Conf6
|
||||
|
||||
if s.conf.Enabled && !v4conf.Enabled && !v6conf.Enabled {
|
||||
if s.conf.Enabled && !v4Enabled && !v6Enabled {
|
||||
return nil, fmt.Errorf("neither dhcpv4 nor dhcpv6 srv is configured")
|
||||
}
|
||||
|
||||
|
@ -289,6 +269,39 @@ func Create(conf *ServerConfig) (s *server, err error) {
|
|||
return s, nil
|
||||
}
|
||||
|
||||
// setServers updates DHCPv4 and DHCPv6 servers created from the provided
|
||||
// configuration conf.
|
||||
func (s *server) setServers(conf *ServerConfig) (v4Enabled, v6Enabled bool, err error) {
|
||||
v4conf := conf.Conf4
|
||||
v4conf.InterfaceName = s.conf.InterfaceName
|
||||
v4conf.notify = s.onNotify
|
||||
v4conf.Enabled = s.conf.Enabled && v4conf.RangeStart.IsValid()
|
||||
|
||||
s.srv4, err = v4Create(&v4conf)
|
||||
if err != nil {
|
||||
if v4conf.Enabled {
|
||||
return true, false, fmt.Errorf("creating dhcpv4 srv: %w", err)
|
||||
}
|
||||
|
||||
log.Debug("dhcpd: warning: creating dhcpv4 srv: %s", err)
|
||||
}
|
||||
|
||||
v6conf := conf.Conf6
|
||||
v6conf.InterfaceName = s.conf.InterfaceName
|
||||
v6conf.notify = s.onNotify
|
||||
v6conf.Enabled = s.conf.Enabled
|
||||
if len(v6conf.RangeStart) == 0 {
|
||||
v6conf.Enabled = false
|
||||
}
|
||||
|
||||
s.srv6, err = v6Create(v6conf)
|
||||
if err != nil {
|
||||
return v4conf.Enabled, v6conf.Enabled, fmt.Errorf("creating dhcpv6 srv: %w", err)
|
||||
}
|
||||
|
||||
return v4conf.Enabled, v6conf.Enabled, nil
|
||||
}
|
||||
|
||||
// Enabled returns true when the server is enabled.
|
||||
func (s *server) Enabled() (ok bool) {
|
||||
return s.conf.Enabled
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
)
|
||||
|
||||
type v4ServerConfJSON struct {
|
||||
|
@ -263,6 +264,28 @@ func (s *server) handleDHCPSetConfigV6(
|
|||
return srv6, enabled, err
|
||||
}
|
||||
|
||||
// createServers returns DHCPv4 and DHCPv6 servers created from the provided
|
||||
// configuration conf.
|
||||
func (s *server) createServers(conf *dhcpServerConfigJSON) (srv4, srv6 DHCPServer, err error) {
|
||||
srv4, v4Enabled, err := s.handleDHCPSetConfigV4(conf)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("bad dhcpv4 configuration: %s", err)
|
||||
}
|
||||
|
||||
srv6, v6Enabled, err := s.handleDHCPSetConfigV6(conf)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("bad dhcpv6 configuration: %s", err)
|
||||
}
|
||||
|
||||
if conf.Enabled == aghalg.NBTrue && !v4Enabled && !v6Enabled {
|
||||
return nil, nil, fmt.Errorf("dhcpv4 or dhcpv6 configuration must be complete")
|
||||
}
|
||||
|
||||
return srv4, srv6, nil
|
||||
}
|
||||
|
||||
// handleDHCPSetConfig is the handler for the POST /control/dhcp/set_config
|
||||
// HTTP API.
|
||||
func (s *server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
conf := &dhcpServerConfigJSON{}
|
||||
conf.Enabled = aghalg.BoolToNullBool(s.conf.Enabled)
|
||||
|
@ -275,22 +298,9 @@ func (s *server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
srv4, v4Enabled, err := s.handleDHCPSetConfigV4(conf)
|
||||
srv4, srv6, err := s.createServers(conf)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "bad dhcpv4 configuration: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
srv6, v6Enabled, err := s.handleDHCPSetConfigV6(conf)
|
||||
if err != nil {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "bad dhcpv6 configuration: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if conf.Enabled == aghalg.NBTrue && !v4Enabled && !v6Enabled {
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "dhcpv4 or dhcpv6 configuration must be complete")
|
||||
aghhttp.Error(r, w, http.StatusBadRequest, "%s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -350,10 +360,10 @@ type netInterfaceJSON struct {
|
|||
Addrs6 []netip.Addr `json:"ipv6_addresses"`
|
||||
}
|
||||
|
||||
// handleDHCPInterfaces is the handler for the GET /control/dhcp/interfaces HTTP
|
||||
// API.
|
||||
// handleDHCPInterfaces is the handler for the GET /control/dhcp/interfaces
|
||||
// HTTP API.
|
||||
func (s *server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
|
||||
resp := map[string]netInterfaceJSON{}
|
||||
resp := map[string]*netInterfaceJSON{}
|
||||
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
|
@ -364,68 +374,23 @@ func (s *server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
for _, iface := range ifaces {
|
||||
if iface.Flags&net.FlagLoopback != 0 {
|
||||
// it's a loopback, skip it
|
||||
continue
|
||||
}
|
||||
if iface.Flags&net.FlagBroadcast == 0 {
|
||||
// this interface doesn't support broadcast, skip it
|
||||
// It's a loopback, skip it.
|
||||
continue
|
||||
}
|
||||
|
||||
var addrs []net.Addr
|
||||
addrs, err = iface.Addrs()
|
||||
if err != nil {
|
||||
aghhttp.Error(
|
||||
r,
|
||||
w,
|
||||
http.StatusInternalServerError,
|
||||
"Failed to get addresses for interface %s: %s",
|
||||
iface.Name,
|
||||
err,
|
||||
)
|
||||
if iface.Flags&net.FlagBroadcast == 0 {
|
||||
// This interface doesn't support broadcast, skip it.
|
||||
continue
|
||||
}
|
||||
|
||||
jsonIface, iErr := newNetInterfaceJSON(iface)
|
||||
if iErr != nil {
|
||||
aghhttp.Error(r, w, http.StatusInternalServerError, "%s", iErr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
jsonIface := netInterfaceJSON{
|
||||
Name: iface.Name,
|
||||
HardwareAddr: iface.HardwareAddr.String(),
|
||||
}
|
||||
|
||||
if iface.Flags != 0 {
|
||||
jsonIface.Flags = iface.Flags.String()
|
||||
}
|
||||
// we don't want link-local addresses in json, so skip them
|
||||
for _, addr := range addrs {
|
||||
ipnet, ok := addr.(*net.IPNet)
|
||||
if !ok {
|
||||
// not an IPNet, should not happen
|
||||
aghhttp.Error(
|
||||
r,
|
||||
w,
|
||||
http.StatusInternalServerError,
|
||||
"got iface.Addrs() element %[1]s that is not net.IPNet, it is %[1]T",
|
||||
addr)
|
||||
|
||||
return
|
||||
}
|
||||
// ignore link-local
|
||||
//
|
||||
// TODO(e.burkov): Try to listen DHCP on LLA as well.
|
||||
if ipnet.IP.IsLinkLocalUnicast() {
|
||||
continue
|
||||
}
|
||||
|
||||
if ip4 := ipnet.IP.To4(); ip4 != nil {
|
||||
addr := netip.AddrFrom4(*(*[4]byte)(ip4))
|
||||
jsonIface.Addrs4 = append(jsonIface.Addrs4, addr)
|
||||
} else {
|
||||
addr := netip.AddrFrom16(*(*[16]byte)(ipnet.IP))
|
||||
jsonIface.Addrs6 = append(jsonIface.Addrs6, addr)
|
||||
}
|
||||
}
|
||||
if len(jsonIface.Addrs4)+len(jsonIface.Addrs6) != 0 {
|
||||
jsonIface.GatewayIP = aghnet.GatewayIP(iface.Name)
|
||||
if jsonIface != nil {
|
||||
resp[iface.Name] = jsonIface
|
||||
}
|
||||
}
|
||||
|
@ -433,6 +398,64 @@ func (s *server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
|
|||
_ = aghhttp.WriteJSONResponse(w, r, resp)
|
||||
}
|
||||
|
||||
// newNetInterfaceJSON creates a JSON object from a [net.Interface] iface.
|
||||
func newNetInterfaceJSON(iface net.Interface) (out *netInterfaceJSON, err error) {
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to get addresses for interface %s: %s",
|
||||
iface.Name,
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
out = &netInterfaceJSON{
|
||||
Name: iface.Name,
|
||||
HardwareAddr: iface.HardwareAddr.String(),
|
||||
}
|
||||
|
||||
if iface.Flags != 0 {
|
||||
out.Flags = iface.Flags.String()
|
||||
}
|
||||
|
||||
// We don't want link-local addresses in JSON, so skip them.
|
||||
for _, addr := range addrs {
|
||||
ipNet, ok := addr.(*net.IPNet)
|
||||
if !ok {
|
||||
// Not an IPNet, should not happen.
|
||||
return nil, fmt.Errorf("got iface.Addrs() element %[1]s that is not"+
|
||||
" net.IPNet, it is %[1]T", addr)
|
||||
}
|
||||
|
||||
// Ignore link-local.
|
||||
//
|
||||
// TODO(e.burkov): Try to listen DHCP on LLA as well.
|
||||
if ipNet.IP.IsLinkLocalUnicast() {
|
||||
continue
|
||||
}
|
||||
|
||||
vAddr, iErr := netutil.IPToAddrNoMapped(ipNet.IP)
|
||||
if iErr != nil {
|
||||
// Not an IPNet, should not happen.
|
||||
return nil, fmt.Errorf("failed to convert IP address %[1]s: %w", addr, iErr)
|
||||
}
|
||||
|
||||
if vAddr.Is4() {
|
||||
out.Addrs4 = append(out.Addrs4, vAddr)
|
||||
} else {
|
||||
out.Addrs6 = append(out.Addrs6, vAddr)
|
||||
}
|
||||
}
|
||||
|
||||
if len(out.Addrs4)+len(out.Addrs6) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
out.GatewayIP = aghnet.GatewayIP(iface.Name)
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// dhcpSearchOtherResult contains information about other DHCP server for
|
||||
// specific network interface.
|
||||
type dhcpSearchOtherResult struct {
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/netutil"
|
||||
"golang.org/x/net/icmp"
|
||||
|
@ -195,7 +196,7 @@ func createICMPv6RAPacket(params icmpv6RA) (data []byte, err error) {
|
|||
return data, nil
|
||||
}
|
||||
|
||||
// Init - initialize RA module
|
||||
// Init initializes RA module.
|
||||
func (ra *raCtx) Init() (err error) {
|
||||
ra.stop.Store(0)
|
||||
ra.conn = nil
|
||||
|
@ -203,8 +204,7 @@ func (ra *raCtx) Init() (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
log.Debug("dhcpv6 ra: source IP address: %s DNS IP address: %s",
|
||||
ra.ipAddr, ra.dnsIPAddr)
|
||||
log.Debug("dhcpv6 ra: source IP address: %s DNS IP address: %s", ra.ipAddr, ra.dnsIPAddr)
|
||||
|
||||
params := icmpv6RA{
|
||||
managedAddressConfiguration: !ra.raSLAACOnly,
|
||||
|
@ -223,18 +223,15 @@ func (ra *raCtx) Init() (err error) {
|
|||
return fmt.Errorf("creating packet: %w", err)
|
||||
}
|
||||
|
||||
success := false
|
||||
ipAndScope := ra.ipAddr.String() + "%" + ra.ifaceName
|
||||
ra.conn, err = icmp.ListenPacket("ip6:ipv6-icmp", ipAndScope)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dhcpv6 ra: icmp.ListenPacket: %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if !success {
|
||||
derr := ra.Close()
|
||||
if derr != nil {
|
||||
log.Error("closing context: %s", derr)
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.WithDeferred(err, ra.Close())
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -269,7 +266,6 @@ func (ra *raCtx) Init() (err error) {
|
|||
log.Debug("dhcpv6 ra: loop exit")
|
||||
}()
|
||||
|
||||
success = true
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -342,8 +342,8 @@ func (s *v4Server) rmLease(lease *Lease) (err error) {
|
|||
// server to be configured and it's not.
|
||||
const ErrUnconfigured errors.Error = "server is unconfigured"
|
||||
|
||||
// AddStaticLease implements the DHCPServer interface for *v4Server. It is safe
|
||||
// for concurrent use.
|
||||
// AddStaticLease implements the DHCPServer interface for *v4Server. It is
|
||||
// safe for concurrent use.
|
||||
func (s *v4Server) AddStaticLease(l *Lease) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "dhcpv4: adding static lease: %w") }()
|
||||
|
||||
|
@ -354,21 +354,23 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) {
|
|||
l.IP = l.IP.Unmap()
|
||||
|
||||
if !l.IP.Is4() {
|
||||
return fmt.Errorf("invalid ip %q, only ipv4 is supported", l.IP)
|
||||
return fmt.Errorf("invalid IP %q: only IPv4 is supported", l.IP)
|
||||
} else if gwIP := s.conf.GatewayIP; gwIP == l.IP {
|
||||
return fmt.Errorf("can't assign the gateway IP %s to the lease", gwIP)
|
||||
return fmt.Errorf("can't assign the gateway IP %q to the lease", gwIP)
|
||||
}
|
||||
|
||||
l.IsStatic = true
|
||||
|
||||
err = netutil.ValidateMAC(l.HWAddr)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
if hostname := l.Hostname; hostname != "" {
|
||||
hostname, err = normalizeHostname(hostname)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -386,32 +388,9 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) {
|
|||
l.Hostname = hostname
|
||||
}
|
||||
|
||||
// Perform the following actions in an anonymous function to make sure
|
||||
// that the lock gets unlocked before the notification step.
|
||||
func() {
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
err = s.rmDynamicLease(l)
|
||||
if err != nil {
|
||||
err = fmt.Errorf(
|
||||
"removing dynamic leases for %s (%s): %w",
|
||||
l.IP,
|
||||
l.HWAddr,
|
||||
err,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err = s.addLease(l)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("adding static lease for %s (%s): %w", l.IP, l.HWAddr, err)
|
||||
|
||||
return
|
||||
}
|
||||
}()
|
||||
err = s.updateStaticLease(l)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -421,6 +400,25 @@ func (s *v4Server) AddStaticLease(l *Lease) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// updateStaticLease safe removes dynamic lease with the same properties and
|
||||
// then adds a static lease l.
|
||||
func (s *v4Server) updateStaticLease(l *Lease) (err error) {
|
||||
s.leasesLock.Lock()
|
||||
defer s.leasesLock.Unlock()
|
||||
|
||||
err = s.rmDynamicLease(l)
|
||||
if err != nil {
|
||||
return fmt.Errorf("removing dynamic leases for %s (%s): %w", l.IP, l.HWAddr, err)
|
||||
}
|
||||
|
||||
err = s.addLease(l)
|
||||
if err != nil {
|
||||
return fmt.Errorf("adding static lease for %s (%s): %w", l.IP, l.HWAddr, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveStaticLease removes a static lease. It is safe for concurrent use.
|
||||
func (s *v4Server) RemoveStaticLease(l *Lease) (err error) {
|
||||
defer func() { err = errors.Annotate(err, "dhcpv4: %w") }()
|
||||
|
@ -894,24 +892,9 @@ func (s *v4Server) handleDecline(req, resp *dhcpv4.DHCPv4) (err error) {
|
|||
reqIP = req.ClientIPAddr
|
||||
}
|
||||
|
||||
netIP, ok := netip.AddrFromSlice(reqIP)
|
||||
if !ok {
|
||||
log.Info("dhcpv4: invalid IP: %s", reqIP)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var oldLease *Lease
|
||||
for _, l := range s.leases {
|
||||
if bytes.Equal(l.HWAddr, mac) && l.IP == netIP {
|
||||
oldLease = l
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
oldLease := s.findLeaseForIP(reqIP, mac)
|
||||
if oldLease == nil {
|
||||
log.Info("dhcpv4: lease with ip %s for %s not found", reqIP, mac)
|
||||
log.Info("dhcpv4: lease with IP %s for %s not found", reqIP, mac)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -925,7 +908,7 @@ func (s *v4Server) handleDecline(req, resp *dhcpv4.DHCPv4) (err error) {
|
|||
if err != nil {
|
||||
return fmt.Errorf("allocating new lease for %s: %w", mac, err)
|
||||
} else if newLease == nil {
|
||||
log.Info("dhcpv4: allocating new lease for %s: no more ip addresses", mac)
|
||||
log.Info("dhcpv4: allocating new lease for %s: no more IP addresses", mac)
|
||||
|
||||
resp.YourIPAddr = make([]byte, 4)
|
||||
resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck))
|
||||
|
@ -941,15 +924,32 @@ func (s *v4Server) handleDecline(req, resp *dhcpv4.DHCPv4) (err error) {
|
|||
return fmt.Errorf("adding new lease for %s: %w", mac, err)
|
||||
}
|
||||
|
||||
log.Info("dhcpv4: changed ip from %s to %s for %s", reqIP, newLease.IP, mac)
|
||||
|
||||
resp.YourIPAddr = net.IP(newLease.IP.AsSlice())
|
||||
log.Info("dhcpv4: changed IP from %s to %s for %s", reqIP, newLease.IP, mac)
|
||||
|
||||
resp.YourIPAddr = newLease.IP.AsSlice()
|
||||
resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// findLeaseForIP returns a lease for provided ip and mac.
|
||||
func (s *v4Server) findLeaseForIP(ip net.IP, mac net.HardwareAddr) (l *Lease) {
|
||||
netIP, ok := netip.AddrFromSlice(ip)
|
||||
if !ok {
|
||||
log.Info("dhcpv4: invalid IP: %s", ip)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, il := range s.leases {
|
||||
if bytes.Equal(il.HWAddr, mac) && il.IP == netIP {
|
||||
return il
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleRelease is the handler for the DHCP Release request.
|
||||
func (s *v4Server) handleRelease(req, resp *dhcpv4.DHCPv4) (err error) {
|
||||
mac := req.ClientHWAddr
|
||||
|
@ -995,11 +995,80 @@ func (s *v4Server) handleRelease(req, resp *dhcpv4.DHCPv4) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Find a lease associated with MAC and prepare response
|
||||
// Return 1: OK
|
||||
// Return 0: error; reply with Nak
|
||||
// Return -1: error; don't reply
|
||||
func (s *v4Server) handle(req, resp *dhcpv4.DHCPv4) int {
|
||||
// messageHandler describes a DHCPv4 message handler function.
|
||||
type messageHandler func(s *v4Server, req, resp *dhcpv4.DHCPv4) (rCode int, l *Lease, err error)
|
||||
|
||||
// messageHandlers is a map of handlers for various messages with message types
|
||||
// keys.
|
||||
var messageHandlers = map[dhcpv4.MessageType]messageHandler{
|
||||
dhcpv4.MessageTypeDiscover: func(
|
||||
s *v4Server,
|
||||
req *dhcpv4.DHCPv4,
|
||||
resp *dhcpv4.DHCPv4,
|
||||
) (rCode int, l *Lease, err error) {
|
||||
l, err = s.handleDiscover(req, resp)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("handling discover: %s", err)
|
||||
}
|
||||
|
||||
if l == nil {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
return 1, l, nil
|
||||
},
|
||||
dhcpv4.MessageTypeRequest: func(
|
||||
s *v4Server,
|
||||
req *dhcpv4.DHCPv4,
|
||||
resp *dhcpv4.DHCPv4,
|
||||
) (rCode int, l *Lease, err error) {
|
||||
var toReply bool
|
||||
l, toReply = s.handleRequest(req, resp)
|
||||
if l == nil {
|
||||
if toReply {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
// Drop the packet.
|
||||
return -1, nil, nil
|
||||
}
|
||||
|
||||
return 1, l, nil
|
||||
},
|
||||
dhcpv4.MessageTypeDecline: func(
|
||||
s *v4Server,
|
||||
req *dhcpv4.DHCPv4,
|
||||
resp *dhcpv4.DHCPv4,
|
||||
) (rCode int, l *Lease, err error) {
|
||||
err = s.handleDecline(req, resp)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("handling decline: %s", err)
|
||||
}
|
||||
|
||||
return 1, nil, nil
|
||||
},
|
||||
dhcpv4.MessageTypeRelease: func(
|
||||
s *v4Server,
|
||||
req *dhcpv4.DHCPv4,
|
||||
resp *dhcpv4.DHCPv4,
|
||||
) (rCode int, l *Lease, err error) {
|
||||
err = s.handleRelease(req, resp)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("handling release: %s", err)
|
||||
}
|
||||
|
||||
return 1, nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
// handle processes request, it finds a lease associated with MAC address and
|
||||
// prepares response.
|
||||
//
|
||||
// Possible return values are:
|
||||
// - "1": OK,
|
||||
// - "0": error, reply with Nak,
|
||||
// - "-1": error, don't reply.
|
||||
func (s *v4Server) handle(req, resp *dhcpv4.DHCPv4) (rCode int) {
|
||||
var err error
|
||||
|
||||
// Include server's identifier option since any reply should contain it.
|
||||
|
@ -1007,47 +1076,26 @@ func (s *v4Server) handle(req, resp *dhcpv4.DHCPv4) int {
|
|||
// See https://datatracker.ietf.org/doc/html/rfc2131#page-29.
|
||||
resp.UpdateOption(dhcpv4.OptServerIdentifier(s.conf.dnsIPAddrs[0].AsSlice()))
|
||||
|
||||
// TODO(a.garipov): Refactor this into handlers.
|
||||
var l *Lease
|
||||
switch mt := req.MessageType(); mt {
|
||||
case dhcpv4.MessageTypeDiscover:
|
||||
l, err = s.handleDiscover(req, resp)
|
||||
if err != nil {
|
||||
log.Error("dhcpv4: handling discover: %s", err)
|
||||
handler := messageHandlers[req.MessageType()]
|
||||
if handler == nil {
|
||||
s.updateOptions(req, resp)
|
||||
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
if l == nil {
|
||||
return 0
|
||||
}
|
||||
case dhcpv4.MessageTypeRequest:
|
||||
var toReply bool
|
||||
l, toReply = s.handleRequest(req, resp)
|
||||
if l == nil {
|
||||
if toReply {
|
||||
return 0
|
||||
}
|
||||
return -1 // drop packet
|
||||
}
|
||||
case dhcpv4.MessageTypeDecline:
|
||||
err = s.handleDecline(req, resp)
|
||||
if err != nil {
|
||||
log.Error("dhcpv4: handling decline: %s", err)
|
||||
rCode, l, err := handler(s, req, resp)
|
||||
if err != nil {
|
||||
log.Error("dhcpv4: %s", err)
|
||||
|
||||
return 0
|
||||
}
|
||||
case dhcpv4.MessageTypeRelease:
|
||||
err = s.handleRelease(req, resp)
|
||||
if err != nil {
|
||||
log.Error("dhcpv4: handling release: %s", err)
|
||||
return 0
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
if rCode != 1 {
|
||||
return rCode
|
||||
}
|
||||
|
||||
if l != nil {
|
||||
resp.YourIPAddr = net.IP(l.IP.AsSlice())
|
||||
resp.YourIPAddr = l.IP.AsSlice()
|
||||
}
|
||||
|
||||
s.updateOptions(req, resp)
|
||||
|
@ -1162,23 +1210,8 @@ func (s *v4Server) Start() (err error) {
|
|||
// No available IP addresses which may appear later.
|
||||
return nil
|
||||
}
|
||||
// Update the value of Domain Name Server option separately from others if
|
||||
// not assigned yet since its value is available only at server's start.
|
||||
//
|
||||
// TODO(e.burkov): Initialize as implicit option with the rest of default
|
||||
// options when it will be possible to do before the call to Start.
|
||||
if !s.explicitOpts.Has(dhcpv4.OptionDomainNameServer) {
|
||||
s.implicitOpts.Update(dhcpv4.OptDNS(dnsIPAddrs...))
|
||||
}
|
||||
|
||||
for _, ip := range dnsIPAddrs {
|
||||
ip = ip.To4()
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
s.conf.dnsIPAddrs = append(s.conf.dnsIPAddrs, netip.AddrFrom4(*(*[4]byte)(ip)))
|
||||
}
|
||||
s.configureDNSIPAddrs(dnsIPAddrs)
|
||||
|
||||
var c net.PacketConn
|
||||
if c, err = s.newDHCPConn(iface); err != nil {
|
||||
|
@ -1199,10 +1232,10 @@ func (s *v4Server) Start() (err error) {
|
|||
log.Info("dhcpv4: listening")
|
||||
|
||||
go func() {
|
||||
if serr := s.srv.Serve(); errors.Is(serr, net.ErrClosed) {
|
||||
if sErr := s.srv.Serve(); errors.Is(sErr, net.ErrClosed) {
|
||||
log.Info("dhcpv4: server is closed")
|
||||
} else if serr != nil {
|
||||
log.Error("dhcpv4: srv.Serve: %s", serr)
|
||||
} else if sErr != nil {
|
||||
log.Error("dhcpv4: srv.Serve: %s", sErr)
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -1213,6 +1246,28 @@ func (s *v4Server) Start() (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// configureDNSIPAddrs updates v4Server configuration with provided slice of
|
||||
// dns IP addresses.
|
||||
func (s *v4Server) configureDNSIPAddrs(dnsIPAddrs []net.IP) {
|
||||
// Update the value of Domain Name Server option separately from others if
|
||||
// not assigned yet since its value is available only at server's start.
|
||||
//
|
||||
// TODO(e.burkov): Initialize as implicit option with the rest of default
|
||||
// options when it will be possible to do before the call to Start.
|
||||
if !s.explicitOpts.Has(dhcpv4.OptionDomainNameServer) {
|
||||
s.implicitOpts.Update(dhcpv4.OptDNS(dnsIPAddrs...))
|
||||
}
|
||||
|
||||
for _, ip := range dnsIPAddrs {
|
||||
vAddr, err := netutil.IPToAddr(ip, netutil.AddrFamilyIPv4)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
s.conf.dnsIPAddrs = append(s.conf.dnsIPAddrs, vAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop - stop server
|
||||
func (s *v4Server) Stop() (err error) {
|
||||
if s.srv == nil {
|
||||
|
|
|
@ -227,7 +227,7 @@ func TestV4Server_AddRemove_static(t *testing.T) {
|
|||
},
|
||||
name: "with_gateway_ip",
|
||||
wantErrMsg: "dhcpv4: adding static lease: " +
|
||||
"can't assign the gateway IP 192.168.10.1 to the lease",
|
||||
`can't assign the gateway IP "192.168.10.1" to the lease`,
|
||||
}, {
|
||||
lease: &Lease{
|
||||
Hostname: "ip6.local",
|
||||
|
@ -236,7 +236,7 @@ func TestV4Server_AddRemove_static(t *testing.T) {
|
|||
},
|
||||
name: "ipv6",
|
||||
wantErrMsg: `dhcpv4: adding static lease: ` +
|
||||
`invalid ip "ffff::1", only ipv4 is supported`,
|
||||
`invalid IP "ffff::1": only IPv4 is supported`,
|
||||
}, {
|
||||
lease: &Lease{
|
||||
Hostname: "bad-mac.local",
|
||||
|
|
|
@ -586,9 +586,31 @@ func (s *v6Server) packetHandler(conn net.PacketConn, peer net.Addr, req dhcpv6.
|
|||
}
|
||||
}
|
||||
|
||||
// initialize RA module
|
||||
func (s *v6Server) initRA(iface *net.Interface) error {
|
||||
// choose the source IP address - should be link-local-unicast
|
||||
// configureDNSIPAddrs updates v6Server configuration with the slice of DNS IP
|
||||
// addresses of provided interface iface. Initializes RA module.
|
||||
func (s *v6Server) configureDNSIPAddrs(iface *net.Interface) (ok bool, err error) {
|
||||
dnsIPAddrs, err := aghnet.IfaceDNSIPAddrs(
|
||||
iface,
|
||||
aghnet.IPVersion6,
|
||||
defaultMaxAttempts,
|
||||
defaultBackoff,
|
||||
)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("interface %s: %w", iface.Name, err)
|
||||
}
|
||||
|
||||
if len(dnsIPAddrs) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
s.conf.dnsIPAddrs = dnsIPAddrs
|
||||
|
||||
return true, s.initRA(iface)
|
||||
}
|
||||
|
||||
// initRA initializes RA module.
|
||||
func (s *v6Server) initRA(iface *net.Interface) (err error) {
|
||||
// Choose the source IP address - should be link-local-unicast.
|
||||
s.ra.ipAddr = s.conf.dnsIPAddrs[0]
|
||||
for _, ip := range s.conf.dnsIPAddrs {
|
||||
if ip.IsLinkLocalUnicast() {
|
||||
|
@ -604,6 +626,7 @@ func (s *v6Server) initRA(iface *net.Interface) error {
|
|||
s.ra.ifaceName = s.conf.InterfaceName
|
||||
s.ra.iface = iface
|
||||
s.ra.packetSendPeriod = 1 * time.Second
|
||||
|
||||
return s.ra.Init()
|
||||
}
|
||||
|
||||
|
@ -623,37 +646,24 @@ func (s *v6Server) Start() (err error) {
|
|||
|
||||
log.Debug("dhcpv6: starting...")
|
||||
|
||||
dnsIPAddrs, err := aghnet.IfaceDNSIPAddrs(
|
||||
iface,
|
||||
aghnet.IPVersion6,
|
||||
defaultMaxAttempts,
|
||||
defaultBackoff,
|
||||
)
|
||||
ok, err := s.configureDNSIPAddrs(iface)
|
||||
if err != nil {
|
||||
return fmt.Errorf("interface %s: %w", ifaceName, err)
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
if len(dnsIPAddrs) == 0 {
|
||||
if !ok {
|
||||
// No available IP addresses which may appear later.
|
||||
return nil
|
||||
}
|
||||
|
||||
s.conf.dnsIPAddrs = dnsIPAddrs
|
||||
|
||||
err = s.initRA(iface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// don't initialize DHCPv6 server if we must force the clients to use SLAAC
|
||||
// Don't initialize DHCPv6 server if we must force the clients to use SLAAC.
|
||||
if s.conf.RASLAACOnly {
|
||||
log.Debug("not starting dhcpv6 server due to ra_slaac_only=true")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debug("dhcpv6: listening...")
|
||||
|
||||
err = netutil.ValidateMAC(iface.HardwareAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("validating interface %s: %w", iface.Name, err)
|
||||
|
@ -665,20 +675,18 @@ func (s *v6Server) Start() (err error) {
|
|||
Time: dhcpv6.GetTime(),
|
||||
}
|
||||
|
||||
laddr := &net.UDPAddr{
|
||||
IP: net.ParseIP("::"),
|
||||
Port: dhcpv6.DefaultServerPort,
|
||||
}
|
||||
s.srv, err = server6.NewServer(iface.Name, laddr, s.packetHandler, server6.WithDebugLogger())
|
||||
s.srv, err = server6.NewServer(iface.Name, nil, s.packetHandler, server6.WithDebugLogger())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("dhcpv6: listening...")
|
||||
|
||||
go func() {
|
||||
if serr := s.srv.Serve(); errors.Is(serr, net.ErrClosed) {
|
||||
if sErr := s.srv.Serve(); errors.Is(sErr, net.ErrClosed) {
|
||||
log.Info("dhcpv6: server is closed")
|
||||
} else if serr != nil {
|
||||
log.Error("dhcpv6: srv.Serve: %s", serr)
|
||||
} else if sErr != nil {
|
||||
log.Error("dhcpv6: srv.Serve: %s", sErr)
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
|
@ -160,29 +160,7 @@ run_linter "$GO" vet ./...
|
|||
|
||||
run_linter govulncheck ./...
|
||||
|
||||
# Apply more lax standards to the code we haven't properly refactored yet.
|
||||
run_linter gocyclo --over 12 ./internal/dhcpd
|
||||
|
||||
# Apply the normal standards to new or somewhat refactored code.
|
||||
run_linter gocyclo --over 10\
|
||||
./internal/aghio/\
|
||||
./internal/aghnet/\
|
||||
./internal/aghos/\
|
||||
./internal/aghtest/\
|
||||
./internal/dnsforward/\
|
||||
./internal/filtering/\
|
||||
./internal/home/\
|
||||
./internal/next/\
|
||||
./internal/querylog/\
|
||||
./internal/stats/\
|
||||
./internal/tools/\
|
||||
./internal/updater/\
|
||||
./internal/version/\
|
||||
./scripts/blocked-services/\
|
||||
./scripts/vetted-filters/\
|
||||
./scripts/translations/\
|
||||
./main.go\
|
||||
;
|
||||
run_linter gocyclo --over 10 .
|
||||
|
||||
run_linter ineffassign ./...
|
||||
|
||||
|
|
Loading…
Reference in New Issue