Repository: inspektor-gadget/inspektor-gadget
Pull request #4191: Add advise_networkpolicy as image based gadget
Author: login: burak-ok
Created: 2025-03-07T15:39:36Z | Merged: 2025-05-28T15:19:47Z

This PR adds the `advise_networkpolicy` gadget as an image-based gadget

The events are getting collected by a specific new operator, which has to be explicitly enabled. Anyway it is only useful to generate network policies

## Test

I tested with the Guide i created in `README.mdx`. Both the builtin `advise network-policy` and this new `run advise_networkpolicy` produced the same yaml.

## TODO
Create test

## Prerequisites
Cherry-picked commands from other open PRs:
- https://github.com/inspektor-gadget/inspektor-gadget/pull/4168
- https://github.com/inspektor-gadget/inspektor-gadget/pull/4169
- https://github.com/inspektor-gadget/inspektor-gadget/pull/4396
- https://github.com/inspektor-gadget/inspektor-gadget/pull/4493
- https://github.com/inspektor-gadget/inspektor-gadget/pull/4492
- https://github.com/inspektor-gadget/inspektor-gadget/pull/4497
- https://github.com/inspektor-gadget/inspektor-gadget/pull/4523


Diff:
diff --git a/cmd/common/oci.go b/cmd/common/oci.go
index 4ca7c2680f2..b48dfb4de34 100644
--- a/cmd/common/oci.go
+++ b/cmd/common/oci.go
@@ -37,6 +37,7 @@ import (
 	"github.com/inspektor-gadget/inspektor-gadget/pkg/operators"
 	clioperator "github.com/inspektor-gadget/inspektor-gadget/pkg/operators/cli"
 	"github.com/inspektor-gadget/inspektor-gadget/pkg/operators/combiner"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/operators/generate_networkpolicy"
 	_ "github.com/inspektor-gadget/inspektor-gadget/pkg/operators/limiter"
 	ocihandler "github.com/inspektor-gadget/inspektor-gadget/pkg/operators/oci-handler"
 	_ "github.com/inspektor-gadget/inspektor-gadget/pkg/operators/otel-logs"
@@ -173,7 +174,7 @@ func NewRunCommand(rootCmd *cobra.Command, runtime runtime.Runtime, hiddenColumn
 			}
 			ops = append(ops, op)
 		}
-		ops = append(ops, clioperator.CLIOperator, combiner.CombinerOperator)
+		ops = append(ops, clioperator.CLIOperator, combiner.CombinerOperator, generate_networkpolicy.GNPOperator)
 		initializedOperators = true
 
 		imageName := actualArgs[0]
@@ -292,7 +293,7 @@ func NewRunCommand(rootCmd *cobra.Command, runtime runtime.Runtime, hiddenColumn
 			}
 			ops = append(ops, op)
 		}
-		ops = append(ops, clioperator.CLIOperator, combiner.CombinerOperator)
+		ops = append(ops, clioperator.CLIOperator, combiner.CombinerOperator, generate_networkpolicy.GNPOperator)
 
 		timeoutDuration := time.Duration(timeoutSeconds) * time.Second
 
diff --git a/docs/gadgets/advise_networkpolicy.mdx b/docs/gadgets/advise_networkpolicy.mdx
new file mode 120000
index 00000000000..3d2f395ddeb
--- /dev/null
+++ b/docs/gadgets/advise_networkpolicy.mdx
@@ -0,0 +1 @@
+gadgets/advise_networkpolicy/README.mdx
\ No newline at end of file
diff --git a/gadgets/Makefile b/gadgets/Makefile
index dd1d4e4fb38..966f9d66669 100644
--- a/gadgets/Makefile
+++ b/gadgets/Makefile
@@ -21,6 +21,7 @@ UNIT_TEST_DIR = test/unit
 INTEGRATION_TEST_DIR = test/integration
 
 GADGETS = \
+	advise_networkpolicy \
 	advise_seccomp \
 	audit_seccomp \
 	bpfstats \
