diff --git a/internal/dhcpd/broadcast_bsd.go b/internal/dhcpd/broadcast_bsd.go index b55a903d633..644bdb5117b 100644 --- a/internal/dhcpd/broadcast_bsd.go +++ b/internal/dhcpd/broadcast_bsd.go @@ -10,11 +10,10 @@ import ( // broadcast sends resp to the broadcast address specific for network interface. func (c *dhcpConn) broadcast(respData []byte, peer *net.UDPAddr) (n int, err error) { // Despite the fact that server4.NewIPv4UDPConn explicitly sets socket - // options to allow broadcasting, it also binds the connection to a - // specific interface. On FreeBSD and OpenBSD net.UDPConn.WriteTo - // causes errors while writing to the addresses that belong to another - // interface. So, use the broadcast address specific for the interface - // bound. + // options to allow broadcasting, it also binds the connection to a specific + // interface. On FreeBSD and OpenBSD net.UDPConn.WriteTo causes errors + // while writing to the addresses that belong to another interface. So, use + // the broadcast address specific for the interface bound. peer.IP = c.bcastIP return c.udpConn.WriteTo(respData, peer) diff --git a/internal/dhcpd/server.go b/internal/dhcpd/config.go similarity index 80% rename from internal/dhcpd/server.go rename to internal/dhcpd/config.go index 32723468666..0e3632b2ac3 100644 --- a/internal/dhcpd/server.go +++ b/internal/dhcpd/config.go @@ -3,8 +3,34 @@ package dhcpd import ( "net" "time" + + "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" ) +// ServerConfig is the configuration for the DHCP server. The order of YAML +// fields is important, since the YAML configuration file follows it. +type ServerConfig struct { + // Called when the configuration is changed by HTTP request + ConfigModified func() `yaml:"-"` + + // Register an HTTP handler + HTTPRegister aghhttp.RegisterFunc `yaml:"-"` + + Enabled bool `yaml:"enabled"` + InterfaceName string `yaml:"interface_name"` + + // LocalDomainName is the domain name used for DHCP hosts. For example, + // a DHCP client with the hostname "myhost" can be addressed as "myhost.lan" + // when LocalDomainName is "lan". + LocalDomainName string `yaml:"local_domain_name"` + + Conf4 V4ServerConf `yaml:"dhcpv4"` + Conf6 V6ServerConf `yaml:"dhcpv6"` + + WorkDir string `yaml:"-"` + DBFilePath string `yaml:"-"` +} + // DHCPServer - DHCP server interface type DHCPServer interface { // ResetLeases resets leases. diff --git a/internal/dhcpd/db.go b/internal/dhcpd/db.go index af6bf72e017..91a83447992 100644 --- a/internal/dhcpd/db.go +++ b/internal/dhcpd/db.go @@ -32,7 +32,7 @@ func normalizeIP(ip net.IP) net.IP { } // Load lease table from DB -func (s *Server) dbLoad() (err error) { +func (s *server) dbLoad() (err error) { dynLeases := []*Lease{} staticLeases := []*Lease{} v6StaticLeases := []*Lease{} @@ -132,7 +132,7 @@ func normalizeLeases(staticLeases, dynLeases []*Lease) []*Lease { } // Store lease table in DB -func (s *Server) dbStore() (err error) { +func (s *server) dbStore() (err error) { // Use an empty slice here as opposed to nil so that it doesn't write // "null" into the database file if leases are empty. leases := []leaseJSON{} diff --git a/internal/dhcpd/dhcpd.go b/internal/dhcpd/dhcpd.go index 006424e29d8..96afef4b08c 100644 --- a/internal/dhcpd/dhcpd.go +++ b/internal/dhcpd/dhcpd.go @@ -8,7 +8,6 @@ import ( "path/filepath" "time" - "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/timeutil" @@ -24,11 +23,16 @@ const ( // DefaultDHCPLeaseTTL is the default time-to-live for leases. DefaultDHCPLeaseTTL = uint32(timeutil.Day / time.Second) + // DefaultDHCPTimeoutICMP is the default timeout for waiting ICMP responses. DefaultDHCPTimeoutICMP = 1000 ) -var webHandlersRegistered = false +// Currently used defaults for ifaceDNSAddrs. +const ( + defaultMaxAttempts int = 10 + defaultBackoff time.Duration = 500 * time.Millisecond +) // Lease contains the necessary information about a DHCP lease type Lease struct { @@ -124,30 +128,6 @@ func (l *Lease) UnmarshalJSON(data []byte) (err error) { return nil } -// ServerConfig is the configuration for the DHCP server. The order of YAML -// fields is important, since the YAML configuration file follows it. -type ServerConfig struct { - // Called when the configuration is changed by HTTP request - ConfigModified func() `yaml:"-"` - - // Register an HTTP handler - HTTPRegister aghhttp.RegisterFunc `yaml:"-"` - - Enabled bool `yaml:"enabled"` - InterfaceName string `yaml:"interface_name"` - - // LocalDomainName is the domain name used for DHCP hosts. For example, - // a DHCP client with the hostname "myhost" can be addressed as "myhost.lan" - // when LocalDomainName is "lan". - LocalDomainName string `yaml:"local_domain_name"` - - Conf4 V4ServerConf `yaml:"dhcpv4"` - Conf6 V6ServerConf `yaml:"dhcpv6"` - - WorkDir string `yaml:"-"` - DBFilePath string `yaml:"-"` -} - // OnLeaseChangedT is a callback for lease changes. type OnLeaseChangedT func(flags int) @@ -161,8 +141,8 @@ const ( LeaseChangedDBStore ) -// Server - the current state of the DHCP server -type Server struct { +// server - the current state of the DHCP server +type server struct { srv4 DHCPServer srv6 DHCPServer @@ -174,27 +154,36 @@ type Server struct { onLeaseChanged []OnLeaseChangedT } +// type check +var _ Interface = (*server)(nil) + // GetLeasesFlags are the flags for GetLeases. type GetLeasesFlags uint8 // GetLeasesFlags values const ( - LeasesDynamic GetLeasesFlags = 0b0001 - LeasesStatic GetLeasesFlags = 0b0010 + LeasesDynamic GetLeasesFlags = 0b01 + LeasesStatic GetLeasesFlags = 0b10 LeasesAll = LeasesDynamic | LeasesStatic ) -// ServerInterface is an interface for servers. -type ServerInterface interface { +// Interface is an interface for servers. +type Interface interface { + Start() (err error) + Stop() (err error) + Enabled() (ok bool) Leases(flags GetLeasesFlags) (leases []*Lease) SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT) + FindMACbyIP(ip net.IP) (mac net.HardwareAddr) + + WriteDiskConfig(c *ServerConfig) } // Create - create object -func Create(conf *ServerConfig) (s *Server, err error) { - s = &Server{ +func Create(conf *ServerConfig) (s *server, err error) { + s = &server{ conf: &ServerConfig{ ConfigModified: conf.ConfigModified, @@ -209,11 +198,7 @@ func Create(conf *ServerConfig) (s *Server, err error) { }, } - if !webHandlersRegistered && s.conf.HTTPRegister != nil { - s.registerHandlers() - - webHandlersRegistered = true - } + s.registerHandlers() v4conf := conf.Conf4 v4conf.Enabled = s.conf.Enabled @@ -258,12 +243,12 @@ func Create(conf *ServerConfig) (s *Server, err error) { } // Enabled returns true when the server is enabled. -func (s *Server) Enabled() (ok bool) { +func (s *server) Enabled() (ok bool) { return s.conf.Enabled } // resetLeases resets all leases in the lease database. -func (s *Server) resetLeases() (err error) { +func (s *server) resetLeases() (err error) { err = s.srv4.ResetLeases(nil) if err != nil { return err @@ -280,7 +265,7 @@ func (s *Server) resetLeases() (err error) { } // server calls this function after DB is updated -func (s *Server) onNotify(flags uint32) { +func (s *server) onNotify(flags uint32) { if flags == LeaseChangedDBStore { err := s.dbStore() if err != nil { @@ -294,11 +279,11 @@ func (s *Server) onNotify(flags uint32) { } // SetOnLeaseChanged - set callback -func (s *Server) SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT) { +func (s *server) SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT) { s.onLeaseChanged = append(s.onLeaseChanged, onLeaseChanged) } -func (s *Server) notify(flags int) { +func (s *server) notify(flags int) { if len(s.onLeaseChanged) == 0 { return } @@ -309,7 +294,7 @@ func (s *Server) notify(flags int) { } // WriteDiskConfig - write configuration -func (s *Server) WriteDiskConfig(c *ServerConfig) { +func (s *server) WriteDiskConfig(c *ServerConfig) { c.Enabled = s.conf.Enabled c.InterfaceName = s.conf.InterfaceName c.LocalDomainName = s.conf.LocalDomainName @@ -318,7 +303,7 @@ func (s *Server) WriteDiskConfig(c *ServerConfig) { } // Start will listen on port 67 and serve DHCP requests. -func (s *Server) Start() (err error) { +func (s *server) Start() (err error) { err = s.srv4.Start() if err != nil { return err @@ -333,7 +318,7 @@ func (s *Server) Start() (err error) { } // Stop closes the listening UDP socket -func (s *Server) Stop() (err error) { +func (s *server) Stop() (err error) { err = s.srv4.Stop() if err != nil { return err @@ -349,12 +334,12 @@ func (s *Server) Stop() (err error) { // Leases returns the list of active IPv4 and IPv6 DHCP leases. It's safe for // concurrent use. -func (s *Server) Leases(flags GetLeasesFlags) (leases []*Lease) { +func (s *server) Leases(flags GetLeasesFlags) (leases []*Lease) { return append(s.srv4.GetLeases(flags), s.srv6.GetLeases(flags)...) } // FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases -func (s *Server) FindMACbyIP(ip net.IP) net.HardwareAddr { +func (s *server) FindMACbyIP(ip net.IP) net.HardwareAddr { if ip.To4() != nil { return s.srv4.FindMACbyIP(ip) } @@ -362,6 +347,6 @@ func (s *Server) FindMACbyIP(ip net.IP) net.HardwareAddr { } // AddStaticLease - add static v4 lease -func (s *Server) AddStaticLease(l *Lease) error { +func (s *server) AddStaticLease(l *Lease) error { return s.srv4.AddStaticLease(l) } diff --git a/internal/dhcpd/dhcpd_test.go b/internal/dhcpd/dhcpd_test.go index b704cbb4bff..4c7f5c62513 100644 --- a/internal/dhcpd/dhcpd_test.go +++ b/internal/dhcpd/dhcpd_test.go @@ -26,7 +26,7 @@ func testNotify(flags uint32) { // Leases database store/load. func TestDB(t *testing.T) { var err error - s := Server{ + s := server{ conf: &ServerConfig{ DBFilePath: dbFilename, }, diff --git a/internal/dhcpd/http_others.go b/internal/dhcpd/http_others.go index ed99c35ee37..a338a589213 100644 --- a/internal/dhcpd/http_others.go +++ b/internal/dhcpd/http_others.go @@ -66,7 +66,7 @@ type dhcpStatusResponse struct { Enabled bool `json:"enabled"` } -func (s *Server) handleDHCPStatus(w http.ResponseWriter, r *http.Request) { +func (s *server) handleDHCPStatus(w http.ResponseWriter, r *http.Request) { status := &dhcpStatusResponse{ Enabled: s.conf.Enabled, IfaceName: s.conf.InterfaceName, @@ -94,7 +94,7 @@ func (s *Server) handleDHCPStatus(w http.ResponseWriter, r *http.Request) { } } -func (s *Server) enableDHCP(ifaceName string) (code int, err error) { +func (s *server) enableDHCP(ifaceName string) (code int, err error) { var hasStaticIP bool hasStaticIP, err = aghnet.IfaceHasStaticIP(ifaceName) if err != nil { @@ -150,7 +150,7 @@ type dhcpServerConfigJSON struct { Enabled aghalg.NullBool `json:"enabled"` } -func (s *Server) handleDHCPSetConfigV4( +func (s *server) handleDHCPSetConfigV4( conf *dhcpServerConfigJSON, ) (srv4 DHCPServer, enabled bool, err error) { if conf.V4 == nil { @@ -177,7 +177,7 @@ func (s *Server) handleDHCPSetConfigV4( return srv4, enabled, err } -func (s *Server) handleDHCPSetConfigV6( +func (s *server) handleDHCPSetConfigV6( conf *dhcpServerConfigJSON, ) (srv6 DHCPServer, enabled bool, err error) { if conf.V6 == nil { @@ -206,7 +206,7 @@ func (s *Server) handleDHCPSetConfigV6( return srv6, enabled, err } -func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { +func (s *server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { conf := &dhcpServerConfigJSON{} conf.Enabled = aghalg.BoolToNullBool(s.conf.Enabled) conf.InterfaceName = s.conf.InterfaceName @@ -288,7 +288,7 @@ type netInterfaceJSON struct { Addrs6 []net.IP `json:"ipv6_addresses"` } -func (s *Server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { +func (s *server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { response := map[string]netInterfaceJSON{} ifaces, err := net.Interfaces() @@ -411,7 +411,7 @@ type dhcpSearchResult struct { // . Search for another DHCP server running // . Check if a static IP is configured for the network interface // Respond with results -func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) { +func (s *server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) { // This use of ReadAll is safe, because request's body is now limited. body, err := io.ReadAll(r.Body) if err != nil { @@ -483,7 +483,7 @@ func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque } } -func (s *Server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request) { +func (s *server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request) { l := &Lease{} err := json.NewDecoder(r.Body).Decode(l) if err != nil { @@ -515,7 +515,7 @@ func (s *Server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request } } -func (s *Server) handleDHCPRemoveStaticLease(w http.ResponseWriter, r *http.Request) { +func (s *server) handleDHCPRemoveStaticLease(w http.ResponseWriter, r *http.Request) { l := &Lease{} err := json.NewDecoder(r.Body).Decode(l) if err != nil { @@ -552,7 +552,7 @@ func (s *Server) handleDHCPRemoveStaticLease(w http.ResponseWriter, r *http.Requ } } -func (s *Server) handleReset(w http.ResponseWriter, r *http.Request) { +func (s *server) handleReset(w http.ResponseWriter, r *http.Request) { err := s.Stop() if err != nil { aghhttp.Error(r, w, http.StatusInternalServerError, "stopping dhcp: %s", err) @@ -592,7 +592,7 @@ func (s *Server) handleReset(w http.ResponseWriter, r *http.Request) { s.conf.ConfigModified() } -func (s *Server) handleResetLeases(w http.ResponseWriter, r *http.Request) { +func (s *server) handleResetLeases(w http.ResponseWriter, r *http.Request) { err := s.resetLeases() if err != nil { msg := "resetting leases: %s" @@ -602,7 +602,11 @@ func (s *Server) handleResetLeases(w http.ResponseWriter, r *http.Request) { } } -func (s *Server) registerHandlers() { +func (s *server) registerHandlers() { + if s.conf.HTTPRegister == nil { + return + } + s.conf.HTTPRegister(http.MethodGet, "/control/dhcp/status", s.handleDHCPStatus) s.conf.HTTPRegister(http.MethodGet, "/control/dhcp/interfaces", s.handleDHCPInterfaces) s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/set_config", s.handleDHCPSetConfig) diff --git a/internal/dhcpd/http_windows.go b/internal/dhcpd/http_windows.go index 8241e419103..316fca31fd8 100644 --- a/internal/dhcpd/http_windows.go +++ b/internal/dhcpd/http_windows.go @@ -24,7 +24,7 @@ type jsonError struct { // // TODO(a.garipov): Either take the logger from the server after we've // refactored logging or make this not a method of *Server. -func (s *Server) notImplemented(w http.ResponseWriter, r *http.Request) { +func (s *server) notImplemented(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusNotImplemented) @@ -43,7 +43,7 @@ func (s *Server) notImplemented(w http.ResponseWriter, r *http.Request) { // initialize a DHCP server on Windows, but there are currently too many // interconnected parts--such as HTTP handlers and frontend--to make that work // properly. -func (s *Server) registerHandlers() { +func (s *server) registerHandlers() { s.conf.HTTPRegister(http.MethodGet, "/control/dhcp/status", s.notImplemented) s.conf.HTTPRegister(http.MethodGet, "/control/dhcp/interfaces", s.notImplemented) s.conf.HTTPRegister(http.MethodPost, "/control/dhcp/set_config", s.notImplemented) diff --git a/internal/dhcpd/http_windows_test.go b/internal/dhcpd/http_windows_test.go index 22c26da01a2..12b9d7d9f23 100644 --- a/internal/dhcpd/http_windows_test.go +++ b/internal/dhcpd/http_windows_test.go @@ -14,7 +14,7 @@ import ( ) func TestServer_notImplemented(t *testing.T) { - s := &Server{} + s := &server{} w := httptest.NewRecorder() r, err := http.NewRequest(http.MethodGet, "/unsupported", nil) diff --git a/internal/dhcpd/v46.go b/internal/dhcpd/v46.go deleted file mode 100644 index 5e44eca336a..00000000000 --- a/internal/dhcpd/v46.go +++ /dev/null @@ -1,12 +0,0 @@ -package dhcpd - -import ( - "time" -) - -// Currently used defaults for ifaceDNSAddrs. -const ( - defaultMaxAttempts int = 10 - - defaultBackoff time.Duration = 500 * time.Millisecond -) diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go index 33be1e83f9f..49ae087be22 100644 --- a/internal/dnsforward/dnsforward.go +++ b/internal/dnsforward/dnsforward.go @@ -58,10 +58,10 @@ type hostToIPTable = map[string]net.IP // // The zero Server is empty and ready for use. type Server struct { - dnsProxy *proxy.Proxy // DNS proxy instance - dnsFilter *filtering.DNSFilter // DNS filter instance - dhcpServer dhcpd.ServerInterface // DHCP server instance (optional) - queryLog querylog.QueryLog // Query log instance + dnsProxy *proxy.Proxy // DNS proxy instance + dnsFilter *filtering.DNSFilter // DNS filter instance + dhcpServer dhcpd.Interface // DHCP server instance (optional) + queryLog querylog.QueryLog // Query log instance stats stats.Interface access *accessCtx @@ -110,7 +110,7 @@ type DNSCreateParams struct { DNSFilter *filtering.DNSFilter Stats stats.Interface QueryLog querylog.QueryLog - DHCPServer dhcpd.ServerInterface + DHCPServer dhcpd.Interface PrivateNets netutil.SubnetSet Anonymizer *aghnet.IPMut LocalDomain string diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go index a76d5a41844..11a46ed75c4 100644 --- a/internal/dnsforward/dnsforward_test.go +++ b/internal/dnsforward/dnsforward_test.go @@ -1005,11 +1005,19 @@ func publicKey(priv any) any { } } +// testDHCP is a mock dhcpd.Interface implementation. +// +// TODO(e.burkov): Create aghtest.DHCP when the API stabilized. type testDHCP struct{} -func (d *testDHCP) Enabled() (ok bool) { return true } +func (*testDHCP) Start() (err error) { return nil } +func (*testDHCP) Stop() (err error) { return nil } +func (*testDHCP) FindMACbyIP(net.IP) (mac net.HardwareAddr) { return nil } +func (*testDHCP) Enabled() (ok bool) { return true } +func (*testDHCP) SetOnLeaseChanged(dhcpd.OnLeaseChangedT) {} +func (*testDHCP) WriteDiskConfig(*dhcpd.ServerConfig) {} -func (d *testDHCP) Leases(flags dhcpd.GetLeasesFlags) (leases []*dhcpd.Lease) { +func (*testDHCP) Leases(flags dhcpd.GetLeasesFlags) (leases []*dhcpd.Lease) { return []*dhcpd.Lease{{ IP: net.IP{192, 168, 12, 34}, HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, @@ -1017,8 +1025,6 @@ func (d *testDHCP) Leases(flags dhcpd.GetLeasesFlags) (leases []*dhcpd.Lease) { }} } -func (d *testDHCP) SetOnLeaseChanged(onLeaseChanged dhcpd.OnLeaseChangedT) {} - func TestPTRResponseFromDHCPLeases(t *testing.T) { const localDomain = "lan" diff --git a/internal/home/clients.go b/internal/home/clients.go index e50b7904bc4..7396e8c68de 100644 --- a/internal/home/clients.go +++ b/internal/home/clients.go @@ -126,7 +126,7 @@ type clientsContainer struct { allTags *stringutil.Set // dhcpServer is used for looking up clients IP addresses by MAC addresses - dhcpServer *dhcpd.Server + dhcpServer dhcpd.Interface // dnsServer is used for checking clients IP status access list status dnsServer *dnsforward.Server @@ -146,7 +146,7 @@ type clientsContainer struct { // Note: this function must be called only once func (clients *clientsContainer) Init( objects []*clientObject, - dhcpServer *dhcpd.Server, + dhcpServer dhcpd.Interface, etcHosts *aghnet.HostsContainer, arpdb aghnet.ARPDB, ) { diff --git a/internal/home/clients_test.go b/internal/home/clients_test.go index 8afd56216ba..5b4ccdd31cf 100644 --- a/internal/home/clients_test.go +++ b/internal/home/clients_test.go @@ -279,8 +279,6 @@ func TestClientsAddExisting(t *testing.T) { t.Skip("skipping dhcp test on windows") } - var err error - ip := net.IP{1, 2, 3, 4} // First, init a DHCP server with a single static lease. @@ -296,13 +294,15 @@ func TestClientsAddExisting(t *testing.T) { }, } - clients.dhcpServer, err = dhcpd.Create(config) + dhcpServer, err := dhcpd.Create(config) require.NoError(t, err) testutil.CleanupAndRequireSuccess(t, func() (err error) { return os.Remove("leases.db") }) - err = clients.dhcpServer.AddStaticLease(&dhcpd.Lease{ + clients.dhcpServer = dhcpServer + + err = dhcpServer.AddStaticLease(&dhcpd.Lease{ HWAddr: net.HardwareAddr{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}, IP: ip, Hostname: "testhost", diff --git a/internal/home/home.go b/internal/home/home.go index a84753fdcc2..4b200bc17aa 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -53,7 +53,7 @@ type homeContext struct { rdns *RDNS // rDNS module whois *WHOIS // WHOIS module dnsFilter *filtering.DNSFilter // DNS filtering module - dhcpServer *dhcpd.Server // DHCP module + dhcpServer dhcpd.Interface // DHCP module auth *Auth // HTTP authentication module filters Filtering // DNS filtering module web *Web // Web (HTTP, HTTPS) module @@ -641,14 +641,9 @@ func configureLogger(args options) { log.Fatalf("cannot initialize syslog: %s", err) } } else { - logFilePath := filepath.Join(Context.workDir, ls.File) - if filepath.IsAbs(ls.File) { - logFilePath = ls.File - } - - _, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644) - if err != nil { - log.Fatalf("cannot create a log file: %s", err) + logFilePath := ls.File + if !filepath.IsAbs(logFilePath) { + logFilePath = filepath.Join(Context.workDir, logFilePath) } log.SetOutput(&lumberjack.Logger{