diff --git a/Dockerfile b/Dockerfile index d4d8942..79fd5e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,9 @@ RUN apk add --update --no-cache \ util-linux \ && rm -rf /var/cache/apk/* +# Create log directory +RUN mkdir -p /var/log/network-go + COPY --from=builder /build/network-go /usr/local/bin/network-go -CMD ["/usr/local/bin/network-go"] \ No newline at end of file +CMD ["/usr/local/bin/network-go"] diff --git a/network-go/docker/docker.go b/network-go/docker/docker.go index 1ff7b8a..7824ce2 100644 --- a/network-go/docker/docker.go +++ b/network-go/docker/docker.go @@ -16,6 +16,7 @@ import ( "github.com/docker/docker/client" "firewall_containers/network-go/config" + "firewall_containers/network-go/logger" ) // DockerAPI defines the interface for Docker operations, enabling mock implementations for testing @@ -42,20 +43,29 @@ var _ DockerAPI = (*Client)(nil) // NewClient creates a new Docker client func NewClient() (*Client, error) { + logger.Info("DOCKER: creating Docker client (using DOCKER_HOST env)") cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) if err != nil { return nil, fmt.Errorf("failed to create Docker client: %w", err) } + ping, err := cli.Ping(context.Background()) + if err != nil { + logger.Warn("DOCKER: Docker daemon ping failed: %v", err) + } else { + logger.Info("DOCKER: connected to Docker daemon (API version=%s, OS=%s)", ping.APIVersion, ping.OSType) + } return &Client{cli: cli}, nil } // Close closes the Docker client func (c *Client) Close() error { + logger.Debug("DOCKER: closing Docker client") return c.cli.Close() } // EnsureNetwork creates a Docker network if it does not already exist func (c *Client) EnsureNetwork(ctx context.Context, netCfg config.NetworkConfig) error { + logger.Debug("DOCKER: checking if network %q exists", netCfg.NetworkName) existingNetworks, err := c.cli.NetworkList(ctx, network.ListOptions{ Filters: filters.NewArgs(filters.Arg("name", netCfg.NetworkName)), }) @@ -65,6 +75,7 @@ func (c *Client) EnsureNetwork(ctx context.Context, netCfg config.NetworkConfig) for _, n := range existingNetworks { if n.Name == netCfg.NetworkName { + logger.Debug("DOCKER: network %q already exists", netCfg.NetworkName) return nil } } @@ -95,26 +106,30 @@ func (c *Client) EnsureNetwork(ctx context.Context, netCfg config.NetworkConfig) Attachable: true, } + logger.Info("DOCKER: creating network %q (subnet=%s, gateway=%s)", netCfg.NetworkName, netCfg.Subnet, netCfg.Gateway) resp, err := c.cli.NetworkCreate(ctx, netCfg.NetworkName, createOpts) if err != nil { return fmt.Errorf("failed to create network %s: %w", netCfg.NetworkName, err) } - _ = resp + logger.Info("DOCKER: network %q created (ID=%s)", netCfg.NetworkName, resp.ID) return nil } // RemoveNetwork removes a Docker network func (c *Client) RemoveNetwork(ctx context.Context, networkName string) error { + logger.Info("DOCKER: removing network %q", networkName) err := c.cli.NetworkRemove(ctx, networkName) if err != nil { return fmt.Errorf("failed to remove network %s: %w", networkName, err) } + logger.Info("DOCKER: network %q removed", networkName) return nil } // ConnectContainer connects a container to a network with a specific IP func (c *Client) ConnectContainer(ctx context.Context, containerName, networkName, ip string) error { + logger.Info("DOCKER: connecting container %q to network %q with IP %s", containerName, networkName, ip) endpointSettings := &network.EndpointSettings{ IPAMConfig: &network.EndpointIPAMConfig{ IPv4Address: ip, @@ -125,33 +140,41 @@ func (c *Client) ConnectContainer(ctx context.Context, containerName, networkNam if err != nil { // "endpoint with name ... already exists" is expected on re-reconciliation if strings.Contains(err.Error(), "already exists") { + logger.Info("DOCKER: container %q already connected to network %q", containerName, networkName) return nil } return fmt.Errorf("failed to connect container %s to network %s: %w", containerName, networkName, err) } + logger.Info("DOCKER: container %q connected to network %q with IP %s", containerName, networkName, ip) return nil } // DisconnectContainer disconnects a container from a network func (c *Client) DisconnectContainer(ctx context.Context, containerName, networkName string) error { + logger.Info("DOCKER: disconnecting container %q from network %q", containerName, networkName) err := c.cli.NetworkDisconnect(ctx, networkName, containerName, true) if err != nil { return fmt.Errorf("failed to disconnect container %s from network %s: %w", containerName, networkName, err) } + logger.Info("DOCKER: container %q disconnected from network %q", containerName, networkName) return nil } // InspectContainer returns the container's details func (c *Client) InspectContainer(ctx context.Context, containerName string) (*types.ContainerJSON, error) { + logger.Debug("DOCKER: inspecting container %q", containerName) container, err := c.cli.ContainerInspect(ctx, containerName) if err != nil { return nil, fmt.Errorf("failed to inspect container %s: %w", containerName, err) } + logger.Debug("DOCKER: container %q inspected (status=%s, PID=%d)", + containerName, container.State.Status, container.State.Pid) return &container, nil } // WaitForContainerRunning waits until a container is in running state, with a timeout func (c *Client) WaitForContainerRunning(ctx context.Context, containerName string, timeout time.Duration) error { + logger.Info("DOCKER: waiting for container %q to be running (timeout=%s)", containerName, timeout) ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() @@ -165,17 +188,21 @@ func (c *Client) WaitForContainerRunning(ctx context.Context, containerName stri case <-ticker.C: container, err := c.cli.ContainerInspect(ctx, containerName) if err != nil { + logger.Debug("DOCKER: container %q inspect failed (not ready yet): %v", containerName, err) continue } if container.State != nil && container.State.Running { + logger.Info("DOCKER: container %q is now running", containerName) return nil } + logger.Debug("DOCKER: container %q status=%s, not running yet", containerName, container.State.Status) } } } // GetContainerPID returns the PID of a container for nsenter operations func (c *Client) GetContainerPID(ctx context.Context, containerName string) (int, error) { + logger.Info("DOCKER: getting PID for container %q", containerName) cont, err := c.cli.ContainerInspect(ctx, containerName) if err != nil { return 0, fmt.Errorf("failed to inspect container %s: %w", containerName, err) @@ -183,6 +210,7 @@ func (c *Client) GetContainerPID(ctx context.Context, containerName string) (int if cont.State == nil || !cont.State.Running { return 0, fmt.Errorf("container %s is not running", containerName) } + logger.Info("DOCKER: container %q PID=%d", containerName, cont.State.Pid) return cont.State.Pid, nil } @@ -193,6 +221,7 @@ func (c *Client) AddRouteInContainer(ctx context.Context, containerName, network return fmt.Errorf("failed to get PID for container %s: %w", containerName, err) } + logger.Info("DOCKER: adding route in container %q (PID=%d): %s via %s", containerName, pid, network, gateway) args := []string{ "-t", fmt.Sprintf("%d", pid), "-n", "--", @@ -204,6 +233,8 @@ func (c *Client) AddRouteInContainer(ctx context.Context, containerName, network if err != nil { return fmt.Errorf("failed to add route in container %s: %w\noutput: %s", containerName, err, string(output)) } + logger.Info("DOCKER: route added in container %q: %s via %s", containerName, network, gateway) + logger.Debug("DOCKER: nsenter output: %s", strings.TrimSpace(string(output))) return nil } @@ -211,6 +242,8 @@ func (c *Client) AddRouteInContainer(ctx context.Context, containerName, network // First tries exact name match, then exact selector, then prefix matching // (matching the old shell script's grep $D"-" behavior). func (c *Client) FindContainerName(ctx context.Context, name, selector string) (string, error) { + logger.Info("DOCKER: finding container: name=%q selector=%q", name, selector) + // Try exact name match using ContainerList with a name filter // Docker API name filter does an exact match by default containers, err := c.cli.ContainerList(ctx, container.ListOptions{ @@ -221,11 +254,13 @@ func (c *Client) FindContainerName(ctx context.Context, name, selector string) ( }) if err == nil && len(containers) > 0 { cName := strings.TrimPrefix(containers[0].Names[0], "/") + logger.Info("DOCKER: found container by exact name match: %q", cName) return cName, nil } // Try exact selector match if selector != "" && selector != name { + logger.Debug("DOCKER: trying selector match: %q", selector) containers, err = c.cli.ContainerList(ctx, container.ListOptions{ Filters: filters.NewArgs( filters.Arg("name", "^/?"+regexp.QuoteMeta(selector)+"$"), @@ -234,6 +269,7 @@ func (c *Client) FindContainerName(ctx context.Context, name, selector string) ( }) if err == nil && len(containers) > 0 { cName := strings.TrimPrefix(containers[0].Names[0], "/") + logger.Info("DOCKER: found container by selector match: %q", cName) return cName, nil } } @@ -249,6 +285,7 @@ func (c *Client) FindContainerName(ctx context.Context, name, selector string) ( if strings.Contains(candidate, "-") { prefix = candidate[:strings.Index(candidate, "-")] } + logger.Debug("DOCKER: trying prefix match: candidate=%q prefix=%q", candidate, prefix) containers, err = c.cli.ContainerList(ctx, container.ListOptions{ Filters: filters.NewArgs( @@ -257,16 +294,18 @@ func (c *Client) FindContainerName(ctx context.Context, name, selector string) ( ), }) if err != nil { + logger.Debug("DOCKER: prefix list failed: %v", err) continue } for _, c := range containers { for _, cName := range c.Names { cName = strings.TrimPrefix(cName, "/") + logger.Info("DOCKER: found container by prefix match %q-: %q", prefix, cName) return cName, nil } } } return "", fmt.Errorf("no running container found matching name=%q selector=%q", name, selector) -} +} \ No newline at end of file diff --git a/network-go/firewall/firewall.go b/network-go/firewall/firewall.go index 41e3fb7..0b33b61 100644 --- a/network-go/firewall/firewall.go +++ b/network-go/firewall/firewall.go @@ -2,7 +2,6 @@ package firewall import ( "context" - "log" "net" "strconv" "time" @@ -10,6 +9,7 @@ import ( "firewall_containers/network-go/config" "firewall_containers/network-go/docker" "firewall_containers/network-go/iptables" + "firewall_containers/network-go/logger" "firewall_containers/network-go/resolver" ) @@ -33,16 +33,18 @@ func NewOrchestrator(dockerClient docker.DockerAPI, iptablesMgr iptables.IPTable // ReconcileAll runs the full reconciliation: networks, container connections, and firewall rules func (o *Orchestrator) ReconcileAll(ctx context.Context, cfg *config.NetworksConfig) { - log.Println("FIREWALL: starting full reconciliation") + logger.Info("FIREWALL: starting full reconciliation") + logger.Debug("FIREWALL: config has %d networks, %d IPs, %d policies", + len(cfg.Networks), len(cfg.IPs), len(cfg.Policies)) // Update resolver with latest config o.resolver.SetConfig(cfg) // Step 0: Enable IP forwarding (may fail in containers with read-only fs) if err := o.iptablesMgr.EnsureIPForward(); err != nil { - log.Printf("FIREWALL: WARNING could not enable ip_forward: %v", err) + logger.Warn("FIREWALL: could not enable ip_forward: %v", err) } else { - log.Println("FIREWALL: IP forwarding enabled") + logger.Info("FIREWALL: IP forwarding enabled") } // Step 1: Ensure all defined networks exist @@ -54,15 +56,18 @@ func (o *Orchestrator) ReconcileAll(ctx context.Context, cfg *config.NetworksCon // Step 3: Reconcile firewall policies o.reconcilePolicies(ctx, cfg) - log.Println("FIREWALL: full reconciliation completed") + logger.Info("FIREWALL: full reconciliation completed") } // reconcileNetworks creates Docker networks if they don't exist func (o *Orchestrator) reconcileNetworks(ctx context.Context, cfg *config.NetworksConfig) { for name, netCfg := range cfg.Networks { - log.Printf("FIREWALL: ensuring network %s (%s, subnet=%s, gateway=%s)", name, netCfg.NetworkName, netCfg.Subnet, netCfg.Gateway) + logger.Info("FIREWALL: ensuring network %s (name=%s, subnet=%s, gateway=%s)", + name, netCfg.NetworkName, netCfg.Subnet, netCfg.Gateway) if err := o.dockerClient.EnsureNetwork(ctx, netCfg); err != nil { - log.Printf("FIREWALL: ERROR ensuring network %s: %v", name, err) + logger.Error("FIREWALL: failed to ensure network %s: %v", name, err) + } else { + logger.Debug("FIREWALL: network %s ready", name) } } } @@ -72,28 +77,46 @@ func (o *Orchestrator) reconcileIPs(ctx context.Context, cfg *config.NetworksCon for ipStr, ipCfg := range cfg.IPs { networkName := findNetworkForIP(cfg, ipStr) if networkName == "" { - log.Printf("FIREWALL: WARNING no network found for IP %s (container=%s)", ipStr, ipCfg.ContainerName) + logger.Warn("FIREWALL: no network found for IP %s (container=%s, selector=%s)", + ipStr, ipCfg.ContainerName, ipCfg.Selector) continue } + logger.Info("FIREWALL: resolving container name for IP %s (container=%s, selector=%s)", + ipStr, ipCfg.ContainerName, ipCfg.Selector) + // Resolve the actual container name, with fallback to fuzzy matching // (old shell script behavior: docker ps | grep $D"-") containerName, err := o.dockerClient.FindContainerName(ctx, ipCfg.ContainerName, ipCfg.Selector) if err != nil { - log.Printf("FIREWALL: WARNING container %s (selector=%s) not found: %v, trying connection anyway", ipCfg.ContainerName, ipCfg.Selector, err) + logger.Warn("FIREWALL: container %s (selector=%s) not found: %v, using config name anyway", + ipCfg.ContainerName, ipCfg.Selector, err) containerName = ipCfg.ContainerName + } else if containerName != ipCfg.ContainerName { + logger.Info("FIREWALL: container resolved: config_name=%s -> actual=%s", + ipCfg.ContainerName, containerName) } - log.Printf("FIREWALL: connecting container %s to network %s with IP %s", containerName, networkName, ipStr) + logger.Info("FIREWALL: connecting container %s to network %s with IP %s", + containerName, networkName, ipStr) waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second) - if err := o.dockerClient.WaitForContainerRunning(waitCtx, containerName, 10*time.Second); err != nil { - log.Printf("FIREWALL: WARNING container %s not running yet: %v, connecting anyway", containerName, err) - } + waitErr := o.dockerClient.WaitForContainerRunning(waitCtx, containerName, 10*time.Second) cancel() + if waitErr != nil { + logger.Warn("FIREWALL: container %s not running yet: %v, connecting anyway", + containerName, waitErr) + } else { + logger.Debug("FIREWALL: container %s is running", containerName) + } + if err := o.dockerClient.ConnectContainer(ctx, containerName, networkName, ipStr); err != nil { - log.Printf("FIREWALL: ERROR connecting container %s to %s: %v", containerName, networkName, err) + logger.Error("FIREWALL: failed to connect container %s to %s: %v", + containerName, networkName, err) + } else { + logger.Info("FIREWALL: container %s connected to network %s with IP %s", + containerName, networkName, ipStr) } } } @@ -101,7 +124,10 @@ func (o *Orchestrator) reconcileIPs(ctx context.Context, cfg *config.NetworksCon // reconcilePolicies translates PolicyConfig entries into iptables rules func (o *Orchestrator) reconcilePolicies(ctx context.Context, cfg *config.NetworksConfig) { for i, policy := range cfg.Policies { - log.Printf("FIREWALL: processing policy[%d]", i) + logger.Info("FIREWALL: processing policy[%d]", i) + logger.Debug("FIREWALL: policy[%d] details: service=%s container=%s selector=%s from=%s to=%s port=%d proto=%s nat=%s iface=%s", + i, policy.ServiceName, policy.ContainerName, policy.Selector, + policy.From, policy.To, policy.Port, policy.Proto, policy.Nat, policy.Iface) proto := policy.Proto if proto == "" { @@ -122,6 +148,7 @@ func (o *Orchestrator) reconcilePolicies(ctx context.Context, cfg *config.Networ comment = policy.ServiceName } } + logger.Debug("FIREWALL: policy[%d] comment=%q", i, comment) // CASE 1: Rule with "from" field — this is a FORWARD ACCEPT rule if policy.From != "" { @@ -136,7 +163,7 @@ func (o *Orchestrator) reconcilePolicies(ctx context.Context, cfg *config.Networ } // Unhandled pattern - log.Printf("FIREWALL: policy[%d] unhandled pattern — service=%s container=%s selector=%s from=%s to=%s port=%d proto=%s nat=%s", + logger.Warn("FIREWALL: policy[%d] unhandled pattern — service=%s container=%s selector=%s from=%s to=%s port=%d proto=%s nat=%s", i, policy.ServiceName, policy.ContainerName, policy.Selector, policy.From, policy.To, policy.Port, policy.Proto, policy.Nat) } } @@ -147,32 +174,43 @@ func (o *Orchestrator) applyForwardRule(ctx context.Context, cfg *config.Network if policy.To != "" { targetIP = o.resolveIP(policy.To) } + logger.Info("FIREWALL: forward rule: from=%q (IP=%s) to=%q (IP=%s) proto=%s port=%s", + policy.From, sourceIP, policy.To, targetIP, proto, port) // Determine the chain: use DOCKER-USER (iptables-legacy) or FORWARD chain := "FORWARD" if o.iptablesMgr.Binary() == "/usr/sbin/iptables-legacy" { chain = "DOCKER-USER" } + logger.Debug("FIREWALL: using iptables chain=%s (binary=%s)", chain, o.iptablesMgr.Binary()) // Ensure established/related rule exists at the top if err := o.iptablesMgr.EnsureEstablishedRelated(chain); err != nil { - log.Printf("FIREWALL: ERROR ensuring established/related rule in %s: %v", chain, err) + logger.Error("FIREWALL: failed to ensure established/related rule in %s: %v", chain, err) + } else { + logger.Debug("FIREWALL: established/related rule ensured in %s", chain) } // Insert the FORWARD ACCEPT rule if err := o.iptablesMgr.InsertForwardAccept(chain, sourceIP, targetIP, proto, "", port, comment); err != nil { - log.Printf("FIREWALL: ERROR inserting FORWARD ACCEPT rule: %v", err) + logger.Error("FIREWALL: failed to insert FORWARD ACCEPT rule in %s: %v", chain, err) + } else { + logger.Info("FIREWALL: FORWARD ACCEPT rule inserted: chain=%s src=%s dst=%s proto=%s port=%s comment=%q", + chain, sourceIP, targetIP, proto, port, comment) } } func (o *Orchestrator) applyNATRule(ctx context.Context, cfg *config.NetworksConfig, policy config.PolicyConfig, proto, port, comment string) { to := policy.To + logger.Info("FIREWALL: NAT rule: to=%s proto=%s port=%s nat=%s iface=%s", + to, proto, port, policy.Nat, policy.Iface) // Resolve "to" as target IP targetIP := o.resolveIP(to) + logger.Debug("FIREWALL: resolved target %q -> IP=%q", to, targetIP) if targetIP == "" { - log.Printf("FIREWALL: WARNING cannot resolve target %s for nat policy", to) + logger.Warn("FIREWALL: cannot resolve target %s for nat policy", to) return } @@ -185,26 +223,34 @@ func (o *Orchestrator) applyNATRule(ctx context.Context, cfg *config.NetworksCon if selector == "" { selector = policy.Name } + logger.Debug("FIREWALL: DNAT selector=%s", selector) // Try to insert rules inside the container namespace via nsenter usedContainer := false if selector != "" { pid, err := o.dockerClient.GetContainerPID(ctx, selector) if err == nil { + logger.Info("FIREWALL: inserting DNAT rule in container %s (PID=%d)", selector, pid) if err := o.iptablesMgr.InsertPreroutingRuleInContainer(pid, "0.0.0.0/0", proto, port, targetIP, port, comment); err != nil { - log.Printf("FIREWALL: ERROR inserting container PREROUTING rule: %v", err) + logger.Error("FIREWALL: failed to insert container PREROUTING rule: %v", err) } else { + logger.Info("FIREWALL: DNAT rule inserted in container %s: target=%s proto=%s port=%s", + selector, targetIP, proto, port) usedContainer = true } } else { - log.Printf("FIREWALL: WARNING cannot get PID for container %s: %v, trying host rules", selector, err) + logger.Warn("FIREWALL: cannot get PID for container %s: %v, trying host rules", selector, err) } } // Fall back to host-level PREROUTING if container not used if !usedContainer && policy.Iface != "" { + logger.Info("FIREWALL: inserting host-level DNAT rule on interface %s", policy.Iface) if err := o.iptablesMgr.InsertPreroutingRuleOnInterface(policy.Iface, proto, port, targetIP, port, comment); err != nil { - log.Printf("FIREWALL: ERROR inserting interface PREROUTING rule: %v", err) + logger.Error("FIREWALL: failed to insert interface PREROUTING rule on %s: %v", policy.Iface, err) + } else { + logger.Info("FIREWALL: host DNAT rule inserted: iface=%s target=%s proto=%s port=%s", + policy.Iface, targetIP, proto, port) } } } @@ -214,15 +260,19 @@ func (o *Orchestrator) applyNATRule(ctx context.Context, cfg *config.NetworksCon func (o *Orchestrator) resolveIP(name string) string { // If it's already an IP, return it as CIDR if config.IsIP(name) { - return config.ToCIDR(name) + result := config.ToCIDR(name) + logger.Debug("FIREWALL: resolveIP(%q): direct IP -> %s", name, result) + return result } // Use the resolver which looks up from networks.json ips := o.resolver.Resolve(name) if len(ips) > 0 { + logger.Debug("FIREWALL: resolveIP(%q): resolved -> %s", name, ips[0]) return ips[0] } + logger.Debug("FIREWALL: resolveIP(%q): not found", name) return "" } diff --git a/network-go/iptables/iptables.go b/network-go/iptables/iptables.go index f3ffe26..7866efb 100644 --- a/network-go/iptables/iptables.go +++ b/network-go/iptables/iptables.go @@ -4,6 +4,8 @@ import ( "fmt" "os/exec" "strings" + + "firewall_containers/network-go/logger" ) // IPTablesAPI defines the interface for iptables operations, enabling mock implementations for testing @@ -40,13 +42,16 @@ func NewManager(debug bool) *Manager { // detectBinary checks if iptables-legacy is available (matching shell script logic) func (m *Manager) detectBinary() { + logger.Info("IPTABLES: detecting iptables binary") cmd := exec.Command("/usr/sbin/iptables-legacy", "-L") output, err := cmd.CombinedOutput() if err == nil && strings.Contains(string(output), "DOCKER-USER") { m.binary = "/usr/sbin/iptables-legacy" + logger.Info("IPTABLES: detected iptables-legacy (DOCKER-USER chain present)") return } m.binary = "/usr/sbin/iptables" + logger.Info("IPTABLES: using default iptables binary") } // Binary returns the detected iptables binary path @@ -56,23 +61,29 @@ func (m *Manager) Binary() string { // run executes an iptables command on the host func (m *Manager) run(args ...string) error { + cmdStr := m.binary + " " + strings.Join(args, " ") + logger.Info("IPTABLES: executing: %s", cmdStr) if m.debug { - fmt.Printf("[IPTABLES DEBUG] %s %s\n", m.binary, strings.Join(args, " ")) + logger.Debug("IPTABLES DEBUG: %s %s", m.binary, strings.Join(args, " ")) } cmd := exec.Command(m.binary, args...) output, err := cmd.CombinedOutput() if err != nil { + logger.Error("IPTABLES: command failed: %s\noutput: %s", cmdStr, strings.TrimSpace(string(output))) return fmt.Errorf("iptables %s failed: %w\noutput: %s", strings.Join(args, " "), err, string(output)) } + logger.Debug("IPTABLES: command succeeded: %s", cmdStr) + if len(output) > 0 { + logger.Debug("IPTABLES: output: %s", strings.TrimSpace(string(output))) + } return nil } // runInContainer executes an iptables command inside a container's network namespace via nsenter func (m *Manager) runInContainer(pid int, table string, args ...string) error { - iptPath := "/usr/sbin/iptables-legacy" - if !strings.Contains(m.binary, "legacy") { - iptPath = "/usr/sbin/iptables" - } + // Use the same binary path that was auto-detected on the host + // Alpine installs iptables at /usr/sbin/ not /sbin/ + iptPath := m.binary fullArgs := []string{"-t", fmt.Sprintf("%d", pid), "-n", "--", iptPath} if table != "" { @@ -80,14 +91,22 @@ func (m *Manager) runInContainer(pid int, table string, args ...string) error { } fullArgs = append(fullArgs, args...) + cmdStr := "nsenter " + strings.Join(fullArgs, " ") + logger.Info("IPTABLES: executing nsenter for PID %d: %s", pid, cmdStr) if m.debug { - fmt.Printf("[IPTABLES DEBUG] nsenter %s\n", strings.Join(fullArgs, " ")) + logger.Debug("IPTABLES DEBUG: nsenter %s", strings.Join(fullArgs, " ")) } + cmd := exec.Command("nsenter", fullArgs...) output, err := cmd.CombinedOutput() if err != nil { + logger.Error("IPTABLES: nsenter command failed for PID %d\noutput: %s", pid, strings.TrimSpace(string(output))) return fmt.Errorf("nsenter iptables failed: %w\noutput: %s", err, string(output)) } + logger.Info("IPTABLES: nsenter command succeeded for PID %d", pid) + if len(output) > 0 { + logger.Debug("IPTABLES: nsenter output: %s", strings.TrimSpace(string(output))) + } return nil } @@ -95,38 +114,47 @@ func (m *Manager) runInContainer(pid int, table string, args ...string) error { // Logs a warning if it fails (e.g. read-only filesystem in a container), // since this should be configured at the host level. func (m *Manager) EnsureIPForward() error { + logger.Info("IPTABLES: enabling IP forwarding (echo 1 > /proc/sys/net/ipv4/ip_forward)") cmd := exec.Command("sh", "-c", "echo 1 > /proc/sys/net/ipv4/ip_forward") output, err := cmd.CombinedOutput() if err != nil { + logger.Warn("IPTABLES: failed to enable ip_forward (expected if read-only fs): %v", err) return fmt.Errorf("failed to enable ip_forward: %w\noutput: %s", err, string(output)) } + logger.Info("IPTABLES: IP forwarding enabled") return nil } // EnsureEstablishedRelated inserts an ESTABLISHED,RELATED accept rule at the top of a chain func (m *Manager) EnsureEstablishedRelated(chain string) error { + logger.Debug("IPTABLES: checking for ESTABLISHED,RELATED rule in %s", chain) checkArgs := []string{"-w", "-n", "-L", chain} cmd := exec.Command(m.binary, checkArgs...) output, err := cmd.Output() if err != nil { + logger.Debug("IPTABLES: could not list chain %s: %v", chain, err) return nil } if !strings.Contains(string(output), "ESTABLISHED") || !strings.Contains(string(output), "RELATED") { + logger.Info("IPTABLES: inserting ESTABLISHED,RELATED ACCEPT rule in %s", chain) args := []string{"-w", "-I", chain, "-m", "state", "--state", "established,related", "-j", "ACCEPT"} return m.run(args...) } + logger.Debug("IPTABLES: ESTABLISHED,RELATED rule already exists in %s", chain) return nil } // DeleteLine deletes a specific line number from a chain func (m *Manager) DeleteLine(chain string, lineNum string) error { + logger.Info("IPTABLES: deleting line %s from chain %s", lineNum, chain) args := []string{"-w", "-D", chain, lineNum} return m.run(args...) } // DeleteLineInContainer deletes a specific line number from a chain inside a container namespace func (m *Manager) DeleteLineInContainer(pid int, table, chain, lineNum string) error { + logger.Info("IPTABLES: deleting line %s from chain %s in container PID %d", lineNum, chain, pid) args := []string{"-D", chain, lineNum} return m.runInContainer(pid, table, args...) } @@ -141,6 +169,7 @@ func (m *Manager) getLineNumbers(chain, table string, grepPatterns ...string) [] cmd := exec.Command(m.binary, args...) output, err := cmd.Output() if err != nil { + logger.Debug("IPTABLES: getLineNumbers failed for %s: %v", chain, err) return nil } @@ -161,12 +190,16 @@ func (m *Manager) getLineNumbers(chain, table string, grepPatterns ...string) [] } } } + logger.Debug("IPTABLES: getLineNumbers chain=%s patterns=%v found=%v", chain, grepPatterns, matchingLines) return matchingLines } // deleteMatchingLines deletes all lines in a chain matching the given patterns func (m *Manager) deleteMatchingLines(chain, table string, grepPatterns ...string) error { lines := m.getLineNumbers(chain, table, grepPatterns...) + if len(lines) > 0 { + logger.Info("IPTABLES: deleting %d matching lines from %s: %v", len(lines), chain, lines) + } for i := len(lines) - 1; i >= 0; i-- { if err := m.DeleteLine(chain, lines[i]); err != nil { return err @@ -177,15 +210,13 @@ func (m *Manager) deleteMatchingLines(chain, table string, grepPatterns ...strin // deleteMatchingLinesInContainer deletes matching lines inside a container namespace func (m *Manager) deleteMatchingLinesInContainer(pid int, table, chain string, grepPatterns ...string) error { - iptPath := "/usr/sbin/iptables-legacy" - if !strings.Contains(m.binary, "legacy") { - iptPath = "/usr/sbin/iptables" - } + iptPath := m.binary nsenterArgs := []string{"-t", fmt.Sprintf("%d", pid), "-n", "--", iptPath, "-w", "--line-number", "-n", "-t", table, "-L", chain} cmd := exec.Command("nsenter", nsenterArgs...) output, err := cmd.Output() if err != nil { + logger.Debug("IPTABLES: deleteMatchingLinesInContainer list failed for PID %d chain %s: %v", pid, chain, err) return nil } @@ -207,6 +238,11 @@ func (m *Manager) deleteMatchingLinesInContainer(pid int, table, chain string, g } } + if len(matchingLines) > 0 { + logger.Info("IPTABLES: deleting %d matching lines from container PID %d chain %s: %v", + len(matchingLines), pid, chain, matchingLines) + } + for i := len(matchingLines) - 1; i >= 0; i-- { if err := m.DeleteLineInContainer(pid, table, chain, matchingLines[i]); err != nil { return err @@ -217,6 +253,8 @@ func (m *Manager) deleteMatchingLinesInContainer(pid int, table, chain string, g // InsertPreroutingRule inserts a DNAT PREROUTING rule on the host func (m *Manager) InsertPreroutingRule(sourceIP, proto, sourcePort, targetIP, targetPort, comment string) error { + logger.Info("IPTABLES: inserting PREROUTING DNAT rule: src=%s proto=%s sport=%s -> dst=%s dport=%s comment=%q", + sourceIP, proto, sourcePort, targetIP, targetPort, comment) patterns := []string{"DNAT", sourcePort, targetIP, targetPort, comment} if err := m.deleteMatchingLines("PREROUTING", "nat", patterns...); err != nil { return fmt.Errorf("failed to delete old PREROUTING rules: %w", err) @@ -235,6 +273,8 @@ func (m *Manager) InsertPreroutingRule(sourceIP, proto, sourcePort, targetIP, ta // InsertPreroutingRuleOnInterface inserts a DNAT PREROUTING rule on a specific interface func (m *Manager) InsertPreroutingRuleOnInterface(iface, proto, sourcePort, targetIP, targetPort, comment string) error { + logger.Info("IPTABLES: inserting PREROUTING DNAT rule on interface %s: proto=%s dport=%s -> %s:%s comment=%q", + iface, proto, sourcePort, targetIP, targetPort, comment) args := []string{ "-w", "-t", "nat", "-I", "PREROUTING", "-i", iface, @@ -248,6 +288,8 @@ func (m *Manager) InsertPreroutingRuleOnInterface(iface, proto, sourcePort, targ // InsertPostroutingMasquerade inserts a MASQUERADE POSTROUTING rule on the host func (m *Manager) InsertPostroutingMasquerade(sourceCIDR, proto, sourcePort, comment string) error { + logger.Info("IPTABLES: inserting POSTROUTING MASQUERADE rule: src=%s proto=%s sport=%s comment=%q", + sourceCIDR, proto, sourcePort, comment) patterns := []string{"MASQUERADE", comment, sourceCIDR, sourcePort} if err := m.deleteMatchingLines("POSTROUTING", "nat", patterns...); err != nil { return fmt.Errorf("failed to delete old POSTROUTING rules: %w", err) @@ -266,6 +308,8 @@ func (m *Manager) InsertPostroutingMasquerade(sourceCIDR, proto, sourcePort, com // InsertPostroutingMasqueradeForTarget inserts a MASQUERADE POSTROUTING rule for a target func (m *Manager) InsertPostroutingMasqueradeForTarget(targetCIDR, proto, targetPort, comment string) error { + logger.Info("IPTABLES: inserting POSTROUTING MASQUERADE rule for target: dst=%s proto=%s dport=%s comment=%q", + targetCIDR, proto, targetPort, comment) patterns := []string{"MASQUERADE", comment, targetCIDR, targetPort} if err := m.deleteMatchingLines("POSTROUTING", "nat", patterns...); err != nil { return fmt.Errorf("failed to delete old POSTROUTING rules: %w", err) @@ -284,6 +328,8 @@ func (m *Manager) InsertPostroutingMasqueradeForTarget(targetCIDR, proto, target // InsertForwardAccept inserts a FORWARD ACCEPT rule on the host func (m *Manager) InsertForwardAccept(chain, sourceIP, targetIP, proto, sourcePort, targetPort, comment string) error { + logger.Info("IPTABLES: inserting FORWARD ACCEPT rule: chain=%s src=%s dst=%s proto=%s sport=%s dport=%s comment=%q", + chain, sourceIP, targetIP, proto, sourcePort, targetPort, comment) var grepPatterns []string grepPatterns = append(grepPatterns, proto) if sourceIP != "" { @@ -323,7 +369,11 @@ func (m *Manager) InsertForwardAccept(chain, sourceIP, targetIP, proto, sourcePo // DeleteForwardAccept deletes a FORWARD ACCEPT rule by comment func (m *Manager) DeleteForwardAccept(chain, comment string) error { + logger.Info("IPTABLES: deleting FORWARD ACCEPT rules in %s with comment=%q", chain, comment) lines := m.getLineNumbers(chain, "", comment) + if len(lines) > 0 { + logger.Info("IPTABLES: found %d FORWARD ACCEPT rules to delete: %v", len(lines), lines) + } for i := len(lines) - 1; i >= 0; i-- { if err := m.DeleteLine(chain, lines[i]); err != nil { return err @@ -334,6 +384,8 @@ func (m *Manager) DeleteForwardAccept(chain, comment string) error { // InsertPreroutingRuleInContainer inserts a DNAT PREROUTING rule inside a container namespace func (m *Manager) InsertPreroutingRuleInContainer(pid int, sourceIP, proto, sourcePort, targetIP, targetPort, comment string) error { + logger.Info("IPTABLES: inserting PREROUTING DNAT rule in container PID %d: src=%s proto=%s dport=%s -> %s:%s comment=%q", + pid, sourceIP, proto, sourcePort, targetIP, targetPort, comment) patterns := []string{"DNAT", sourcePort, targetIP, targetPort, comment} if err := m.deleteMatchingLinesInContainer(pid, "nat", "PREROUTING", patterns...); err != nil { return fmt.Errorf("failed to delete old container PREROUTING rules: %w", err) @@ -352,6 +404,8 @@ func (m *Manager) InsertPreroutingRuleInContainer(pid int, sourceIP, proto, sour // InsertPostroutingMasqueradeInContainer inserts a MASQUERADE POSTROUTING rule inside a container namespace func (m *Manager) InsertPostroutingMasqueradeInContainer(pid int, sourceCIDR, proto, sourcePort, comment string) error { + logger.Info("IPTABLES: inserting POSTROUTING MASQUERADE rule in container PID %d: src=%s proto=%s sport=%s comment=%q", + pid, sourceCIDR, proto, sourcePort, comment) patterns := []string{"MASQUERADE", comment, sourceCIDR, sourcePort} if err := m.deleteMatchingLinesInContainer(pid, "nat", "POSTROUTING", patterns...); err != nil { return fmt.Errorf("failed to delete old container POSTROUTING rules: %w", err) diff --git a/network-go/logger/logger.go b/network-go/logger/logger.go new file mode 100644 index 0000000..8f95e3d --- /dev/null +++ b/network-go/logger/logger.go @@ -0,0 +1,154 @@ +package logger + +import ( + "fmt" + "io" + "log" + "os" + "path/filepath" + "sync" + "time" +) + +// Log levels +const ( + LevelInfo = "INFO" + LevelWarn = "WARNING" + LevelError = "ERROR" + LevelDebug = "DEBUG" +) + +// Logger provides dual-output logging to stdout and a file +type Logger struct { + infoLog *log.Logger + warnLog *log.Logger + errorLog *log.Logger + debugLog *log.Logger + debug bool + mu sync.Mutex +} + +var defaultLogger *Logger + +func init() { + defaultLogger = NewLogger(false, "") +} + +// SetDefault sets the default logger instance +func SetDefault(l *Logger) { + defaultLogger = l +} + +// Default returns the current default logger +func Default() *Logger { + return defaultLogger +} + +// NewLogger creates a new dual-output logger. +// If logPath is empty, only stdout is used. +func NewLogger(debug bool, logPath string) *Logger { + flags := log.Ldate | log.Ltime | log.Lmicroseconds + + var infoWriter, warnWriter, errorWriter, debugWriter io.Writer + + if logPath != "" { + // Ensure directory exists + dir := filepath.Dir(logPath) + if err := os.MkdirAll(dir, 0755); err == nil { + file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err == nil { + // Multi-writer: stdout + file + infoWriter = io.MultiWriter(os.Stdout, file) + warnWriter = io.MultiWriter(os.Stdout, file) + errorWriter = io.MultiWriter(os.Stderr, file) + debugWriter = io.MultiWriter(os.Stdout, file) + + fmt.Fprintf(os.Stdout, "%s [%s] LOGGER: log file opened: %s\n", + time.Now().Format("2006/01/02 15:04:05.000"), LevelInfo, logPath) + } else { + fmt.Fprintf(os.Stderr, "%s [%s] LOGGER: failed to open log file %s: %v, using stdout only\n", + time.Now().Format("2006/01/02 15:04:05.000"), LevelWarn, logPath, err) + infoWriter = os.Stdout + warnWriter = os.Stdout + errorWriter = os.Stderr + debugWriter = os.Stdout + } + } else { + fmt.Fprintf(os.Stderr, "%s [%s] LOGGER: failed to create log dir %s: %v, using stdout only\n", + time.Now().Format("2006/01/02 15:04:05.000"), LevelWarn, dir, err) + infoWriter = os.Stdout + warnWriter = os.Stdout + errorWriter = os.Stderr + debugWriter = os.Stdout + } + } else { + infoWriter = os.Stdout + warnWriter = os.Stdout + errorWriter = os.Stderr + debugWriter = os.Stdout + } + + return &Logger{ + infoLog: log.New(infoWriter, "", flags), + warnLog: log.New(warnWriter, "", flags), + errorLog: log.New(errorWriter, "", flags), + debugLog: log.New(debugWriter, "", flags), + debug: debug, + } +} + +// Info logs an INFO message +func (l *Logger) Info(format string, args ...interface{}) { + l.mu.Lock() + defer l.mu.Unlock() + l.infoLog.Printf("[%s] %s", LevelInfo, fmt.Sprintf(format, args...)) +} + +// Warn logs a WARNING message +func (l *Logger) Warn(format string, args ...interface{}) { + l.mu.Lock() + defer l.mu.Unlock() + l.warnLog.Printf("[%s] %s", LevelWarn, fmt.Sprintf(format, args...)) +} + +// Error logs an ERROR message +func (l *Logger) Error(format string, args ...interface{}) { + l.mu.Lock() + defer l.mu.Unlock() + l.errorLog.Printf("[%s] %s", LevelError, fmt.Sprintf(format, args...)) +} + +// Debug logs a DEBUG message (only if debug mode is enabled) +func (l *Logger) Debug(format string, args ...interface{}) { + if !l.debug { + return + } + l.mu.Lock() + defer l.mu.Unlock() + l.debugLog.Printf("[%s] %s", LevelDebug, fmt.Sprintf(format, args...)) +} + +// Info logs an INFO message using the default logger +func Info(format string, args ...interface{}) { + defaultLogger.Info(format, args...) +} + +// Warn logs a WARNING message using the default logger +func Warn(format string, args ...interface{}) { + defaultLogger.Warn(format, args...) +} + +// Error logs an ERROR message using the default logger +func Error(format string, args ...interface{}) { + defaultLogger.Error(format, args...) +} + +// Debug logs a DEBUG message using the default logger +func Debug(format string, args ...interface{}) { + defaultLogger.Debug(format, args...) +} + +// SetDebug enables or disables debug logging +func (l *Logger) SetDebug(debug bool) { + l.debug = debug +} \ No newline at end of file diff --git a/network-go/main.go b/network-go/main.go index 502a0a7..dec5eea 100644 --- a/network-go/main.go +++ b/network-go/main.go @@ -2,7 +2,6 @@ package main import ( "context" - "log" "os" "os/signal" "syscall" @@ -12,21 +11,24 @@ import ( "firewall_containers/network-go/docker" "firewall_containers/network-go/firewall" "firewall_containers/network-go/iptables" + "firewall_containers/network-go/logger" "firewall_containers/network-go/watcher" ) // Config path - can be overridden via environment variable const defaultConfigPath = "/etc/user/config/networks.json" +// Log path - can be overridden via environment variable +const defaultLogPath = "/var/log/network-go/network-go.log" + // Watch period - can be overridden via environment variable const defaultWatchPeriod = 30 * time.Second -func getConfigPath() string { - path := os.Getenv("NETWORKS_CONFIG_PATH") - if path == "" { - return defaultConfigPath +func getEnv(key, defaultVal string) string { + if val := os.Getenv(key); val != "" { + return val } - return path + return defaultVal } func getWatchPeriod() time.Duration { @@ -36,7 +38,7 @@ func getWatchPeriod() time.Duration { } seconds, err := time.ParseDuration(periodStr + "s") if err != nil { - log.Printf("MAIN: invalid WATCH_PERIOD_SECONDS=%s, using default %s", periodStr, defaultWatchPeriod) + logger.Warn("MAIN: invalid WATCH_PERIOD_SECONDS=%s, using default %s", periodStr, defaultWatchPeriod) return defaultWatchPeriod } return seconds @@ -47,34 +49,44 @@ func getDebug() bool { } func main() { - log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) - log.Println("MAIN: starting network-go firewall container manager") - - configPath := getConfigPath() + configPath := getEnv("NETWORKS_CONFIG_PATH", defaultConfigPath) + logPath := getEnv("NETWORK_GO_LOG_PATH", defaultLogPath) watchPeriod := getWatchPeriod() debug := getDebug() - log.Printf("MAIN: config path = %s", configPath) - log.Printf("MAIN: watch period = %s", watchPeriod) - log.Printf("MAIN: debug = %v", debug) + // Initialize the dual-output logger (stdout + file) + log := logger.NewLogger(debug, logPath) + logger.SetDefault(log) + + log.Info("MAIN: starting network-go firewall container manager") + log.Info("MAIN: config path = %s", configPath) + log.Info("MAIN: log path = %s", logPath) + log.Info("MAIN: watch period = %s", watchPeriod) + log.Info("MAIN: debug = %v", debug) // Create Docker client (uses DOCKER_HOST env var automatically) dockerClient, err := docker.NewClient() if err != nil { - log.Fatalf("MAIN: failed to create Docker client: %v", err) + log.Error("MAIN: failed to create Docker client: %v", err) + os.Exit(1) } defer dockerClient.Close() + log.Info("MAIN: Docker client created") // Create iptables manager iptablesMgr := iptables.NewManager(debug) + log.Info("MAIN: iptables manager created (binary=%s)", iptablesMgr.Binary()) ctx := context.Background() // Load initial config cfg, err := config.Load(configPath) if err != nil { - log.Fatalf("MAIN: failed to load initial config: %v", err) + log.Error("MAIN: failed to load initial config: %v", err) + os.Exit(1) } + log.Info("MAIN: config loaded: %d networks, %d IPs, %d policies", + len(cfg.Networks), len(cfg.IPs), len(cfg.Policies)) // Create the firewall orchestrator (needs config for resolver) orchestrator := firewall.NewOrchestrator(dockerClient, iptablesMgr, cfg) @@ -84,12 +96,14 @@ func main() { // Set up file watcher to detect changes and re-reconcile onChange := func() { - log.Println("MAIN: config file change detected, reloading and reconciling") + log.Info("MAIN: config file change detected, reloading and reconciling") newCfg, err := config.Load(configPath) if err != nil { - log.Printf("MAIN: failed to reload config: %v", err) + log.Error("MAIN: failed to reload config: %v", err) return } + log.Info("MAIN: config reloaded: %d networks, %d IPs, %d policies", + len(newCfg.Networks), len(newCfg.IPs), len(newCfg.Policies)) orchestrator.ReconcileAll(ctx, newCfg) } @@ -101,7 +115,7 @@ func main() { signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) sig := <-sigCh - log.Printf("MAIN: received signal %v, shutting down", sig) + log.Info("MAIN: received signal %v, shutting down", sig) fileWatcher.Stop() - log.Println("MAIN: shutdown complete") + log.Info("MAIN: shutdown complete") } \ No newline at end of file diff --git a/network-go/resolver/resolver.go b/network-go/resolver/resolver.go index 8d4dae3..c5cdab0 100644 --- a/network-go/resolver/resolver.go +++ b/network-go/resolver/resolver.go @@ -1,10 +1,10 @@ package resolver import ( - "log" "strings" "firewall_containers/network-go/config" + "firewall_containers/network-go/logger" ) // Resolver resolves names to IPs using the networks.json configuration @@ -15,53 +15,73 @@ type Resolver struct { // NewResolver creates a new name resolver backed by the networks.json config func NewResolver(cfg *config.NetworksConfig) *Resolver { - return &Resolver{ + r := &Resolver{ cfg: cfg, retries: 2, } + if cfg != nil { + logger.Debug("RESOLVER: created with %d IPs in config", len(cfg.IPs)) + } else { + logger.Debug("RESOLVER: created with nil config") + } + return r } // SetConfig updates the config used for name resolution func (r *Resolver) SetConfig(cfg *config.NetworksConfig) { r.cfg = cfg + if cfg != nil { + logger.Debug("RESOLVER: config updated with %d IPs", len(cfg.IPs)) + } else { + logger.Debug("RESOLVER: config set to nil") + } } // SetRetries sets the number of retries for resolution func (r *Resolver) SetRetries(n int) { r.retries = n + logger.Debug("RESOLVER: retries set to %d", n) } // Resolve resolves a name to one or more IP addresses // It looks up the name in the networks.json config by container_name and selector fields func (r *Resolver) Resolve(name string) []string { if r.cfg == nil { + logger.Debug("RESOLVER: resolve(%q): config is nil", name) return nil } + logger.Debug("RESOLVER: resolving %q", name) var ips []string // Look up by container_name and selector in the IPs section for _, ipCfg := range r.cfg.IPs { if ipCfg.ContainerName == name || ipCfg.Selector == name { + logger.Debug("RESOLVER: exact match for %q: container=%s selector=%s -> IP=%s", + name, ipCfg.ContainerName, ipCfg.Selector, ipCfg.IP) ips = append(ips, ipCfg.IP) } } - // If no exact match, try prefix matching: extract the prefix before the first dash + // If no exact match, try prefix matching: extract the prefix before the first dash // and match any container/selector starting with that prefix. // This allows "wireguardproxy-client" to match "wireguardproxyclient" (after dash stripping) // or "app-1"/"app-2" to match "app-x". if len(ips) == 0 && strings.Contains(name, "-") { prefix := name[:strings.Index(name, "-")] + logger.Debug("RESOLVER: prefix matching %q with prefix %q", name, prefix) for _, ipCfg := range r.cfg.IPs { if strings.HasPrefix(ipCfg.ContainerName, prefix) || strings.HasPrefix(ipCfg.Selector, prefix) { + logger.Debug("RESOLVER: prefix match for %q: %s -> IP=%s", prefix, ipCfg.ContainerName, ipCfg.IP) ips = append(ips, ipCfg.IP) } } } if len(ips) == 0 { - log.Printf("RESOLVER: no IP found for %s in networks.json config", name) + logger.Warn("RESOLVER: no IP found for %q in networks.json config", name) + } else { + logger.Info("RESOLVER: %q resolved to %v", name, ips) } return ips diff --git a/network-go/watcher/watcher.go b/network-go/watcher/watcher.go index b89541e..f92ff76 100644 --- a/network-go/watcher/watcher.go +++ b/network-go/watcher/watcher.go @@ -3,9 +3,10 @@ package watcher import ( "crypto/md5" "fmt" - "log" "os" "time" + + "firewall_containers/network-go/logger" ) // FileWatcher periodically checks a file for changes using MD5 hash @@ -42,7 +43,7 @@ func (fw *FileWatcher) Start() { // Compute initial hash hash, err := fw.hashFile() if err != nil { - log.Printf("WATCHER: initial hash computation failed for %s: %v", fw.path, err) + logger.Warn("WATCHER: initial hash computation failed for %s: %v", fw.path, err) } else { fw.lastHash = hash } @@ -51,22 +52,22 @@ func (fw *FileWatcher) Start() { ticker := time.NewTicker(fw.period) defer ticker.Stop() - log.Printf("WATCHER: started watching %s every %s", fw.path, fw.period) + logger.Info("WATCHER: started watching %s every %s", fw.path, fw.period) for { select { case <-fw.stopCh: - log.Printf("WATCHER: stopped watching %s", fw.path) + logger.Info("WATCHER: stopped watching %s", fw.path) return case <-ticker.C: hash, err := fw.hashFile() if err != nil { - log.Printf("WATCHER: failed to hash %s: %v", fw.path, err) + logger.Warn("WATCHER: failed to hash %s: %v", fw.path, err) continue } if hash != fw.lastHash { - log.Printf("WATCHER: detected change in %s", fw.path) + logger.Info("WATCHER: detected change in %s", fw.path) fw.lastHash = hash if fw.onChange != nil { fw.onChange()