diff --git a/gadgets/advise_networkpolicy/README.md b/gadgets/advise_networkpolicy/README.md
new file mode 100644
index 00000000000..38be819c62c
--- /dev/null
+++ b/gadgets/advise_networkpolicy/README.md
@@ -0,0 +1,5 @@
+# advise networkpolicy
+
+The advise networkpolicy gadget monitors the network activity in the specified namespaces and records a summary of TCP and UDP traffic. This is then used to generate Kubernetes network policies.
+
+Check the full documentation on https://inspektor-gadget.io/docs/latest/gadgets/advise_networkpolicy
diff --git a/gadgets/advise_networkpolicy/README.mdx b/gadgets/advise_networkpolicy/README.mdx
new file mode 100644
index 00000000000..08f27f787cc
--- /dev/null
+++ b/gadgets/advise_networkpolicy/README.mdx
@@ -0,0 +1,144 @@
+---
+title: advise networkpolicy
+sidebar_position: 0
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+The advise networkpolicy gadget monitors the network activity in the specified namespaces
+and records a summary of TCP and UDP traffic. This is then used to generate Kubernetes
+network policies.
+
+## Getting started
+
+
+```bash
+$ kubectl gadget run ghcr.io/inspektor-gadget/gadget/advise_networkpolicy:%IG_TAG%
+```
+
+## Flags
+
+No flags.
+
+## Guide
+
+First, we need to create an nginx deployment which can respond to our test requests. 
+
+```bash
+$ kubectl create service nodeport nginx --tcp=80:80
+service/nginx created
+$ kubectl create deployment nginx --image=nginx
+deployment.apps/nginx created
+```
+
+Then, start the advise_networkpolicy gadget in another terminal.
+
+```bash
+$ kubectl gadget run advise_networkpolicy:%IG_TAG% --map-fetch-count=0 --map-fetch-interval=0
+```
+
+Now we can deploy our client pod, send a request to our nginx deployment and `1.1.1.1` and then quit.
+
+```bash
+$ kubectl run --rm -ti --image busybox test-pod          
+If you don't see a command prompt, try pressing enter.
+/ # wget nginx
+Connecting to nginx (10.105.129.249:80)
+saving to 'index.html'
+index.html           100% |********************************|   615  0:00:00 ETA
+'index.html' saved
+/ # wget 1.1.1.1
+Connecting to 1.1.1.1 (1.1.1.1:80)
+Connecting to 1.1.1.1 (1.1.1.1:443)
+wget: note: TLS certificate validation not implemented
+Connecting to one.one.one.one (1.1.1.1:443)
+wget: can't open 'index.html': File exists
+/ # exit
+Session ended, resume using 'kubectl attach test-pod -c test-pod -i -t' command when the pod is running
+pod "test-pod" deleted
+```
+
+Let's switch back to the gadget terminal, stop our gadget. The policy will then be printed:
+
+```bash
+$ kubectl gadget run advise_networkpolicy:%IG_TAG% --map-fetch-count=0 --map-fetch-interval=0
+...
+^C
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  creationTimestamp: null
+  name: nginx-network
+  namespace: default
+spec:
+  ingress:
+  - from:
+    - podSelector:
+        matchLabels:
+          run: test-pod
+    ports:
+    - port: 80
+      protocol: TCP
+  podSelector:
+    matchLabels:
+      app: nginx
+  policyTypes:
+  - Ingress
+  - Egress
+---
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  creationTimestamp: null
+  name: test-pod-network
+  namespace: default
+spec:
+  egress:
+  - ports:
+    - port: 80
+      protocol: TCP
+    to:
+    - ipBlock:
+        cidr: 1.1.1.1/32
+  - ports:
+    - port: 80
+      protocol: TCP
+    to:
+    - podSelector:
+        matchLabels:
+          app: nginx
+  - ports:
+    - port: 443
+      protocol: TCP
+    to:
+    - ipBlock:
+        cidr: 1.1.1.1/32
+  - ports:
+    - port: 53
+      protocol: UDP
+    to:
+    - namespaceSelector:
+        matchLabels:
+          kubernetes.io/metadata.name: kube-system
+      podSelector:
+        matchLabels:
+          k8s-app: kube-dns
+          kubernetes.io/cluster-service: "true"
+          kubernetes.io/name: CoreDNS
+  podSelector:
+    matchLabels:
+      run: test-pod
+  policyTypes:
+  - Ingress
+  - Egress
+```
+
+Finally, clean the system:
+
+```bash
+$ kubectl delete deployment nginx
+deployment.apps "nginx" deleted
+$ kubectl delete service nginx
+service "nginx" deleted
+```
diff --git a/gadgets/advise_networkpolicy/artifacthub-pkg.yml b/gadgets/advise_networkpolicy/artifacthub-pkg.yml
new file mode 100644
index 00000000000..6e8ce8adb8f
--- /dev/null
+++ b/gadgets/advise_networkpolicy/artifacthub-pkg.yml
@@ -0,0 +1,29 @@
+# Artifact Hub package metadata file
+version: 0.40.0
+name: "advise_networkpolicy"
+category: monitoring-logging
+displayName: "advise networkpolicy"
+createdAt: "2025-05-03T11:23:41Z"
+digest: "2025-05-03T11:23:41Z"
+description: "Generate network policies according to the K8s traffic"
+logoURL: "https://inspektor-gadget.io/media/brand-icon.svg"
+license: ""
+homeURL: "https://inspektor-gadget.io/docs/latest/gadgets/advise_networkpolicy"
+containersImages:
+    - name: gadget
+      image: "ghcr.io/inspektor-gadget/gadget/advise_networkpolicy:latest"
+      platforms:
+        - linux/amd64
+        - linux/arm64
+keywords:
+    - gadget
+links:
+    - name: source
+      url: "https://github.com/inspektor-gadget/inspektor-gadget/tree/main/gadgets/advise_networkpolicy"
+install: |
+    # Run
+    ```bash
+    kubectl gadget run ghcr.io/inspektor-gadget/gadget/advise_networkpolicy:latest
+    ```
+provider:
+    name: Inspektor Gadget
diff --git a/gadgets/advise_networkpolicy/gadget.yaml b/gadgets/advise_networkpolicy/gadget.yaml
new file mode 100644
index 00000000000..103749e4edb
--- /dev/null
+++ b/gadgets/advise_networkpolicy/gadget.yaml
@@ -0,0 +1,12 @@
+name: advise networkpolicy
+description: Generate network policies according to the K8s traffic
+homepageURL: https://inspektor-gadget.io/
+documentationURL: https://www.inspektor-gadget.io/docs/latest/gadgets/advise_networkpolicy
+sourceURL: https://github.com/inspektor-gadget/inspektor-gadget/tree/main/gadgets/advise_networkpolicy
+datasources:
+  network_connections:
+    annotations:
+      cli.supported-output-modes: none
+      ebpf.map.flush-on-stop: true
+      generate_networkpolicy.enable: true
+      kubenameresolver.enable: true
diff --git a/gadgets/advise_networkpolicy/program.bpf.c b/gadgets/advise_networkpolicy/program.bpf.c
new file mode 100644
index 00000000000..d4e1b2dfdf7
--- /dev/null
+++ b/gadgets/advise_networkpolicy/program.bpf.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 The Inspektor Gadget authors */
+#include <linux/bpf.h>
+#include <linux/if_ether.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/in.h>
+#include <linux/tcp.h>
+#include <linux/types.h>
+#include <linux/udp.h>
+#include <stdbool.h>
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#include <gadget/buffer.h>
+#include <gadget/macros.h>
+#include <gadget/types.h>
+#include <gadget/maps.bpf.h>
+
+#define GADGET_TYPE_NETWORKING
+#include <gadget/sockets-map.h>
+
+#define PACKET_HOST 0
+#define PACKET_OUTGOING 4
+
+struct event_t {
+	gadget_netns_id netns_id;
+	struct gadget_l4endpoint_t endpoint;
+	__u8 egress;
+};
+
+struct empty_t {
+	__u8 unused;
+};
+
+const struct empty_t zero = {};
+
+// When using GADGET_MAPITER, both the key and value has to be structs
+struct {
+	__uint(type, BPF_MAP_TYPE_HASH);
+	__uint(max_entries, 10240);
+	__type(key, struct event_t);
+	__type(value, struct empty_t);
+} packets SEC(".maps");
+
+GADGET_MAPITER(network_connections, packets);
+
+SEC("socket1")
+int ig_trace_net(struct __sk_buff *skb)
+{
+	// Skip multicast, broadcast, forwarding...
+	if (skb->pkt_type != PACKET_HOST && skb->pkt_type != PACKET_OUTGOING)
+		return 0;
+
+	// Skip frames with non-IP Ethernet protocol.
+	struct ethhdr ethh;
+	if (bpf_skb_load_bytes(skb, 0, &ethh, sizeof ethh))
+		return 0;
+	if (bpf_ntohs(ethh.h_proto) != ETH_P_IP)
+		return 0;
+
+	int ip_off = ETH_HLEN;
+	// Read the IP header.
+	struct iphdr iph;
+	if (bpf_skb_load_bytes(skb, ip_off, &iph, sizeof iph))
+		return 0;
+
+	// An IPv4 header doesn't have a fixed size. The IHL field of a packet
+	// represents the size of the IP header in 32-bit words, so we need to
+	// multiply this value by 4 to get the header size in bytes.
+	__u8 ip_header_len = iph.ihl * 4;
+	int l4_off = ip_off + ip_header_len;
+	__u16 port;
+
+	if (iph.protocol == IPPROTO_TCP) {
+		// Read the TCP header.
+		struct tcphdr tcph;
+		if (bpf_skb_load_bytes(skb, l4_off, &tcph, sizeof tcph))
+			return 0;
+
+		if (!tcph.syn || tcph.ack)
+			return 0;
+
+		port = bpf_htons(tcph.dest);
+	} else if (iph.protocol == IPPROTO_UDP) {
+		// Read the UDP header.
+		struct udphdr udph;
+		if (bpf_skb_load_bytes(skb, l4_off, &udph, sizeof udph))
+			return 0;
+
+		// UDP packets don't have a TCP-SYN to identify the direction.
+		// Check usage of dynamic ports instead.
+		// https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
+		// System Ports: 0-1023
+		// User Ports: 1024-49151
+		// Dynamic and/or Private Ports: 49152-65535
+		// However, Linux uses ephemeral ports: 32768-60999 (/proc/sys/net/ipv4/ip_local_port_range)
+		// And /proc/sys/net/ipv4/ip_unprivileged_port_start: 1024
+		if (bpf_htons(udph.dest) < 1024)
+			port = bpf_htons(udph.dest);
+		else
+			return 0;
+	} else {
+		// Skip packets with IP protocol other than TCP/UDP.
+		return 0;
+	}
+
+	struct event_t event = {};
+	event.netns_id = skb->cb[0]; // cb[0] initialized by dispatcher.bpf.c
+
+	if (skb->pkt_type == PACKET_HOST) {
+		event.endpoint.addr_raw.v4 = iph.saddr;
+	} else {
+		event.endpoint.addr_raw.v4 = iph.daddr;
+	}
+	event.endpoint.proto_raw = iph.protocol;
+	event.endpoint.port = port;
+	event.endpoint.version = 4;
+	event.egress = skb->pkt_type == PACKET_OUTGOING;
+
+	bpf_map_update_elem(&packets, &event, &zero, BPF_ANY);
+
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/gadgets/advise_networkpolicy/test/integration/advise_networkpolicy_test.go b/gadgets/advise_networkpolicy/test/integration/advise_networkpolicy_test.go
new file mode 100644
index 00000000000..87dd336dbc6
--- /dev/null
+++ b/gadgets/advise_networkpolicy/test/integration/advise_networkpolicy_test.go
@@ -0,0 +1,154 @@
+// Copyright 2025 The Inspektor Gadget authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tests
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	gadgettesting "github.com/inspektor-gadget/inspektor-gadget/gadgets/testing"
+	igtesting "github.com/inspektor-gadget/inspektor-gadget/pkg/testing"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/testing/containers"
+	igrunner "github.com/inspektor-gadget/inspektor-gadget/pkg/testing/ig"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/testing/utils"
+)
+
+var expectedYaml = `apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  creationTimestamp: null
+  name: test-advise-networkpolicy-client-network
+  namespace: <CLIENT_NAMESPACE>
+spec:
+  egress:
+  - ports:
+    - port: 80
+      protocol: TCP
+    to:
+    - namespaceSelector:
+        matchLabels:
+          kubernetes.io/metadata.name: <SERVER_NAMESPACE>
+      podSelector:
+        matchLabels:
+          run: test-advise-networkpolicy-server
+  podSelector:
+    matchLabels:
+      run: test-advise-networkpolicy-client
+  policyTypes:
+  - Ingress
+  - Egress
+---
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  creationTimestamp: null
+  name: test-advise-networkpolicy-server-network
+  namespace: <SERVER_NAMESPACE>
+spec:
+  ingress:
+  - from:
+    - namespaceSelector:
+        matchLabels:
+          kubernetes.io/metadata.name: <CLIENT_NAMESPACE>
+      podSelector:
+        matchLabels:
+          run: test-advise-networkpolicy-client
+    ports:
+    - port: 80
+      protocol: TCP
+  podSelector:
+    matchLabels:
+      run: test-advise-networkpolicy-server
+  policyTypes:
+  - Ingress
+  - Egress`
+
+func TestAdviseNetworkpolicyGadget(t *testing.T) {
+	gadgettesting.RequireEnvironmentVariables(t)
+	utils.InitTest(t)
+
+	if utils.CurrentTestComponent != utils.KubectlGadgetTestComponent {
+		t.Skip("This gadget is only for kubectl-gadget")
+	}
+
+	containerFactory, err := containers.NewContainerFactory(utils.Runtime)
+	require.NoError(t, err, "new container factory")
+
+	serverNs := utils.GenerateTestNamespaceName(t, "test-advise-networkpolicy")
+	serverContainerName := "test-advise-networkpolicy-server"
+	serverContainerImage := gadgettesting.NginxImage
+	serverContainerOpts := []containers.ContainerOption{
+		containers.WithContainerImage(serverContainerImage),
+		containers.WithContainerNamespace(serverNs),
+	}
+	testServerContainer := containerFactory.NewContainer(
+		serverContainerName,
+		"nginx && sleep 10000",
+		serverContainerOpts...,
+	)
+
+	testServerContainer.Start(t)
+	t.Cleanup(func() {
+		testServerContainer.Stop(t)
+	})
+
+	clientNs := utils.GenerateTestNamespaceName(t, "test-advise-networkpolicy")
+	clientContainerName := "test-advise-networkpolicy-client"
+	clientContainerImage := gadgettesting.BusyBoxImage
+	clientContainerOpts := []containers.ContainerOption{
+		containers.WithContainerImage(clientContainerImage),
+		containers.WithContainerNamespace(clientNs),
+	}
+	testClientContainer := containerFactory.NewContainer(
+		clientContainerName,
+		fmt.Sprintf("while true; do sleep 0.5 && wget %s; done", testServerContainer.IP()),
+		clientContainerOpts...,
+	)
+
+	testClientContainer.Start(t)
+	t.Cleanup(func() {
+		testClientContainer.Stop(t)
+	})
+
+	var runnerOpts []igrunner.Option
+	var testingOpts []igtesting.Option
+
+	runnerOpts = append(runnerOpts,
+		igrunner.WithFlags(fmt.Sprintf("-n=%s,%s", serverNs, clientNs), "--timeout=5", "--map-fetch-count=0", "--map-fetch-interval=0"),
+		igrunner.WithOutputMode("advise"))
+	testingOpts = append(testingOpts, igtesting.WithCbBeforeCleanup(utils.PrintLogsFn(serverNs, clientNs)))
+
+	runnerOpts = append(runnerOpts, igrunner.WithValidateOutput(
+		func(t *testing.T, output string) {
+			expectedYaml = strings.ReplaceAll(expectedYaml, "<SERVER_NAMESPACE>", serverNs)
+			expectedYaml = strings.ReplaceAll(expectedYaml, "<CLIENT_NAMESPACE>", clientNs)
+			yamlExpectedArr := strings.Split(expectedYaml, "---")
+			yamlActualArr := strings.Split(output, "---")
+
+			require.Equal(t, len(yamlExpectedArr), len(yamlActualArr), "number of policies")
+
+			assert.YAMLEq(t, yamlExpectedArr[0], yamlActualArr[0], "first policy")
+			assert.YAMLEq(t, yamlExpectedArr[1], yamlActualArr[1], "second policy")
+		},
+	))
+
+	adviseNetworkPolicyCmd := igrunner.New("advise_networkpolicy", runnerOpts...)
+
+	igtesting.RunTestSteps([]igtesting.TestStep{adviseNetworkPolicyCmd}, t, testingOpts...)
+}
diff --git a/pkg/operators/generate_networkpolicy/generate_networkpolicy.go b/pkg/operators/generate_networkpolicy/generate_networkpolicy.go
new file mode 100644
index 00000000000..91024af7b40
--- /dev/null
+++ b/pkg/operators/generate_networkpolicy/generate_networkpolicy.go
@@ -0,0 +1,349 @@
+// Copyright 2025 The Inspektor Gadget authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package generate_networkpolicy
+
+import (
+	"errors"
+	"fmt"
+	"sort"
+
+	v1 "k8s.io/api/core/v1"
+	networkingv1 "k8s.io/api/networking/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+	k8syaml "sigs.k8s.io/yaml"
+
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/types"
+)
+
+type NetworkEvent struct {
+	K8s types.K8sMetadata
+
+	egress   bool
+	endpoint types.L4Endpoint
+	proto    string // L4Endpoint has proto as uint8, but we need a string here
+}
+
+var defaultLabelsToIgnore = map[string]struct{}{
+	"controller-revision-hash": {},
+	"pod-template-generation":  {},
+	"pod-template-hash":        {},
+}
+
+var LabelsToIgnore = defaultLabelsToIgnore
+
+/* labelFilteredKeyList returns a sorted list of label keys but without the labels to
+ * ignore.
+ */
+func labelFilteredKeyList(labels map[string]string) []string {
+	keys := make([]string, 0, len(labels))
+	for k := range labels {
+		if _, ok := LabelsToIgnore[k]; ok {
+			continue
+		}
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+
+	return keys
+}
+
+func labelFilter(labels map[string]string) map[string]string {
+	ret := map[string]string{}
+	for k := range labels {
+		if _, ok := LabelsToIgnore[k]; ok {
+			continue
+		}
+		ret[k] = labels[k]
+	}
+	return ret
+}
+
+/* labelKeyString returns a sorted list of labels in a single string.
+ * label1=value1,label2=value2
+ */
+func labelKeyString(labels map[string]string) (ret string) {
+	keys := labelFilteredKeyList(labels)
+
+	for index, k := range keys {
+		sep := ","
+		if index == 0 {
+			sep = ""
+		}
+		ret += fmt.Sprintf("%s%s=%s", sep, k, labels[k])
+	}
+	return
+}
+
+/* localPodKey returns a key that can be used to group pods together:
+ * namespace:label1=value1,label2=value2
+ */
+func localPodKey(e NetworkEvent) (ret string) {
+	return e.K8s.Namespace + ":" + labelKeyString(e.K8s.PodLabels)
+}
+
+func networkPeerKey(e NetworkEvent) (string, error) {
+	var ret string
+	switch e.endpoint.Kind {
+	case types.EndpointKindPod:
+		ret = string(e.endpoint.Kind) + ":" + e.endpoint.Namespace + ":" + labelKeyString(e.endpoint.PodLabels)
+	case types.EndpointKindService:
+		ret = string(e.endpoint.Kind) + ":" + e.endpoint.Namespace + ":" + labelKeyString(e.endpoint.PodLabels)
+	case types.EndpointKindRaw:
+		ret = string(e.endpoint.Kind) + ":" + e.endpoint.Addr
+	default:
+		return "", fmt.Errorf("unknown endpoint kind: %s", e.endpoint.Kind)
+	}
+	return fmt.Sprintf("%s:%d", ret, e.endpoint.Port), nil
+}
+
+func eventToRule(e NetworkEvent) ([]networkingv1.NetworkPolicyPort, []networkingv1.NetworkPolicyPeer, error) {
+	port := intstr.FromInt(int(e.endpoint.Port))
+	protocol := v1.Protocol(e.proto)
+	ports := []networkingv1.NetworkPolicyPort{
+		{
+			Port:     &port,
+			Protocol: &protocol,
+		},
+	}
+	var peers []networkingv1.NetworkPolicyPeer
+	switch e.endpoint.Kind {
+	case types.EndpointKindPod:
+		peers = []networkingv1.NetworkPolicyPeer{
+			{
+				PodSelector: &metav1.LabelSelector{MatchLabels: labelFilter(e.endpoint.PodLabels)},
+			},
+		}
+		if e.K8s.Namespace != e.endpoint.Namespace {
+			peers[0].NamespaceSelector = &metav1.LabelSelector{
+				MatchLabels: map[string]string{
+					// Kubernetes 1.22 is guaranteed to add the following label on namespaces:
+					// kubernetes.io/metadata.name=obj.Name
+					// See:
+					// https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/2161-apiserver-default-labels#proposal
+					"kubernetes.io/metadata.name": e.endpoint.Namespace,
+				},
+			}
+		}
+	case types.EndpointKindService:
+		peers = []networkingv1.NetworkPolicyPeer{
+			{
+				PodSelector: &metav1.LabelSelector{MatchLabels: e.endpoint.PodLabels},
+			},
+		}
+		if e.K8s.Namespace != e.endpoint.Namespace {
+			peers[0].NamespaceSelector = &metav1.LabelSelector{
+				MatchLabels: map[string]string{
+					// Kubernetes 1.22 is guaranteed to add the following label on namespaces:
+					// kubernetes.io/metadata.name=obj.Name
+					// See:
+					// https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/2161-apiserver-default-labels#proposal
+					"kubernetes.io/metadata.name": e.endpoint.Namespace,
+				},
+			}
+		}
+	case types.EndpointKindRaw:
+		if e.endpoint.Addr == "127.0.0.1" {
+			// No need to generate a network policy for localhost
+			peers = []networkingv1.NetworkPolicyPeer{}
+		} else {
+			peers = []networkingv1.NetworkPolicyPeer{
+				{
+					IPBlock: &networkingv1.IPBlock{
+						CIDR: e.endpoint.Addr + "/32",
+					},
+				},
+			}
+		}
+	default:
+		return nil, nil, fmt.Errorf("unknown endpoint kind: %s", e.endpoint.Kind)
+	}
+	return ports, peers, nil
+}
+
+func sortIngressRules(rules []networkingv1.NetworkPolicyIngressRule) ([]networkingv1.NetworkPolicyIngressRule, error) {
+	var errs []error
+	sort.Slice(rules, func(i, j int) bool {
+		ri, rj := rules[i], rules[j]
+
+		// No need to support all network policies, but only the ones
+		// generated by eventToRule()
+		if len(ri.Ports) != 1 || len(rj.Ports) != 1 {
+			errs = append(errs, fmt.Errorf("rules with multiple ports"))
+			return true
+		}
+		if ri.Ports[0].Protocol == nil || rj.Ports[0].Protocol == nil {
+			errs = append(errs, fmt.Errorf("rules without protocol"))
+			return true
+		}
+
+		switch {
+		case *ri.Ports[0].Protocol != *rj.Ports[0].Protocol:
+			return *ri.Ports[0].Protocol < *rj.Ports[0].Protocol
+		case ri.Ports[0].Port.IntVal != rj.Ports[0].Port.IntVal:
+			return ri.Ports[0].Port.IntVal < rj.Ports[0].Port.IntVal
+		default:
+			yamlOutput1, _ := k8syaml.Marshal(ri)
+			yamlOutput2, _ := k8syaml.Marshal(rj)
+			return string(yamlOutput1) < string(yamlOutput2)
+		}
+	})
+	return rules, errors.Join(errs...)
+}
+
+func sortEgressRules(rules []networkingv1.NetworkPolicyEgressRule) ([]networkingv1.NetworkPolicyEgressRule, error) {
+	var errs []error
+	sort.Slice(rules, func(i, j int) bool {
+		ri, rj := rules[i], rules[j]
+
+		// No need to support all network policies, but only the ones
+		// generated by eventToRule()
+		if len(ri.Ports) != 1 || len(rj.Ports) != 1 {
+			errs = append(errs, fmt.Errorf("rules with multiple ports"))
+			return true
+		}
+		if ri.Ports[0].Protocol == nil || rj.Ports[0].Protocol == nil {
+			errs = append(errs, fmt.Errorf("rules without protocol"))
+			return true
+		}
+
+		switch {
+		case *ri.Ports[0].Protocol != *rj.Ports[0].Protocol:
+			return *ri.Ports[0].Protocol < *rj.Ports[0].Protocol
+		case ri.Ports[0].Port.IntVal != rj.Ports[0].Port.IntVal:
+			return ri.Ports[0].Port.IntVal < rj.Ports[0].Port.IntVal
+		default:
+			yamlOutput1, _ := k8syaml.Marshal(ri)
+			yamlOutput2, _ := k8syaml.Marshal(rj)
+			return string(yamlOutput1) < string(yamlOutput2)
+		}
+	})
+	return rules, errors.Join(errs...)
+}
+
+func handleEvents(eventsBySource map[string][]NetworkEvent) ([]networkingv1.NetworkPolicy, error) {
+	policies := make([]networkingv1.NetworkPolicy, 0, len(eventsBySource))
+
+	for _, events := range eventsBySource {
+		egressNetworkPeer := map[string]NetworkEvent{}
+		ingressNetworkPeer := map[string]NetworkEvent{}
+		for _, e := range events {
+			key, err := networkPeerKey(e)
+			if err != nil {
+				return nil, fmt.Errorf("generating network peer key: %w", err)
+			}
+			// api.Warnf("key for event with kind %s: %s", e.endpoint.Kind, key)
+			if e.egress {
+				if _, ok := egressNetworkPeer[key]; ok {
+					// api.Warnf("duplicate egress network peer: %s", key)
+					continue
+				}
+				egressNetworkPeer[key] = e
+			} else {
+				if _, ok := ingressNetworkPeer[key]; ok {
+					// api.Warnf("duplicate ingress network peer: %s", key)
+					continue
+				}
+				ingressNetworkPeer[key] = e
+			}
+		}
+		// api.Warnf("> Found %d egress network peers", len(egressNetworkPeer))
+		// api.Warnf("> Found %d ingress network peers", len(ingressNetworkPeer))
+
+		egressPolicies := []networkingv1.NetworkPolicyEgressRule{}
+		for _, p := range egressNetworkPeer {
+			ports, peers, err := eventToRule(p)
+			if err != nil {
+				return nil, fmt.Errorf("generating network policy egress rule: %w", err)
+			}
+			if len(peers) > 0 {
+				rule := networkingv1.NetworkPolicyEgressRule{
+					Ports: ports,
+					To:    peers,
+				}
+				egressPolicies = append(egressPolicies, rule)
+			}
+		}
+		ingressPolicies := []networkingv1.NetworkPolicyIngressRule{}
+		for _, p := range ingressNetworkPeer {
+			ports, peers, err := eventToRule(p)
+			if err != nil {
+				return nil, fmt.Errorf("generating network policy ingress rule: %w", err)
+			}
+			if len(peers) > 0 {
+				rule := networkingv1.NetworkPolicyIngressRule{
+					Ports: ports,
+					From:  peers,
+				}
+				ingressPolicies = append(ingressPolicies, rule)
+			}
+		}
+
+		name := events[0].K8s.PodName
+		if events[0].K8s.Owner.Name != "" {
+			name = events[0].K8s.Owner.Name
+		}
+		ingressRules, err := sortIngressRules(ingressPolicies)
+		if err != nil {
+			return nil, fmt.Errorf("sorting ingress rules: %w", err)
+		}
+		egressRules, err := sortEgressRules(egressPolicies)
+		if err != nil {
+			return nil, fmt.Errorf("sorting egress rules: %w", err)
+		}
+		name += "-network"
+		policy := networkingv1.NetworkPolicy{
+			TypeMeta: metav1.TypeMeta{
+				APIVersion: "networking.k8s.io/v1",
+				Kind:       "NetworkPolicy",
+			},
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      name,
+				Namespace: events[0].K8s.Namespace,
+				Labels:    map[string]string{},
+			},
+			Spec: networkingv1.NetworkPolicySpec{
+				PodSelector: metav1.LabelSelector{MatchLabels: labelFilter(events[0].K8s.PodLabels)},
+				PolicyTypes: []networkingv1.PolicyType{"Ingress", "Egress"},
+				Ingress:     ingressRules,
+				Egress:      egressRules,
+			},
+		}
+		policies = append(policies, policy)
+	}
+
+	sort.Slice(policies, func(i, j int) bool {
+		return policies[i].Name < policies[j].Name
+	})
+
+	return policies, nil
+}
+
+func FormatPolicies(policies []networkingv1.NetworkPolicy) (out string) {
+	for i, p := range policies {
+		// api.Warnf("policy %d: %s", i, p.Name)
+		yamlOutput, err := k8syaml.Marshal(p)
+		if err != nil {
+			// api.Warnf("marshalling policy: %s", err)
+			continue
+		}
+		sep := "---\n"
+		if i == len(policies)-1 {
+			sep = ""
+		}
+		out += fmt.Sprintf("%s%s", string(yamlOutput), sep)
+	}
+	return
+}
diff --git a/pkg/operators/generate_networkpolicy/generate_networkpolicy_op.go b/pkg/operators/generate_networkpolicy/generate_networkpolicy_op.go
new file mode 100644
index 00000000000..512fcfa8be1
--- /dev/null
+++ b/pkg/operators/generate_networkpolicy/generate_networkpolicy_op.go
@@ -0,0 +1,299 @@
+// Copyright 2025 The Inspektor Gadget authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package generate_networkpolicy provides an operator that generates network policies
+// based on the network traffic observed in the cluster.
+// This is a temporary solution until we have a way of running gadget code on the client side
+package generate_networkpolicy
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/datasource"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-service/api"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/operators"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/params"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/types"
+)
+
+const (
+	name     = "GenerateNetworkPolicy"
+	Priority = 9200
+)
+
+type gnpOperator struct{}
+
+func (s *gnpOperator) Name() string {
+	return name
+}
+
+func (s *gnpOperator) Init(params *params.Params) error {
+	return nil
+}
+
+func (s *gnpOperator) GlobalParams() api.Params {
+	return nil
+}
+
+func (s *gnpOperator) InstanceParams() api.Params {
+	return nil
+}
+
+type k8sAccesors struct {
+	k8sHostNetwork       datasource.FieldAccessor
+	k8sNamespace         datasource.FieldAccessor
+	k8sPodLabels         datasource.FieldAccessor
+	k8sPodIP             datasource.FieldAccessor
+	k8sPodName           datasource.FieldAccessor
+	k8sOwnerName         datasource.FieldAccessor
+	endpointAddr         datasource.FieldAccessor
+	endpointPort         datasource.FieldAccessor
+	endpointK8sKind      datasource.FieldAccessor
+	endpointK8sName      datasource.FieldAccessor
+	endpointK8sNamespace datasource.FieldAccessor
+	endpointK8sLabels    datasource.FieldAccessor
+	endpointProto        datasource.FieldAccessor
+	egress               datasource.FieldAccessor
+
+	adviseDS    datasource.DataSource
+	adviseField datasource.FieldAccessor
+}
+
+func (s *gnpOperator) getAccessors(gadgetCtx operators.GadgetContext) (map[datasource.DataSource]k8sAccesors, error) {
+	logger := gadgetCtx.Logger()
+	accessors := make(map[datasource.DataSource]k8sAccesors)
+	for _, ds := range gadgetCtx.GetDataSources() {
+		logger.Debugf("GenerateNetworkPolicy inspecting datasource %q", ds.Name())
+
+		if ds.Annotations()["generate_networkpolicy.enable"] != "true" {
+			logger.Debugf("GenerateNetworkPolicy not enabled by annotation")
+			continue
+		}
+
+		acc := k8sAccesors{}
+
+		acc.k8sHostNetwork = ds.GetField("k8s.hostnetwork")
+		if acc.k8sHostNetwork == nil {
+			return nil, fmt.Errorf("no hostnetwork field found")
+		}
+		acc.k8sNamespace = ds.GetField("k8s.namespace")
+		if acc.k8sNamespace == nil {
+			return nil, fmt.Errorf("no namespace field found")
+		}
+		acc.k8sPodLabels = ds.GetField("k8s.podLabels")
+		if acc.k8sPodLabels == nil {
+			return nil, fmt.Errorf("no podLabels field found")
+		}
+		acc.k8sPodIP = ds.GetField("k8s.podIP")
+		if acc.k8sPodIP == nil {
+			return nil, fmt.Errorf("no podIP field found")
+		}
+		acc.k8sPodName = ds.GetField("k8s.podName")
+		if acc.k8sPodName == nil {
+			return nil, fmt.Errorf("no podName field found")
+		}
+		acc.k8sOwnerName = ds.GetField("k8s.owner.name")
+		if acc.k8sOwnerName == nil {
+			return nil, fmt.Errorf("no owner.name field found")
+		}
+		acc.endpointAddr = ds.GetField("endpoint.addr")
+		if acc.endpointAddr == nil {
+			return nil, fmt.Errorf("no endpoint.addr field found")
+		}
+		acc.endpointPort = ds.GetField("endpoint.port")
+		if acc.endpointPort == nil {
+			return nil, fmt.Errorf("no endpoint.port field found")
+		}
+		acc.endpointK8sKind = ds.GetField("endpoint.k8s.kind")
+		if acc.endpointK8sKind == nil {
+			return nil, fmt.Errorf("no endpoint.k8s.kind field found")
+		}
+		acc.endpointK8sName = ds.GetField("endpoint.k8s.name")
+		if acc.endpointK8sName == nil {
+			return nil, fmt.Errorf("no endpoint.k8s.name field found")
+		}
+		acc.endpointK8sNamespace = ds.GetField("endpoint.k8s.namespace")
+		if acc.endpointK8sNamespace == nil {
+			return nil, fmt.Errorf("no endpoint.k8s.namespace field found")
+		}
+		acc.endpointK8sLabels = ds.GetField("endpoint.k8s.labels")
+		if acc.endpointK8sLabels == nil {
+			return nil, fmt.Errorf("no endpoint.k8s.labels field found")
+		}
+		acc.endpointProto = ds.GetField("endpoint.proto")
+		if acc.endpointProto == nil {
+			return nil, fmt.Errorf("no endpoint.proto field found")
+		}
+		acc.egress = ds.GetField("egress")
+		if acc.egress == nil {
+			return nil, fmt.Errorf("no egress field found")
+		}
+
+		// Disable datasource for other operators
+		ds.Unreference()
+
+		var err error
+		acc.adviseDS, err = gadgetCtx.RegisterDataSource(
+			datasource.TypeSingle,
+			fmt.Sprintf("advise-%s", ds.Name()),
+		)
+		if err != nil {
+			return nil, fmt.Errorf("registering policies data source for %s: %w", acc.adviseDS.Name(), err)
+		}
+		gadgetCtx.Logger().Debugf("GenerateNetworkPolicy: registered ds %q", acc.adviseDS.Name())
+
+		acc.adviseDS.AddAnnotation("cli.default-output-mode", "advise")
+		acc.adviseDS.AddAnnotation("cli.supported-output-modes", "advise")
+
+		acc.adviseField, err = acc.adviseDS.AddField("text", api.Kind_String)
+		if err != nil {
+			return nil, fmt.Errorf("adding field %q: %w", "text", err)
+		}
+
+		accessors[ds] = acc
+	}
+	return accessors, nil
+}
+
+func (s *gnpOperator) InstantiateDataOperator(gadgetCtx operators.GadgetContext, instanceParamValues api.ParamValues) (operators.DataOperatorInstance, error) {
+	accessors, err := s.getAccessors(gadgetCtx)
+	if err != nil {
+		return nil, fmt.Errorf("getting accessors: %w", err)
+	}
+	if len(accessors) == 0 {
+		gadgetCtx.Logger().Debug("GenerateNetworkPolicy: no datasources requiring the operator found")
+		return nil, nil
+	}
+	return &gnpOperatorInstance{
+		accessors: accessors,
+	}, nil
+}
+
+func (s *gnpOperator) Priority() int {
+	return Priority
+}
+
+type gnpOperatorInstance struct {
+	accessors map[datasource.DataSource]k8sAccesors
+}
+
+func (s *gnpOperatorInstance) Name() string {
+	return name + "Instance"
+}
+
+func (s *gnpOperatorInstance) PreStart(gadgetCtx operators.GadgetContext) error {
+	for ds, acc := range s.accessors {
+		ds.SubscribeArray(func(source datasource.DataSource, packet datasource.DataArray) error {
+			eventsBySource := map[string][]NetworkEvent{}
+			for i := range packet.Len() {
+				data := packet.Get(i)
+				k8sLabelsRaw, _ := acc.k8sPodLabels.String(data)
+				k8sLabelPairs := strings.Split(k8sLabelsRaw, ",")
+
+				hostNetwork, _ := acc.k8sHostNetwork.Bool(data)
+				if hostNetwork {
+					continue
+				}
+
+				e := NetworkEvent{
+					endpoint: types.L4Endpoint{
+						L3Endpoint: types.L3Endpoint{
+							PodLabels: map[string]string{},
+						},
+					},
+					K8s: types.K8sMetadata{
+						BasicK8sMetadata: types.BasicK8sMetadata{
+							PodLabels: map[string]string{},
+						},
+					},
+				}
+
+				egressRaw, _ := acc.egress.Uint8(data)
+				e.egress = egressRaw != 0
+				e.endpoint.Addr, _ = acc.endpointAddr.String(data)
+				e.endpoint.Port, _ = acc.endpointPort.Uint16(data)
+				e.endpoint.Name, _ = acc.endpointK8sName.String(data)
+				e.endpoint.Namespace, _ = acc.endpointK8sNamespace.String(data)
+				e.proto, _ = acc.endpointProto.String(data)
+
+				endpointEndpointStr, _ := acc.endpointK8sKind.String(data)
+				e.endpoint.Kind = types.EndpointKind(endpointEndpointStr)
+
+				endpointK8sPodLabelsRaw, _ := acc.endpointK8sLabels.String(data)
+				endpointK8sLabelPairs := strings.Split(endpointK8sPodLabelsRaw, ",")
+				for _, pair := range endpointK8sLabelPairs {
+					kv := strings.Split(pair, "=")
+					if len(kv) != 2 {
+						continue
+					}
+					e.endpoint.PodLabels[kv[0]] = kv[1]
+				}
+
+				e.K8s.PodName, _ = acc.k8sPodName.String(data)
+				e.K8s.Owner.Name, _ = acc.k8sOwnerName.String(data)
+				e.K8s.HostNetwork = hostNetwork
+				e.K8s.Namespace, _ = acc.k8sNamespace.String(data)
+				for _, pair := range k8sLabelPairs {
+					kv := strings.Split(pair, "=")
+					if len(kv) != 2 {
+						continue
+					}
+					e.K8s.PodLabels[kv[0]] = kv[1]
+				}
+
+				// Kubernetes Network Policies can't block traffic from a pod's
+				// own resident node. Therefore we must not generate a network
+				// policy in that case.
+				podIP, _ := acc.k8sPodIP.String(data)
+				if !e.egress && podIP == e.endpoint.Addr {
+					continue
+				}
+
+				key := localPodKey(e)
+				eventsBySource[key] = append(eventsBySource[key], e)
+			}
+
+			if len(eventsBySource) != 0 {
+				// api.Warnf("Got %d events by source", len(eventsBySource))
+				policies, err := handleEvents(eventsBySource)
+				if err != nil {
+					return fmt.Errorf("handling events: %w", err)
+				}
+				// api.Warnf("> Created %d policies", len(policies))
+				policiesStr := FormatPolicies(policies)
+				//// api.Warnf("> Policies:\n%s", policiesStr[:100])
+
+				yamlPack, err := acc.adviseDS.NewPacketSingle()
+				if err != nil {
+					return fmt.Errorf("creating packet: %w", err)
+				}
+				acc.adviseField.PutString(yamlPack, policiesStr)
+				acc.adviseDS.EmitAndRelease(yamlPack)
+			}
+			return nil
+		}, 0)
+	}
+	return nil
+}
+
+func (s *gnpOperatorInstance) Start(gadgetCtx operators.GadgetContext) error {
+	return nil
+}
+
+func (s *gnpOperatorInstance) Stop(gadgetCtx operators.GadgetContext) error {
+	return nil
+}
+
+var GNPOperator = &gnpOperator{}


Reviews:
user: login: mauriciovasquezbernal
body: Two big comments about the general implementation. I'll go deeper once we agree on them.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T18:51:18Z
user: login: burak-ok
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T09:52:37Z
user: login: mauriciovasquezbernal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T14:43:02Z
user: login: burak-ok
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T15:15:27Z
user: login: burak-ok
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T12:22:03Z
user: login: mauriciovasquezbernal
body: It's looking very nice. I did a pass on the last commit, I'll check prerequisites and will make another pass once they're in.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T17:05:36Z
user: login: burak-ok
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T10:42:26Z
user: login: burak-ok
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T10:50:08Z
user: login: mauriciovasquezbernal
body: LGTM. Thanks! 

Some minor nits around, but they aren't a blocker. I didn't check the combiner commit, but I see you already got an approval on that PR.


state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-23T16:37:19Z
user: login: mauriciovasquezbernal
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-23T16:38:03Z

Review comments:
path: gadgets/advise_networkpolicy/artifacthub-pkg.yml
user: login: mauriciovasquezbernal
body: Please update this file
created_at: 2025-05-09T16:43:30Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,10 @@
+name: advise networkpolicy
+description: Network policies according to the K8s traffic
path: gadgets/advise_networkpolicy/gadget.yaml
user: login: mauriciovasquezbernal
body: ```suggestion
description: Generate network policies according to the K8s traffic
```
created_at: 2025-05-09T16:44:05Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,145 @@
+---
+title: advise networkpolicy
+sidebar_position: 0
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+The advise networkpolicy gadget monitors the network activity in the specified namespaces
+and records a summary of TCP and UDP traffic. This is then used to generate Kubernetes
+network policies.
+
+## Getting started
+
+
+```bash
+$ kubectl gadget run ghcr.io/inspektor-gadget/gadget/advise_networkpolicy:%IG_TAG%
+```
+
+## Flags
+
+No flags.
+
+## Guide
+
+First, we need to create an nginx deployment which can respond to our test requests. 
+
+```bash
+$ kubectl create service nodeport nginx --tcp=80:80
+service/nginx created
+$ kubectl create deployment nginx --image=nginx
+deployment.apps/nginx created
+```
+
+Then, start the advise_networkpolicy gadget in another terminal, redirecting its output to a textfile.
+
+```bash
+$ kubectl gadget run advise_seccomp:%IG_TAG% > events.json
path: gadgets/advise_networkpolicy/README.mdx
user: login: mauriciovasquezbernal
body: ```suggestion
$ kubectl gadget run advise_networkpolicy:%IG_TAG% > events.json
```


created_at: 2025-05-09T16:56:57Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,145 @@
+---
+title: advise networkpolicy
+sidebar_position: 0
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+The advise networkpolicy gadget monitors the network activity in the specified namespaces
+and records a summary of TCP and UDP traffic. This is then used to generate Kubernetes
+network policies.
+
+## Getting started
+
+
+```bash
+$ kubectl gadget run ghcr.io/inspektor-gadget/gadget/advise_networkpolicy:%IG_TAG%
+```
+
+## Flags
+
+No flags.
+
+## Guide
+
+First, we need to create an nginx deployment which can respond to our test requests. 
+
+```bash
+$ kubectl create service nodeport nginx --tcp=80:80
+service/nginx created
+$ kubectl create deployment nginx --image=nginx
+deployment.apps/nginx created
+```
+
+Then, start the advise_networkpolicy gadget in another terminal, redirecting its output to a textfile.
+
+```bash
+$ kubectl gadget run advise_seccomp:%IG_TAG% > events.json
+```
+
+Now we can deploy our client pod, send a request to our nginx deployment and `1.1.1.1` and then quit.
+
+```bash
+$ kubectl run --rm -ti --image busybox test-pod          
+If you don't see a command prompt, try pressing enter.
+/ # wget nginx
+Connecting to nginx (10.105.129.249:80)
+saving to 'index.html'
+index.html           100% |********************************|   615  0:00:00 ETA
+'index.html' saved
+/ # wget 1.1.1.1
+Connecting to 1.1.1.1 (1.1.1.1:80)
+Connecting to 1.1.1.1 (1.1.1.1:443)
+wget: note: TLS certificate validation not implemented
+Connecting to one.one.one.one (1.1.1.1:443)
+wget: can't open 'index.html': File exists
+/ # exit
+Session ended, resume using 'kubectl attach test-pod -c test-pod -i -t' command when the pod is running
+pod "test-pod" deleted
+```
+
+Let's switch back to the gadget terminal, stop our gadget and generate our policy.
+
+```bash
+$ kubectl gadget run advise_seccomp:%IG_TAG% > events.json
path: gadgets/advise_networkpolicy/README.mdx
user: login: mauriciovasquezbernal
body: ```suggestion
$ kubectl gadget run advise_networkpolicy:%IG_TAG% > events.json
```
created_at: 2025-05-09T16:57:24Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,145 @@
+---
+title: advise networkpolicy
+sidebar_position: 0
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+The advise networkpolicy gadget monitors the network activity in the specified namespaces
+and records a summary of TCP and UDP traffic. This is then used to generate Kubernetes
+network policies.
+
+## Getting started
+
+
+```bash
+$ kubectl gadget run ghcr.io/inspektor-gadget/gadget/advise_networkpolicy:%IG_TAG%
+```
+
+## Flags
+
+No flags.
+
+## Guide
+
+First, we need to create an nginx deployment which can respond to our test requests. 
+
+```bash
+$ kubectl create service nodeport nginx --tcp=80:80
+service/nginx created
+$ kubectl create deployment nginx --image=nginx
+deployment.apps/nginx created
+```
+
+Then, start the advise_networkpolicy gadget in another terminal, redirecting its output to a textfile.
+
+```bash
+$ kubectl gadget run advise_seccomp:%IG_TAG% > events.json
+```
+
+Now we can deploy our client pod, send a request to our nginx deployment and `1.1.1.1` and then quit.
+
+```bash
+$ kubectl run --rm -ti --image busybox test-pod          
+If you don't see a command prompt, try pressing enter.
+/ # wget nginx
+Connecting to nginx (10.105.129.249:80)
+saving to 'index.html'
+index.html           100% |********************************|   615  0:00:00 ETA
+'index.html' saved
+/ # wget 1.1.1.1
+Connecting to 1.1.1.1 (1.1.1.1:80)
+Connecting to 1.1.1.1 (1.1.1.1:443)
+wget: note: TLS certificate validation not implemented
+Connecting to one.one.one.one (1.1.1.1:443)
+wget: can't open 'index.html': File exists
+/ # exit
+Session ended, resume using 'kubectl attach test-pod -c test-pod -i -t' command when the pod is running
+pod "test-pod" deleted
+```
+
+Let's switch back to the gadget terminal, stop our gadget and generate our policy.
+
+```bash
+$ kubectl gadget run advise_seccomp:%IG_TAG% > events.json
+...
+^C
+$ kubectl-gadget tool report-network-policy --input events.json
path: gadgets/advise_networkpolicy/README.mdx
user: login: mauriciovasquezbernal
body: ```suggestion
$ kubectl gadget tool report-network-policy --input events.json
```
created_at: 2025-05-09T16:58:06Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 The Inspektor Gadget authors */
+#include <linux/bpf.h>
+#include <linux/if_ether.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/in.h>
+#include <linux/tcp.h>
+#include <linux/types.h>
+#include <linux/udp.h>
+#include <stdbool.h>
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#include <gadget/buffer.h>
+#include <gadget/macros.h>
+#include <gadget/types.h>
+#include <gadget/maps.bpf.h>
+
+#define GADGET_TYPE_NETWORKING
+#include <gadget/sockets-map.h>
+
+#define PACKET_HOST 0
+#define PACKET_OUTGOING 4
+
+struct event {
+	gadget_netns_id netns_id;
+	gadget_timestamp timestamp_raw;
+
+	struct gadget_process proc;
+
+	struct gadget_l4endpoint_t endpoint;
+	__u8 egress;
+};
+
+GADGET_TRACER_MAP(events, 1024 * 256);
+GADGET_TRACER(networkpolicy, events, event);
+
+SEC("socket1")
+int ig_trace_net(struct __sk_buff *skb)
+{
+	struct event *event;
+
+	// Skip multicast, broadcast, forwarding...
+	if (skb->pkt_type != PACKET_HOST && skb->pkt_type != PACKET_OUTGOING)
+		return 0;
+
+	// Skip frames with non-IP Ethernet protocol.
+	struct ethhdr ethh;
+	if (bpf_skb_load_bytes(skb, 0, &ethh, sizeof ethh))
+		return 0;
+	if (bpf_ntohs(ethh.h_proto) != ETH_P_IP)
+		return 0;
+
+	int ip_off = ETH_HLEN;
+	// Read the IP header.
+	struct iphdr iph;
+	if (bpf_skb_load_bytes(skb, ip_off, &iph, sizeof iph))
+		return 0;
+
+	// An IPv4 header doesn't have a fixed size. The IHL field of a packet
+	// represents the size of the IP header in 32-bit words, so we need to
+	// multiply this value by 4 to get the header size in bytes.
+	__u8 ip_header_len = iph.ihl * 4;
+	int l4_off = ip_off + ip_header_len;
+	__u16 port;
+
+	if (iph.protocol == IPPROTO_TCP) {
+		// Read the TCP header.
+		struct tcphdr tcph;
+		if (bpf_skb_load_bytes(skb, l4_off, &tcph, sizeof tcph))
+			return 0;
+
+		if (!tcph.syn || tcph.ack)
+			return 0;
+
+		port = bpf_htons(tcph.dest);
+	} else if (iph.protocol == IPPROTO_UDP) {
+		// Read the UDP header.
+		struct udphdr udph;
+		if (bpf_skb_load_bytes(skb, l4_off, &udph, sizeof udph))
+			return 0;
+
+		// UDP packets don't have a TCP-SYN to identify the direction.
+		// Check usage of dynamic ports instead.
+		// https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
+		// System Ports: 0-1023
+		// User Ports: 1024-49151
+		// Dynamic and/or Private Ports: 49152-65535
+		// However, Linux uses ephemeral ports: 32768-60999 (/proc/sys/net/ipv4/ip_local_port_range)
+		// And /proc/sys/net/ipv4/ip_unprivileged_port_start: 1024
+		if (bpf_htons(udph.dest) < 1024)
+			port = bpf_htons(udph.dest);
+		else
+			return 0;
+	} else {
+		// Skip packets with IP protocol other than TCP/UDP.
+		return 0;
+	}
+
+	event = gadget_reserve_buf(&events, sizeof(*event));
+	if (!event)
+		return 0;
+
+	__builtin_memset(event, 0, sizeof(*event));
+	event->netns_id = skb->cb[0]; // cb[0] initialized by dispatcher.bpf.c
+	event->timestamp_raw = bpf_ktime_get_boot_ns();
+
+	if (skb->pkt_type == PACKET_HOST) {
+		event->endpoint.addr_raw.v4 = iph.saddr;
+	} else {
+		event->endpoint.addr_raw.v4 = iph.daddr;
+	}
+	event->endpoint.proto_raw = iph.protocol;
+	event->endpoint.port = port;
+	event->endpoint.version = 4;
+	event->egress = skb->pkt_type == PACKET_OUTGOING;
+
+	struct sockets_value *skb_val = gadget_socket_lookup(skb);
+	gadget_process_populate_from_socket(skb_val, &event->proc);
path: gadgets/advise_networkpolicy/program.bpf.c
user: login: mauriciovasquezbernal
body: Do we we need process information on this gadget?
created_at: 2025-05-09T18:29:39Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 The Inspektor Gadget authors */
+#include <linux/bpf.h>
+#include <linux/if_ether.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/in.h>
+#include <linux/tcp.h>
+#include <linux/types.h>
+#include <linux/udp.h>
+#include <stdbool.h>
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#include <gadget/buffer.h>
+#include <gadget/macros.h>
+#include <gadget/types.h>
+#include <gadget/maps.bpf.h>
+
+#define GADGET_TYPE_NETWORKING
+#include <gadget/sockets-map.h>
+
+#define PACKET_HOST 0
+#define PACKET_OUTGOING 4
+
+struct event {
+	gadget_netns_id netns_id;
+	gadget_timestamp timestamp_raw;
+
+	struct gadget_process proc;
+
+	struct gadget_l4endpoint_t endpoint;
+	__u8 egress;
+};
+
+GADGET_TRACER_MAP(events, 1024 * 256);
+GADGET_TRACER(networkpolicy, events, event);
path: gadgets/advise_networkpolicy/program.bpf.c
user: login: mauriciovasquezbernal
body: I know the original gadget was implemented as a tracer, but I'm now wondering if we can use MAP_ITER here to make it more efficient, in the end we don't care about sending single connection events but about the whole list of connections to generate the policy.
created_at: 2025-05-09T18:31:16Z
author_association: MEMBER
path: cmd/kubectl-gadget/tool/advise-network-policy.go
user: login: mauriciovasquezbernal
body: Could it be implemented as an operator (only on the client part for kubectl-gadget) so users don't need to run two commands to generate the policy?

(Sorry for not mentioning it before when we discussed about this topic, but only while reviewing it I realized it could work on that way).
created_at: 2025-05-09T18:32:07Z
author_association: MEMBER
path: cmd/kubectl-gadget/tool/advise-network-policy.go
user: login: burak-ok
body: I really don't know if I like having a specific operator for a specific gadget. That argument could also be against having the specific `tool` command though.

IDK, for me an operator baked into IG is something kind of general in my head.
created_at: 2025-05-19T09:52:37Z
author_association: MEMBER
path: cmd/kubectl-gadget/tool/advise-network-policy.go
user: login: mauriciovasquezbernal
body: I know both solutions aren't ideal, but if we can make the users' life easier by avoiding them having to run an extra command that'd be better. 

In any case, let's get some more opinions from @alban and @flyth.
created_at: 2025-05-19T14:43:02Z
author_association: MEMBER
path: cmd/kubectl-gadget/tool/advise-network-policy.go
user: login: burak-ok
body: >  but if we can make the users' life easier by avoiding them having to run an extra command that'd be better.

Definitaly it would be a way better UX +1
created_at: 2025-05-19T15:15:27Z
author_association: MEMBER
path: cmd/kubectl-gadget/tool/advise-network-policy.go
user: login: burak-ok
body: Changed it to be an operator
created_at: 2025-05-22T12:22:03Z
author_association: MEMBER
diff_hunk: @@ -173,7 +174,7 @@ func NewRunCommand(rootCmd *cobra.Command, runtime runtime.Runtime, hiddenColumn
 			}
 			ops = append(ops, op)
 		}
-		ops = append(ops, clioperator.CLIOperator, combiner.CombinerOperator)
+		ops = append(ops, clioperator.CLIOperator, combiner.CombinerOperator, generate_networkpolicy.GNPOperator)
path: cmd/common/oci.go
user: login: mauriciovasquezbernal
body: How and why we ended up with a list of operators what is get from `GetDataOperators()` and another hardcoded list of singletons (cli, combiner and now gnp)?

(You don't need to answer it, it's more a reflection to myself that I want to write down so I don't forget about it).
created_at: 2025-05-22T16:12:56Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 The Inspektor Gadget authors */
+#include <linux/bpf.h>
+#include <linux/if_ether.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/in.h>
+#include <linux/tcp.h>
+#include <linux/types.h>
+#include <linux/udp.h>
+#include <stdbool.h>
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#include <gadget/buffer.h>
+#include <gadget/macros.h>
+#include <gadget/types.h>
+#include <gadget/maps.bpf.h>
+
+#define GADGET_TYPE_NETWORKING
+#include <gadget/sockets-map.h>
+
+#define PACKET_HOST 0
+#define PACKET_OUTGOING 4
+
+struct event_t {
+	gadget_netns_id netns_id;
+	gadget_timestamp timestamp_raw;
+
+	struct gadget_l4endpoint_t endpoint;
+	__u8 egress;
+};
+
+struct empty_t {
+	__u8 unused;
+};
+
+const struct empty_t zero = {};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_HASH);
+	__uint(max_entries, 10240);
+	__type(key, struct event_t);
+	__type(value, struct empty_t);
+} packets SEC(".maps");
+
+GADGET_MAPITER(events, packets);
path: gadgets/advise_networkpolicy/program.bpf.c
user: login: mauriciovasquezbernal
body: nit: events -> advise_network, or network_connections. 
created_at: 2025-05-22T16:17:27Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 The Inspektor Gadget authors */
+#include <linux/bpf.h>
+#include <linux/if_ether.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/in.h>
+#include <linux/tcp.h>
+#include <linux/types.h>
+#include <linux/udp.h>
+#include <stdbool.h>
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#include <gadget/buffer.h>
+#include <gadget/macros.h>
+#include <gadget/types.h>
+#include <gadget/maps.bpf.h>
+
+#define GADGET_TYPE_NETWORKING
+#include <gadget/sockets-map.h>
+
+#define PACKET_HOST 0
+#define PACKET_OUTGOING 4
+
+struct event_t {
+	gadget_netns_id netns_id;
+	gadget_timestamp timestamp_raw;
+
+	struct gadget_l4endpoint_t endpoint;
+	__u8 egress;
+};
+
+struct empty_t {
+	__u8 unused;
+};
+
+const struct empty_t zero = {};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_HASH);
+	__uint(max_entries, 10240);
+	__type(key, struct event_t);
+	__type(value, struct empty_t);
path: gadgets/advise_networkpolicy/program.bpf.c
user: login: mauriciovasquezbernal
body: is there any issue if __u8 is used directly as the value type? If yes, please document it.
created_at: 2025-05-22T16:18:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 The Inspektor Gadget authors */
+#include <linux/bpf.h>
+#include <linux/if_ether.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/in.h>
+#include <linux/tcp.h>
+#include <linux/types.h>
+#include <linux/udp.h>
+#include <stdbool.h>
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#include <gadget/buffer.h>
+#include <gadget/macros.h>
+#include <gadget/types.h>
+#include <gadget/maps.bpf.h>
+
+#define GADGET_TYPE_NETWORKING
+#include <gadget/sockets-map.h>
+
+#define PACKET_HOST 0
+#define PACKET_OUTGOING 4
+
+struct event_t {
+	gadget_netns_id netns_id;
+	gadget_timestamp timestamp_raw;
path: gadgets/advise_networkpolicy/program.bpf.c
user: login: mauriciovasquezbernal
body: I think we should remove this from the key, so it doesn't generate a different key for each connection. In the end, we don't care about the number of connections but only if a connection was done. 
created_at: 2025-05-22T16:22:10Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 The Inspektor Gadget authors */
+#include <linux/bpf.h>
+#include <linux/if_ether.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/in.h>
+#include <linux/tcp.h>
+#include <linux/types.h>
+#include <linux/udp.h>
+#include <stdbool.h>
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#include <gadget/buffer.h>
+#include <gadget/macros.h>
+#include <gadget/types.h>
+#include <gadget/maps.bpf.h>
+
+#define GADGET_TYPE_NETWORKING
+#include <gadget/sockets-map.h>
+
+#define PACKET_HOST 0
+#define PACKET_OUTGOING 4
+
+struct event_t {
+	gadget_netns_id netns_id;
+	gadget_timestamp timestamp_raw;
+
+	struct gadget_l4endpoint_t endpoint;
+	__u8 egress;
+};
+
+struct empty_t {
+	__u8 unused;
+};
+
+const struct empty_t zero = {};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_HASH);
+	__uint(max_entries, 10240);
+	__type(key, struct event_t);
+	__type(value, struct empty_t);
+} packets SEC(".maps");
+
+GADGET_MAPITER(events, packets);
+
+SEC("socket1")
+int ig_trace_net(struct __sk_buff *skb)
+{
+	// Skip multicast, broadcast, forwarding...
+	if (skb->pkt_type != PACKET_HOST && skb->pkt_type != PACKET_OUTGOING)
+		return 0;
+
+	// Skip frames with non-IP Ethernet protocol.
+	struct ethhdr ethh;
+	if (bpf_skb_load_bytes(skb, 0, &ethh, sizeof ethh))
+		return 0;
+	if (bpf_ntohs(ethh.h_proto) != ETH_P_IP)
+		return 0;
+
+	int ip_off = ETH_HLEN;
+	// Read the IP header.
+	struct iphdr iph;
+	if (bpf_skb_load_bytes(skb, ip_off, &iph, sizeof iph))
+		return 0;
+
+	// An IPv4 header doesn't have a fixed size. The IHL field of a packet
+	// represents the size of the IP header in 32-bit words, so we need to
+	// multiply this value by 4 to get the header size in bytes.
+	__u8 ip_header_len = iph.ihl * 4;
+	int l4_off = ip_off + ip_header_len;
+	__u16 port;
+
+	if (iph.protocol == IPPROTO_TCP) {
+		// Read the TCP header.
+		struct tcphdr tcph;
+		if (bpf_skb_load_bytes(skb, l4_off, &tcph, sizeof tcph))
+			return 0;
+
+		if (!tcph.syn || tcph.ack)
+			return 0;
+
+		port = bpf_htons(tcph.dest);
+	} else if (iph.protocol == IPPROTO_UDP) {
+		// Read the UDP header.
+		struct udphdr udph;
+		if (bpf_skb_load_bytes(skb, l4_off, &udph, sizeof udph))
+			return 0;
+
+		// UDP packets don't have a TCP-SYN to identify the direction.
+		// Check usage of dynamic ports instead.
+		// https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
+		// System Ports: 0-1023
+		// User Ports: 1024-49151
+		// Dynamic and/or Private Ports: 49152-65535
+		// However, Linux uses ephemeral ports: 32768-60999 (/proc/sys/net/ipv4/ip_local_port_range)
+		// And /proc/sys/net/ipv4/ip_unprivileged_port_start: 1024
+		if (bpf_htons(udph.dest) < 1024)
+			port = bpf_htons(udph.dest);
+		else
+			return 0;
+	} else {
+		// Skip packets with IP protocol other than TCP/UDP.
+		return 0;
+	}
+
+	struct event_t event = {};
+	__builtin_memset(&event, 0, sizeof(event));
path: gadgets/advise_networkpolicy/program.bpf.c
user: login: mauriciovasquezbernal
body: We're filling all fields of this packet, this IMO isn't needed.
created_at: 2025-05-22T16:22:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,295 @@
+// Copyright 2025 The Inspektor Gadget authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package generate_networkpolicy
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/datasource"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-service/api"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/operators"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/params"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/types"
+)
+
+const (
+	name     = "GenerateNetworkPolicy"
+	Priority = 9200
+)
+
+type gnpOperator struct{}
+
+func (s *gnpOperator) Name() string {
+	return name
+}
+
+func (s *gnpOperator) Init(params *params.Params) error {
+	return nil
+}
+
+func (s *gnpOperator) GlobalParams() api.Params {
+	return nil
+}
+
+func (s *gnpOperator) InstanceParams() api.Params {
+	return nil
+}
+
+type k8sAccesors struct {
+	k8sHostNetwork       datasource.FieldAccessor
+	k8sNamespace         datasource.FieldAccessor
+	k8sPodLabels         datasource.FieldAccessor
+	k8sPodIP             datasource.FieldAccessor
+	k8sPodName           datasource.FieldAccessor
+	k8sOwnerName         datasource.FieldAccessor
+	endpointAddr         datasource.FieldAccessor
+	endpointPort         datasource.FieldAccessor
+	endpointK8sKind      datasource.FieldAccessor
+	endpointK8sName      datasource.FieldAccessor
+	endpointK8sNamespace datasource.FieldAccessor
+	endpointK8sLabels    datasource.FieldAccessor
+	endpointProto        datasource.FieldAccessor
+	egress               datasource.FieldAccessor
+
+	adviseDS    datasource.DataSource
+	adviseField datasource.FieldAccessor
+}
+
+func (s *gnpOperator) GetAccessors(gadgetCtx operators.GadgetContext) (map[datasource.DataSource]k8sAccesors, error) {
path: pkg/operators/generate_networkpolicy/generate_networkpolicy_op.go
user: login: mauriciovasquezbernal
body: nit: make it private.
created_at: 2025-05-22T16:24:54Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,295 @@
+// Copyright 2025 The Inspektor Gadget authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package generate_networkpolicy
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/datasource"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-service/api"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/operators"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/params"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/types"
+)
+
+const (
+	name     = "GenerateNetworkPolicy"
+	Priority = 9200
+)
+
+type gnpOperator struct{}
+
+func (s *gnpOperator) Name() string {
+	return name
+}
+
+func (s *gnpOperator) Init(params *params.Params) error {
+	return nil
+}
+
+func (s *gnpOperator) GlobalParams() api.Params {
+	return nil
+}
+
+func (s *gnpOperator) InstanceParams() api.Params {
+	return nil
+}
+
+type k8sAccesors struct {
+	k8sHostNetwork       datasource.FieldAccessor
+	k8sNamespace         datasource.FieldAccessor
+	k8sPodLabels         datasource.FieldAccessor
+	k8sPodIP             datasource.FieldAccessor
+	k8sPodName           datasource.FieldAccessor
+	k8sOwnerName         datasource.FieldAccessor
+	endpointAddr         datasource.FieldAccessor
+	endpointPort         datasource.FieldAccessor
+	endpointK8sKind      datasource.FieldAccessor
+	endpointK8sName      datasource.FieldAccessor
+	endpointK8sNamespace datasource.FieldAccessor
+	endpointK8sLabels    datasource.FieldAccessor
+	endpointProto        datasource.FieldAccessor
+	egress               datasource.FieldAccessor
+
+	adviseDS    datasource.DataSource
+	adviseField datasource.FieldAccessor
+}
+
+func (s *gnpOperator) GetAccessors(gadgetCtx operators.GadgetContext) (map[datasource.DataSource]k8sAccesors, error) {
+	logger := gadgetCtx.Logger()
+	accessors := make(map[datasource.DataSource]k8sAccesors)
+	for _, ds := range gadgetCtx.GetDataSources() {
+		logger.Debugf("GenerateNetworkPolicy inspecting datasource %q", ds.Name())
+
+		if ds.Annotations()["generate_networkpolicy.enable"] != "true" {
+			logger.Debugf("GenerateNetworkPolicy not enabled by annotation")
+			continue
+		}
+
+		acc := k8sAccesors{}
+
+		acc.k8sHostNetwork = ds.GetField("k8s.hostnetwork")
+		if acc.k8sHostNetwork == nil {
+			return nil, fmt.Errorf("no hostnetwork field found")
+		}
+		acc.k8sNamespace = ds.GetField("k8s.namespace")
+		if acc.k8sNamespace == nil {
+			return nil, fmt.Errorf("no namespace field found")
+		}
+		acc.k8sPodLabels = ds.GetField("k8s.podLabels")
+		if acc.k8sPodLabels == nil {
+			return nil, fmt.Errorf("no podLabels field found")
+		}
+		acc.k8sPodIP = ds.GetField("k8s.podIP")
+		if acc.k8sPodIP == nil {
+			return nil, fmt.Errorf("no podIP field found")
+		}
+		acc.k8sPodName = ds.GetField("k8s.podName")
+		if acc.k8sPodName == nil {
+			return nil, fmt.Errorf("no podName field found")
+		}
+		acc.k8sOwnerName = ds.GetField("k8s.owner.name")
+		if acc.k8sOwnerName == nil {
+			return nil, fmt.Errorf("no owner.name field found")
+		}
+		acc.endpointAddr = ds.GetField("endpoint.addr")
+		if acc.endpointAddr == nil {
+			return nil, fmt.Errorf("no endpoint.addr field found")
+		}
+		acc.endpointPort = ds.GetField("endpoint.port")
+		if acc.endpointPort == nil {
+			return nil, fmt.Errorf("no endpoint.port field found")
+		}
+		acc.endpointK8sKind = ds.GetField("endpoint.k8s.kind")
+		if acc.endpointK8sKind == nil {
+			return nil, fmt.Errorf("no endpoint.k8s.kind field found")
+		}
+		acc.endpointK8sName = ds.GetField("endpoint.k8s.name")
+		if acc.endpointK8sName == nil {
+			return nil, fmt.Errorf("no endpoint.k8s.name field found")
+		}
+		acc.endpointK8sNamespace = ds.GetField("endpoint.k8s.namespace")
+		if acc.endpointK8sNamespace == nil {
+			return nil, fmt.Errorf("no endpoint.k8s.namespace field found")
+		}
+		acc.endpointK8sLabels = ds.GetField("endpoint.k8s.labels")
+		if acc.endpointK8sLabels == nil {
+			return nil, fmt.Errorf("no endpoint.k8s.labels field found")
+		}
+		acc.endpointProto = ds.GetField("endpoint.proto")
+		if acc.endpointProto == nil {
+			return nil, fmt.Errorf("no endpoint.proto field found")
+		}
+		acc.egress = ds.GetField("egress")
+		if acc.egress == nil {
+			return nil, fmt.Errorf("no egress field found")
+		}
+
+		// Disable datasource for other operators
+		ds.Unreference()
+
+		var err error
+		acc.adviseDS, err = gadgetCtx.RegisterDataSource(
+			datasource.TypeSingle,
+			fmt.Sprintf("advise-%s", ds.Name()),
+		)
+		if err != nil {
+			return nil, fmt.Errorf("registering policies data source for %s: %w", acc.adviseDS.Name(), err)
+		}
+		gadgetCtx.Logger().Debugf("GenerateNetworkPolicy: registered ds %q", acc.adviseDS.Name())
+
+		acc.adviseDS.AddAnnotation("cli.default-output-mode", "advise")
+		acc.adviseDS.AddAnnotation("cli.supported-output-modes", "advise")
+
+		acc.adviseField, err = acc.adviseDS.AddField("text", api.Kind_String)
+		if err != nil {
+			return nil, fmt.Errorf("adding field %q: %w", "text", err)
+		}
+
+		accessors[ds] = acc
+	}
+	return accessors, nil
+}
+
+func (s *gnpOperator) InstantiateDataOperator(gadgetCtx operators.GadgetContext, instanceParamValues api.ParamValues) (operators.DataOperatorInstance, error) {
+	accessors, err := s.GetAccessors(gadgetCtx)
+	if err != nil {
+		return nil, fmt.Errorf("getting accessors: %w", err)
+	}
+	if len(accessors) == 0 {
+		gadgetCtx.Logger().Debug("GenerateNetworkPolicy: no array data sources found.")
path: pkg/operators/generate_networkpolicy/generate_networkpolicy_op.go
user: login: mauriciovasquezbernal
body: `GenerateNetworkPolicy: no datasources requiring the operator found`
created_at: 2025-05-22T16:26:05Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,295 @@
+// Copyright 2025 The Inspektor Gadget authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package generate_networkpolicy
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/datasource"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-service/api"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/operators"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/params"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/types"
+)
+
+const (
+	name     = "GenerateNetworkPolicy"
+	Priority = 9200
+)
+
+type gnpOperator struct{}
+
+func (s *gnpOperator) Name() string {
+	return name
+}
+
+func (s *gnpOperator) Init(params *params.Params) error {
+	return nil
+}
+
+func (s *gnpOperator) GlobalParams() api.Params {
+	return nil
+}
+
+func (s *gnpOperator) InstanceParams() api.Params {
+	return nil
+}
+
+type k8sAccesors struct {
+	k8sHostNetwork       datasource.FieldAccessor
+	k8sNamespace         datasource.FieldAccessor
+	k8sPodLabels         datasource.FieldAccessor
+	k8sPodIP             datasource.FieldAccessor
+	k8sPodName           datasource.FieldAccessor
+	k8sOwnerName         datasource.FieldAccessor
+	endpointAddr         datasource.FieldAccessor
+	endpointPort         datasource.FieldAccessor
+	endpointK8sKind      datasource.FieldAccessor
+	endpointK8sName      datasource.FieldAccessor
+	endpointK8sNamespace datasource.FieldAccessor
+	endpointK8sLabels    datasource.FieldAccessor
+	endpointProto        datasource.FieldAccessor
+	egress               datasource.FieldAccessor
+
+	adviseDS    datasource.DataSource
+	adviseField datasource.FieldAccessor
+}
+
+func (s *gnpOperator) GetAccessors(gadgetCtx operators.GadgetContext) (map[datasource.DataSource]k8sAccesors, error) {
+	logger := gadgetCtx.Logger()
+	accessors := make(map[datasource.DataSource]k8sAccesors)
+	for _, ds := range gadgetCtx.GetDataSources() {
+		logger.Debugf("GenerateNetworkPolicy inspecting datasource %q", ds.Name())
+
+		if ds.Annotations()["generate_networkpolicy.enable"] != "true" {
+			logger.Debugf("GenerateNetworkPolicy not enabled by annotation")
+			continue
+		}
+
+		acc := k8sAccesors{}
+
+		acc.k8sHostNetwork = ds.GetField("k8s.hostnetwork")
+		if acc.k8sHostNetwork == nil {
+			return nil, fmt.Errorf("no hostnetwork field found")
+		}
+		acc.k8sNamespace = ds.GetField("k8s.namespace")
+		if acc.k8sNamespace == nil {
+			return nil, fmt.Errorf("no namespace field found")
+		}
+		acc.k8sPodLabels = ds.GetField("k8s.podLabels")
+		if acc.k8sPodLabels == nil {
+			return nil, fmt.Errorf("no podLabels field found")
+		}
+		acc.k8sPodIP = ds.GetField("k8s.podIP")
+		if acc.k8sPodIP == nil {
+			return nil, fmt.Errorf("no podIP field found")
+		}
+		acc.k8sPodName = ds.GetField("k8s.podName")
+		if acc.k8sPodName == nil {
+			return nil, fmt.Errorf("no podName field found")
+		}
+		acc.k8sOwnerName = ds.GetField("k8s.owner.name")
+		if acc.k8sOwnerName == nil {
+			return nil, fmt.Errorf("no owner.name field found")
+		}
+		acc.endpointAddr = ds.GetField("endpoint.addr")
+		if acc.endpointAddr == nil {
+			return nil, fmt.Errorf("no endpoint.addr field found")
+		}
+		acc.endpointPort = ds.GetField("endpoint.port")
+		if acc.endpointPort == nil {
+			return nil, fmt.Errorf("no endpoint.port field found")
+		}
+		acc.endpointK8sKind = ds.GetField("endpoint.k8s.kind")
+		if acc.endpointK8sKind == nil {
+			return nil, fmt.Errorf("no endpoint.k8s.kind field found")
+		}
+		acc.endpointK8sName = ds.GetField("endpoint.k8s.name")
+		if acc.endpointK8sName == nil {
+			return nil, fmt.Errorf("no endpoint.k8s.name field found")
+		}
+		acc.endpointK8sNamespace = ds.GetField("endpoint.k8s.namespace")
+		if acc.endpointK8sNamespace == nil {
+			return nil, fmt.Errorf("no endpoint.k8s.namespace field found")
+		}
+		acc.endpointK8sLabels = ds.GetField("endpoint.k8s.labels")
+		if acc.endpointK8sLabels == nil {
+			return nil, fmt.Errorf("no endpoint.k8s.labels field found")
+		}
+		acc.endpointProto = ds.GetField("endpoint.proto")
+		if acc.endpointProto == nil {
+			return nil, fmt.Errorf("no endpoint.proto field found")
+		}
+		acc.egress = ds.GetField("egress")
+		if acc.egress == nil {
+			return nil, fmt.Errorf("no egress field found")
+		}
+
+		// Disable datasource for other operators
+		ds.Unreference()
+
+		var err error
+		acc.adviseDS, err = gadgetCtx.RegisterDataSource(
+			datasource.TypeSingle,
+			fmt.Sprintf("advise-%s", ds.Name()),
+		)
+		if err != nil {
+			return nil, fmt.Errorf("registering policies data source for %s: %w", acc.adviseDS.Name(), err)
+		}
+		gadgetCtx.Logger().Debugf("GenerateNetworkPolicy: registered ds %q", acc.adviseDS.Name())
+
+		acc.adviseDS.AddAnnotation("cli.default-output-mode", "advise")
+		acc.adviseDS.AddAnnotation("cli.supported-output-modes", "advise")
+
+		acc.adviseField, err = acc.adviseDS.AddField("text", api.Kind_String)
+		if err != nil {
+			return nil, fmt.Errorf("adding field %q: %w", "text", err)
+		}
+
+		accessors[ds] = acc
+	}
+	return accessors, nil
+}
+
+func (s *gnpOperator) InstantiateDataOperator(gadgetCtx operators.GadgetContext, instanceParamValues api.ParamValues) (operators.DataOperatorInstance, error) {
+	accessors, err := s.GetAccessors(gadgetCtx)
+	if err != nil {
+		return nil, fmt.Errorf("getting accessors: %w", err)
+	}
+	if len(accessors) == 0 {
+		gadgetCtx.Logger().Debug("GenerateNetworkPolicy: no array data sources found.")
+		return nil, nil
+	}
+	return &gnpOperatorInstance{
+		accessors: accessors,
+	}, nil
+}
+
+func (s *gnpOperator) Priority() int {
+	return Priority
+}
+
+type gnpOperatorInstance struct {
+	accessors map[datasource.DataSource]k8sAccesors
+}
+
+func (s *gnpOperatorInstance) Name() string {
+	return name + "Instance"
+}
+
+func (s *gnpOperatorInstance) PreStart(gadgetCtx operators.GadgetContext) error {
+	for ds, acc := range s.accessors {
+		ds.SubscribeArray(func(source datasource.DataSource, packet datasource.DataArray) error {
+			eventsBySource := map[string][]NetworkEvent{}
+			for i := range packet.Len() {
+				data := packet.Get(i)
+				k8sLabelsRaw, _ := acc.k8sPodLabels.String(data)
+				k8sLabelPairs := strings.Split(k8sLabelsRaw, ",")
+
+				e := NetworkEvent{
+					endpoint: types.L4Endpoint{
+						L3Endpoint: types.L3Endpoint{
+							PodLabels: map[string]string{},
+						},
+					},
+					K8s: types.K8sMetadata{
+						BasicK8sMetadata: types.BasicK8sMetadata{
+							PodLabels: map[string]string{},
+						},
+					},
+				}
+
+				egressRaw, _ := acc.egress.Uint8(data)
+				e.egress = egressRaw != 0
+				e.endpoint.Addr, _ = acc.endpointAddr.String(data)
+				e.endpoint.Port, _ = acc.endpointPort.Uint16(data)
+				e.endpoint.Name, _ = acc.endpointK8sName.String(data)
+				e.endpoint.Namespace, _ = acc.endpointK8sNamespace.String(data)
+				e.proto, _ = acc.endpointProto.String(data)
+
+				endpointEndpointStr, _ := acc.endpointK8sKind.String(data)
+				e.endpoint.Kind = types.EndpointKind(endpointEndpointStr)
+
+				endpointK8sPodLabelsRaw, _ := acc.endpointK8sLabels.String(data)
+				endpointK8sLabelPairs := strings.Split(endpointK8sPodLabelsRaw, ",")
+				for _, pair := range endpointK8sLabelPairs {
+					kv := strings.Split(pair, "=")
+					if len(kv) != 2 {
+						continue
+					}
+					e.endpoint.PodLabels[kv[0]] = kv[1]
+				}
+
+				e.K8s.PodName, _ = acc.k8sPodName.String(data)
+				e.K8s.Owner.Name, _ = acc.k8sOwnerName.String(data)
+				e.K8s.HostNetwork, _ = acc.k8sHostNetwork.Bool(data)
+				e.K8s.Namespace, _ = acc.k8sNamespace.String(data)
+				for _, pair := range k8sLabelPairs {
+					kv := strings.Split(pair, "=")
+					if len(kv) != 2 {
+						continue
+					}
+					e.K8s.PodLabels[kv[0]] = kv[1]
+				}
+
+				if e.K8s.HostNetwork {
+					continue
+				}
path: pkg/operators/generate_networkpolicy/generate_networkpolicy_op.go
user: login: mauriciovasquezbernal
body: nit: should it be moved to the top to avoid wasting some cpu cycles?
created_at: 2025-05-22T16:27:57Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,144 @@
+---
+title: advise networkpolicy
+sidebar_position: 0
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+The advise networkpolicy gadget monitors the network activity in the specified namespaces
+and records a summary of TCP and UDP traffic. This is then used to generate Kubernetes
+network policies.
+
+## Getting started
+
+
+```bash
+$ kubectl gadget run ghcr.io/inspektor-gadget/gadget/advise_networkpolicy:%IG_TAG%
+```
+
+## Flags
+
+No flags.
+
+## Guide
+
+First, we need to create an nginx deployment which can respond to our test requests. 
+
+```bash
+$ kubectl create service nodeport nginx --tcp=80:80
+service/nginx created
+$ kubectl create deployment nginx --image=nginx
+deployment.apps/nginx created
+```
+
+Then, start the advise_networkpolicy gadget in another terminal.
+
+```bash
+$ kubectl gadget run advise_networkpolicy:%IG_TAG%
path: gadgets/advise_networkpolicy/README.mdx
user: login: mauriciovasquezbernal
body: It starts printing policies before closing it. Is it missing `--map-fetch-count=0 --map-fetch-interval=0`?
created_at: 2025-05-22T16:46:20Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,295 @@
+// Copyright 2025 The Inspektor Gadget authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package generate_networkpolicy
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/datasource"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-service/api"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/operators"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/params"
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/types"
+)
+
+const (
+	name     = "GenerateNetworkPolicy"
+	Priority = 9200
+)
+
+type gnpOperator struct{}
+
+func (s *gnpOperator) Name() string {
+	return name
+}
+
+func (s *gnpOperator) Init(params *params.Params) error {
+	return nil
+}
+
+func (s *gnpOperator) GlobalParams() api.Params {
+	return nil
+}
+
+func (s *gnpOperator) InstanceParams() api.Params {
+	return nil
+}
+
+type k8sAccesors struct {
+	k8sHostNetwork       datasource.FieldAccessor
+	k8sNamespace         datasource.FieldAccessor
+	k8sPodLabels         datasource.FieldAccessor
+	k8sPodIP             datasource.FieldAccessor
+	k8sPodName           datasource.FieldAccessor
+	k8sOwnerName         datasource.FieldAccessor
+	endpointAddr         datasource.FieldAccessor
+	endpointPort         datasource.FieldAccessor
+	endpointK8sKind      datasource.FieldAccessor
+	endpointK8sName      datasource.FieldAccessor
+	endpointK8sNamespace datasource.FieldAccessor
+	endpointK8sLabels    datasource.FieldAccessor
+	endpointProto        datasource.FieldAccessor
+	egress               datasource.FieldAccessor
+
+	adviseDS    datasource.DataSource
+	adviseField datasource.FieldAccessor
+}
+
+func (s *gnpOperator) GetAccessors(gadgetCtx operators.GadgetContext) (map[datasource.DataSource]k8sAccesors, error) {
+	logger := gadgetCtx.Logger()
+	accessors := make(map[datasource.DataSource]k8sAccesors)
+	for _, ds := range gadgetCtx.GetDataSources() {
+		logger.Debugf("GenerateNetworkPolicy inspecting datasource %q", ds.Name())
+
+		if ds.Annotations()["generate_networkpolicy.enable"] != "true" {
+			logger.Debugf("GenerateNetworkPolicy not enabled by annotation")
+			continue
+		}
+
+		acc := k8sAccesors{}
+
+		acc.k8sHostNetwork = ds.GetField("k8s.hostnetwork")
+		if acc.k8sHostNetwork == nil {
+			return nil, fmt.Errorf("no hostnetwork field found")
+		}
+		acc.k8sNamespace = ds.GetField("k8s.namespace")
+		if acc.k8sNamespace == nil {
+			return nil, fmt.Errorf("no namespace field found")
+		}
+		acc.k8sPodLabels = ds.GetField("k8s.podLabels")
+		if acc.k8sPodLabels == nil {
+			return nil, fmt.Errorf("no podLabels field found")
+		}
+		acc.k8sPodIP = ds.GetField("k8s.podIP")
+		if acc.k8sPodIP == nil {
+			return nil, fmt.Errorf("no podIP field found")
+		}
+		acc.k8sPodName = ds.GetField("k8s.podName")
+		if acc.k8sPodName == nil {
+			return nil, fmt.Errorf("no podName field found")
+		}
+		acc.k8sOwnerName = ds.GetField("k8s.owner.name")
+		if acc.k8sOwnerName == nil {
+			return nil, fmt.Errorf("no owner.name field found")
+		}
+		acc.endpointAddr = ds.GetField("endpoint.addr")
+		if acc.endpointAddr == nil {
+			return nil, fmt.Errorf("no endpoint.addr field found")
+		}
+		acc.endpointPort = ds.GetField("endpoint.port")
+		if acc.endpointPort == nil {
+			return nil, fmt.Errorf("no endpoint.port field found")
+		}
+		acc.endpointK8sKind = ds.GetField("endpoint.k8s.kind")
+		if acc.endpointK8sKind == nil {
+			return nil, fmt.Errorf("no endpoint.k8s.kind field found")
+		}
+		acc.endpointK8sName = ds.GetField("endpoint.k8s.name")
+		if acc.endpointK8sName == nil {
+			return nil, fmt.Errorf("no endpoint.k8s.name field found")
+		}
+		acc.endpointK8sNamespace = ds.GetField("endpoint.k8s.namespace")
+		if acc.endpointK8sNamespace == nil {
+			return nil, fmt.Errorf("no endpoint.k8s.namespace field found")
+		}
+		acc.endpointK8sLabels = ds.GetField("endpoint.k8s.labels")
+		if acc.endpointK8sLabels == nil {
+			return nil, fmt.Errorf("no endpoint.k8s.labels field found")
+		}
+		acc.endpointProto = ds.GetField("endpoint.proto")
+		if acc.endpointProto == nil {
+			return nil, fmt.Errorf("no endpoint.proto field found")
+		}
+		acc.egress = ds.GetField("egress")
+		if acc.egress == nil {
+			return nil, fmt.Errorf("no egress field found")
+		}
+
+		// Disable datasource for other operators
+		ds.Unreference()
path: pkg/operators/generate_networkpolicy/generate_networkpolicy_op.go
user: login: mauriciovasquezbernal
body: What'd happen if we don't disable it?
created_at: 2025-05-22T16:51:19Z
author_association: MEMBER
path: pkg/operators/generate_networkpolicy/generate_networkpolicy_op.go
user: login: mauriciovasquezbernal
body: Please add a comment saying it's a temporal solution until we're able to run gadget code on the client side. 
created_at: 2025-05-22T16:52:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 The Inspektor Gadget authors */
+#include <linux/bpf.h>
+#include <linux/if_ether.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/in.h>
+#include <linux/tcp.h>
+#include <linux/types.h>
+#include <linux/udp.h>
+#include <stdbool.h>
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#include <gadget/buffer.h>
+#include <gadget/macros.h>
+#include <gadget/types.h>
+#include <gadget/maps.bpf.h>
+
+#define GADGET_TYPE_NETWORKING
+#include <gadget/sockets-map.h>
+
+#define PACKET_HOST 0
+#define PACKET_OUTGOING 4
+
+struct event_t {
+	gadget_netns_id netns_id;
+	gadget_timestamp timestamp_raw;
+
+	struct gadget_l4endpoint_t endpoint;
+	__u8 egress;
+};
+
+struct empty_t {
+	__u8 unused;
+};
+
+const struct empty_t zero = {};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_HASH);
+	__uint(max_entries, 10240);
+	__type(key, struct event_t);
+	__type(value, struct empty_t);
+} packets SEC(".maps");
+
+GADGET_MAPITER(events, packets);
+
+SEC("socket1")
+int ig_trace_net(struct __sk_buff *skb)
+{
+	// Skip multicast, broadcast, forwarding...
+	if (skb->pkt_type != PACKET_HOST && skb->pkt_type != PACKET_OUTGOING)
+		return 0;
+
+	// Skip frames with non-IP Ethernet protocol.
+	struct ethhdr ethh;
+	if (bpf_skb_load_bytes(skb, 0, &ethh, sizeof ethh))
+		return 0;
+	if (bpf_ntohs(ethh.h_proto) != ETH_P_IP)
+		return 0;
+
+	int ip_off = ETH_HLEN;
+	// Read the IP header.
+	struct iphdr iph;
+	if (bpf_skb_load_bytes(skb, ip_off, &iph, sizeof iph))
+		return 0;
+
+	// An IPv4 header doesn't have a fixed size. The IHL field of a packet
+	// represents the size of the IP header in 32-bit words, so we need to
+	// multiply this value by 4 to get the header size in bytes.
+	__u8 ip_header_len = iph.ihl * 4;
+	int l4_off = ip_off + ip_header_len;
+	__u16 port;
+
+	if (iph.protocol == IPPROTO_TCP) {
+		// Read the TCP header.
+		struct tcphdr tcph;
+		if (bpf_skb_load_bytes(skb, l4_off, &tcph, sizeof tcph))
+			return 0;
+
+		if (!tcph.syn || tcph.ack)
+			return 0;
+
+		port = bpf_htons(tcph.dest);
+	} else if (iph.protocol == IPPROTO_UDP) {
+		// Read the UDP header.
+		struct udphdr udph;
+		if (bpf_skb_load_bytes(skb, l4_off, &udph, sizeof udph))
+			return 0;
+
+		// UDP packets don't have a TCP-SYN to identify the direction.
+		// Check usage of dynamic ports instead.
+		// https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
+		// System Ports: 0-1023
+		// User Ports: 1024-49151
+		// Dynamic and/or Private Ports: 49152-65535
+		// However, Linux uses ephemeral ports: 32768-60999 (/proc/sys/net/ipv4/ip_local_port_range)
+		// And /proc/sys/net/ipv4/ip_unprivileged_port_start: 1024
+		if (bpf_htons(udph.dest) < 1024)
+			port = bpf_htons(udph.dest);
+		else
+			return 0;
+	} else {
+		// Skip packets with IP protocol other than TCP/UDP.
+		return 0;
+	}
+
+	struct event_t event = {};
+	__builtin_memset(&event, 0, sizeof(event));
path: gadgets/advise_networkpolicy/program.bpf.c
user: login: burak-ok
body: I've got bitten by new added fields that were then random in some cases (where I didn't set all fields). But we can remove this
created_at: 2025-05-23T10:42:26Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 The Inspektor Gadget authors */
+#include <linux/bpf.h>
+#include <linux/if_ether.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/in.h>
+#include <linux/tcp.h>
+#include <linux/types.h>
+#include <linux/udp.h>
+#include <stdbool.h>
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#include <gadget/buffer.h>
+#include <gadget/macros.h>
+#include <gadget/types.h>
+#include <gadget/maps.bpf.h>
+
+#define GADGET_TYPE_NETWORKING
+#include <gadget/sockets-map.h>
+
+#define PACKET_HOST 0
+#define PACKET_OUTGOING 4
+
+struct event_t {
+	gadget_netns_id netns_id;
+	gadget_timestamp timestamp_raw;
+
+	struct gadget_l4endpoint_t endpoint;
+	__u8 egress;
+};
+
+struct empty_t {
+	__u8 unused;
+};
+
+const struct empty_t zero = {};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_HASH);
+	__uint(max_entries, 10240);
+	__type(key, struct event_t);
+	__type(value, struct empty_t);
path: gadgets/advise_networkpolicy/program.bpf.c
user: login: burak-ok
body: Added a comment inline:
`When using GADGET_MAPITER, the key and value has to a struct`
created_at: 2025-05-23T10:50:08Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,29 @@
+# Artifact Hub package metadata file
+version: 0.40.0
+name: "advise_networkpolicy"
+category: monitoring-logging
+displayName: "advise networkpolicy"
+createdAt: "2025-05-03T11:23:41Z"
+digest: "2025-05-03T11:23:41Z"
+description: "Generate network policies according to the K8s traffic"
+logoURL: "https://inspektor-gadget.io/media/brand-icon.svg"
+license: ""
+homeURL: "https://inspektor-gadget.io/docs/latest/gadgets/advise_networkpolicy"
+containersImages:
+    - name: gadget
+      image: "ghcr.io/inspektor-gadget/gadget/advise_networkpolicy:latest"
+      platforms:
+        - linux/amd64
+        - linux/arm64
+keywords:
+    - gadget
+links:
+    - name: source
+      url: "https://github.com/inspektor-gadget/inspektor-gadget/tree/main/gadgets/advise_networkpolicy"
+install: |
+    # Run
+    ```bash
+    sudo ig run ghcr.io/inspektor-gadget/gadget/advise_networkpolicy:latest
path: gadgets/advise_networkpolicy/artifacthub-pkg.yml
user: login: mauriciovasquezbernal
body: nit: better not to recommend `ig run` on this one as it requires kubernetes.
created_at: 2025-05-23T16:23:29Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 The Inspektor Gadget authors */
+#include <linux/bpf.h>
+#include <linux/if_ether.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/in.h>
+#include <linux/tcp.h>
+#include <linux/types.h>
+#include <linux/udp.h>
+#include <stdbool.h>
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+#include <gadget/buffer.h>
+#include <gadget/macros.h>
+#include <gadget/types.h>
+#include <gadget/maps.bpf.h>
+
+#define GADGET_TYPE_NETWORKING
+#include <gadget/sockets-map.h>
+
+#define PACKET_HOST 0
+#define PACKET_OUTGOING 4
+
+struct event_t {
+	gadget_netns_id netns_id;
+	struct gadget_l4endpoint_t endpoint;
+	__u8 egress;
+};
+
+struct empty_t {
+	__u8 unused;
+};
+
+const struct empty_t zero = {};
+
+// When using GADGET_MAPITER, the key and value has to a struct
path: gadgets/advise_networkpolicy/program.bpf.c
user: login: mauriciovasquezbernal
body: `// When using GADGET_MAPITER, both the key and value have to be structs`


created_at: 2025-05-23T16:24:37Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,334 @@
+// Copyright 2025 The Inspektor Gadget authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package generate_networkpolicy
+
+import (
+	"fmt"
+	"sort"
+
+	v1 "k8s.io/api/core/v1"
+	networkingv1 "k8s.io/api/networking/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+	k8syaml "sigs.k8s.io/yaml"
+
+	"github.com/inspektor-gadget/inspektor-gadget/pkg/types"
+)
+
+type NetworkEvent struct {
+	K8s types.K8sMetadata
+
+	egress   bool
+	endpoint types.L4Endpoint
+	proto    string // L4Endpoint has proto as uint8, but we need a string here
+}
+
+var defaultLabelsToIgnore = map[string]struct{}{
+	"controller-revision-hash": {},
+	"pod-template-generation":  {},
+	"pod-template-hash":        {},
+}
+
+var LabelsToIgnore = defaultLabelsToIgnore
+
+/* labelFilteredKeyList returns a sorted list of label keys but without the labels to
+ * ignore.
+ */
+func labelFilteredKeyList(labels map[string]string) []string {
+	keys := make([]string, 0, len(labels))
+	for k := range labels {
+		if _, ok := LabelsToIgnore[k]; ok {
+			continue
+		}
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+
+	return keys
+}
+
+func labelFilter(labels map[string]string) map[string]string {
+	ret := map[string]string{}
+	for k := range labels {
+		if _, ok := LabelsToIgnore[k]; ok {
+			continue
+		}
+		ret[k] = labels[k]
+	}
+	return ret
+}
+
+/* labelKeyString returns a sorted list of labels in a single string.
+ * label1=value1,label2=value2
+ */
+func labelKeyString(labels map[string]string) (ret string) {
+	keys := labelFilteredKeyList(labels)
+
+	for index, k := range keys {
+		sep := ","
+		if index == 0 {
+			sep = ""
+		}
+		ret += fmt.Sprintf("%s%s=%s", sep, k, labels[k])
+	}
+	return
+}
+
+/* localPodKey returns a key that can be used to group pods together:
+ * namespace:label1=value1,label2=value2
+ */
+func localPodKey(e NetworkEvent) (ret string) {
+	return e.K8s.Namespace + ":" + labelKeyString(e.K8s.PodLabels)
+}
+
+func networkPeerKey(e NetworkEvent) (string, error) {
+	var ret string
+	switch e.endpoint.Kind {
+	case types.EndpointKindPod:
+		ret = string(e.endpoint.Kind) + ":" + e.endpoint.Namespace + ":" + labelKeyString(e.endpoint.PodLabels)
+	case types.EndpointKindService:
+		ret = string(e.endpoint.Kind) + ":" + e.endpoint.Namespace + ":" + labelKeyString(e.endpoint.PodLabels)
+	case types.EndpointKindRaw:
+		ret = string(e.endpoint.Kind) + ":" + e.endpoint.Addr
+	default:
+		return "", fmt.Errorf("unknown endpoint kind: %s", e.endpoint.Kind)
+	}
+	return fmt.Sprintf("%s:%d", ret, e.endpoint.Port), nil
+}
+
+func eventToRule(e NetworkEvent) ([]networkingv1.NetworkPolicyPort, []networkingv1.NetworkPolicyPeer, error) {
+	port := intstr.FromInt(int(e.endpoint.Port))
+	protocol := v1.Protocol(e.proto)
+	ports := []networkingv1.NetworkPolicyPort{
+		{
+			Port:     &port,
+			Protocol: &protocol,
+		},
+	}
+	var peers []networkingv1.NetworkPolicyPeer
+	switch e.endpoint.Kind {
+	case types.EndpointKindPod:
+		peers = []networkingv1.NetworkPolicyPeer{
+			{
+				PodSelector: &metav1.LabelSelector{MatchLabels: labelFilter(e.endpoint.PodLabels)},
+			},
+		}
+		if e.K8s.Namespace != e.endpoint.Namespace {
+			peers[0].NamespaceSelector = &metav1.LabelSelector{
+				MatchLabels: map[string]string{
+					// Kubernetes 1.22 is guaranteed to add the following label on namespaces:
+					// kubernetes.io/metadata.name=obj.Name
+					// See:
+					// https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/2161-apiserver-default-labels#proposal
+					"kubernetes.io/metadata.name": e.endpoint.Namespace,
+				},
+			}
+		}
+	case types.EndpointKindService:
+		peers = []networkingv1.NetworkPolicyPeer{
+			{
+				PodSelector: &metav1.LabelSelector{MatchLabels: e.endpoint.PodLabels},
+			},
+		}
+		if e.K8s.Namespace != e.endpoint.Namespace {
+			peers[0].NamespaceSelector = &metav1.LabelSelector{
+				MatchLabels: map[string]string{
+					// Kubernetes 1.22 is guaranteed to add the following label on namespaces:
+					// kubernetes.io/metadata.name=obj.Name
+					// See:
+					// https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/2161-apiserver-default-labels#proposal
+					"kubernetes.io/metadata.name": e.endpoint.Namespace,
+				},
+			}
+		}
+	case types.EndpointKindRaw:
+		if e.endpoint.Addr == "127.0.0.1" {
+			// No need to generate a network policy for localhost
+			peers = []networkingv1.NetworkPolicyPeer{}
+		} else {
+			peers = []networkingv1.NetworkPolicyPeer{
+				{
+					IPBlock: &networkingv1.IPBlock{
+						CIDR: e.endpoint.Addr + "/32",
+					},
+				},
+			}
+		}
+	default:
+		return nil, nil, fmt.Errorf("unknown endpoint kind: %s", e.endpoint.Kind)
+	}
+	return ports, peers, nil
+}
+
+func sortIngressRules(rules []networkingv1.NetworkPolicyIngressRule) []networkingv1.NetworkPolicyIngressRule {
+	sort.Slice(rules, func(i, j int) bool {
+		ri, rj := rules[i], rules[j]
+
+		// No need to support all network policies, but only the ones
+		// generated by eventToRule()
+		if len(ri.Ports) != 1 || len(rj.Ports) != 1 {
+			panic("rules with multiple ports")
path: pkg/operators/generate_networkpolicy/generate_networkpolicy.go
user: login: mauriciovasquezbernal
body: Do we really want to panic() in this place? Perhaps logging an error is better?
created_at: 2025-05-23T16:28:35Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,29 @@
+# Artifact Hub package metadata file
+version: 0.40.0
+name: "advise_networkpolicy"
+category: monitoring-logging
+displayName: "advise networkpolicy"
+createdAt: "2025-05-03T11:23:41Z"
+digest: "2025-05-03T11:23:41Z"
+description: "Generate network policies according to the K8s traffic"
+logoURL: "https://inspektor-gadget.io/media/brand-icon.svg"
+license: ""
+homeURL: "https://inspektor-gadget.io/docs/latest/gadgets/advise_networkpolicy"
+containersImages:
+    - name: gadget
+      image: "ghcr.io/inspektor-gadget/gadget/advise_networkpolicy:latest"
+      platforms:
+        - linux/amd64
+        - linux/arm64
+keywords:
+    - gadget
+links:
+    - name: source
+      url: "https://github.com/inspektor-gadget/inspektor-gadget/tree/main/gadgets/advise_networkpolicy"
+install: |
+    # Run
+    ```bash
+    sudo ig run ghcr.io/inspektor-gadget/gadget/advise_networkpolicy:latest
path: gadgets/advise_networkpolicy/artifacthub-pkg.yml
user: login: mauriciovasquezbernal
body: I'm aware this file could be autogenerated, so feel free to ignore if it's difficult to fix.
created_at: 2025-05-23T16:38:03Z
author_association: MEMBER

PR comments:
user: login: burak-ok
created_at: 2025-05-27T16:17:14Z
body: Once https://github.com/inspektor-gadget/inspektor-gadget/pull/4523 is merged I can remove the final prerequisite commit
author_association: MEMBER
user: login: burak-ok
created_at: 2025-05-28T15:19:44Z
body: Merging now.

CI Error is on the missing link in the official documentation, which will get added when this is merged
```
  ERROR: 1 dead links found in gadgets/advise_networkpolicy/README.md !
  [✖] https://inspektor-gadget.io/docs/latest/gadgets/advise_networkpolicy → Status: 404
```
author_association: MEMBER