Repository: containernetworking/cni

Issue:
Proposal: Add metadata map field to Interface object in CNI Result
CNI spec should be allowed to pass arbitrary data in CNI result from the container runtime to the OCI runtime. Currently, there is an ability to influence the inputs to the CNI network configuration via runtime configuration. In a similar way, the CNI can add a metadata field to interface to pass down additional data to OCI runtime. The metadata field will be added to the Interface struct as a map[string]string and be optional. We can standardize specific keys similar to our conventions. The NRI plugin system may also benefit from this additional metadata. Our main use case for this is to support virtual runtimes and possibly advanced networking scenarios.  This has been requested several times however no one has completed the work. 

Current: 
 
```
type Interface struct { 
    Name        string `json:"name"` 
    Mac         string `json:"mac,omitempty"` 
    Sandbox     string `json:"sandbox,omitempty"` 
} 
```
 
Proposal: 
 
```
type Interface struct { 
    Name         string `json:"name"` 
    Mac          string `json:"mac,omitempty"` 
    Sandbox      string `json:"sandbox,omitempty"` 
    Metadata     map[string]string `json:"metadata,omitempty"` 
} 
```

Work Involved: 
 
1.	Create proposal as a Github Issue in the CNI repo 
2.	Create a PR for the specification updates 
3.	Create a PR for libcni 
4.	Update community plugins 
5.	Update go-cni/ocicni to support latest types 
6.	Update containerd/cri-o to use latest go-cni/ocicni
 



Discussion:
@squeed: We've discussed interface metadata for some time; I'd like to gather some use cases before we commit to a design. I think it would work well, though. Pinging some people for suggestions:
- @maiqueb 
- @mmirecki
- @dougbtv@maiqueb: /cc@MikeZappa87: @dougbtv we discussed this briefly before the holidays, do you have any thoughts? @squeed: First thought: 

- whatever interface-level metadata we support should be defined in CONVENTIONS.md.
- Keys should be "namespaced", and the namespace `cni.dev/` should be reserved.@squeed: If there is some consensus that this is an avenue we would like to explore, the next steps would be:
1. Identify a few actual use-cases, so we can intuit whether the proposal meets their needs
2. Start to work on a SPEC draft@dougbtv: In favor of moving forward on this. 

I also like the idea of  having namespaced keys... Would we want a namespace field for the metadata as a whole? Or mix-matched within the `Metadata` field, e.g. within the metadata field `{my.io/foo: "bar", org.io/quux: zuuz}` I guess I like the latter after typing it out.@MikeZappa87: If the case of a key that doesn't have cni.dev/ would libcni parse those out? Or will this be pass through? @MikeZappa87: @maiqueb does this help kubevirt? @solidDoWant: Hey folks, I don't think #1069 actually solved this issue. It added a couple of specific, optional fields, but did not address the issue of storing arbitrary data in the result. Can this be reopened?

Here's my use case. I'm writing a plugin that can act as either an "interface" plugin, or a "chained" plugin, depending on whether or not a previous plugin in the chain is acting as an "interface" plugin. This plugin can add an arbitrary number of interfaces depending on what the user has configured.

It's really easy to tell if my plugin should be the interface plugin at `ADD` time. I can simply check to see if an interface exists matching `CF_IFNAME` in the container namespace. However, at `DEL` and `CHECK` time I cannot determine (via a mechanism built into the CNI spec) if my plugin or another plugin created the `CF_IFNAME` interface or not. Support for arbitrary metadata as this issue discusses would allow for me to track the interface creator without storing the state elsewhere (disk, k8s resource, etc.).

I have similar use case for routes, but that's outside the scope of this issue.

Solution patch:
diff --git a/SPEC.md b/SPEC.md
index eee34ee..ef3fe73 100644
--- a/SPEC.md
+++ b/SPEC.md
@@ -565,9 +565,11 @@ Plugins must output a JSON object with the following keys upon a successful `ADD
 
 - `cniVersion`: The same version supplied on input - the string "1.1.0"
 - `interfaces`: An array of all interfaces created by the attachment, including any host-level interfaces:
-    - `name`: The name of the interface.
-    - `mac`: The hardware address of the interface (if applicable).
-    - `sandbox`: The isolation domain reference (e.g. path to network namespace) for the interface, or empty if on the host. For interfaces created inside the container, this should be the value passed via `CNI_NETNS`.
+    - `name` (string): The name of the interface.
+    - `mac` (string): The hardware address of the interface (if applicable).
+    - `sandbox` (string): The isolation domain reference (e.g. path to network namespace) for the interface, or empty if on the host. For interfaces created inside the container, this should be the value passed via `CNI_NETNS`.
+    - `socketPath` (string, optional): An absolute path to a socket file corresponding to this interface, if applicable.
+    - `pciID` (string, optional): The platform-specific identifier of the PCI device corresponding to this interface, if applicable.
 - `ips`: IPs assigned by this attachment. Plugins may include IPs assigned external to the container.
     - `address` (string): an IP address in CIDR notation (eg "192.168.1.3/24").
     - `gateway` (string): the default gateway for this subnet, if one exists.
diff --git a/pkg/types/100/types.go b/pkg/types/100/types.go
index d6d069f..c139257 100644
--- a/pkg/types/100/types.go
+++ b/pkg/types/100/types.go
@@ -268,9 +268,11 @@ func (r *Result) PrintTo(writer io.Writer) error {
 
 // Interface contains values about the created interfaces
 type Interface struct {
-	Name    string `json:"name"`
-	Mac     string `json:"mac,omitempty"`
-	Sandbox string `json:"sandbox,omitempty"`
+	Name       string `json:"name"`
+	Mac        string `json:"mac,omitempty"`
+	Sandbox    string `json:"sandbox,omitempty"`
+	SocketPath string `json:"socketPath,omitempty"`
+	PciID      string `json:"pciID,omitempty"`
 }
 
 func (i *Interface) String() string {



Test patch:
diff --git a/pkg/types/100/types_test.go b/pkg/types/100/types_test.go
index 3bd50bd..e74060c 100644
--- a/pkg/types/100/types_test.go
+++ b/pkg/types/100/types_test.go
@@ -45,15 +45,16 @@ func testResult() *current.Result {
 	Expect(err).NotTo(HaveOccurred())
 	Expect(routev6).NotTo(BeNil())
 	Expect(routegwv6).NotTo(BeNil())
-
 	// Set every field of the struct to ensure source compatibility
 	return &current.Result{
 		CNIVersion: current.ImplementedSpecVersion,
 		Interfaces: []*current.Interface{
 			{
-				Name:    "eth0",
-				Mac:     "00:11:22:33:44:55",
-				Sandbox: "/proc/3553/ns/net",
+				Name:       "eth0",
+				Mac:        "00:11:22:33:44:55",
+				Sandbox:    "/proc/3553/ns/net",
+				PciID:      "8086:9a01",
+				SocketPath: "/path/to/vhost/fd",
 			},
 		},
 		IPs: []*current.IPConfig{
@@ -104,9 +105,11 @@ var _ = Describe("Current types operations", func() {
     "cniVersion": "` + current.ImplementedSpecVersion + `",
     "interfaces": [
         {
-            "name": "eth0",
-            "mac": "00:11:22:33:44:55",
-            "sandbox": "/proc/3553/ns/net"
+            "name":        "eth0",
+            "mac":         "00:11:22:33:44:55",
+            "sandbox":     "/proc/3553/ns/net",
+            "pciID": 	   "8086:9a01",
+            "socketPath":  "/path/to/vhost/fd"
         }
     ],
     "ips": [
diff --git a/pkg/types/types_test.go b/pkg/types/types_test.go
index ee0a829..235f08c 100644
--- a/pkg/types/types_test.go
+++ b/pkg/types/types_test.go
@@ -157,14 +157,15 @@ var _ = Describe("Types", func() {
 			ipv6, err := types.ParseCIDR("abcd:1234:ffff::cdde/64")
 			Expect(err).NotTo(HaveOccurred())
 			Expect(ipv6).NotTo(BeNil())
-
 			result = &current.Result{
 				CNIVersion: "1.0.0",
 				Interfaces: []*current.Interface{
 					{
-						Name:    "eth0",
-						Mac:     "00:11:22:33:44:55",
-						Sandbox: "/proc/3553/ns/net",
+						Name:       "eth0",
+						Mac:        "00:11:22:33:44:55",
+						Sandbox:    "/proc/3553/ns/net",
+						PciID:      "8086:9a01",
+						SocketPath: "/path/to/vhost/fd",
 					},
 				},
 				IPs: []*current.IPConfig{
diff --git a/pkg/version/version_test.go b/pkg/version/version_test.go
index e0ece71..2ddf5eb 100644
--- a/pkg/version/version_test.go
+++ b/pkg/version/version_test.go
@@ -39,9 +39,11 @@ var _ = Describe("Version operations", func() {
 				"cniVersion": "1.0.0",
 				"interfaces": [
 					{
-						"name": "eth0",
-						"mac": "00:11:22:33:44:55",
-						"sandbox": "/proc/3553/ns/net"
+						"name":        "eth0",
+						"mac":         "00:11:22:33:44:55",
+						"sandbox":     "/proc/3553/ns/net",
+						"pciID":       "8086:9a01",
+						"socketPath":  "/path/to/vhost/fd"
 					}
 				],
 				"ips": [
@@ -66,14 +68,15 @@ var _ = Describe("Version operations", func() {
 
 			err = version.ParsePrevResult(conf)
 			Expect(err).NotTo(HaveOccurred())
-
 			expectedResult := &cniv1.Result{
 				CNIVersion: "1.0.0",
 				Interfaces: []*cniv1.Interface{
 					{
-						Name:    "eth0",
-						Mac:     "00:11:22:33:44:55",
-						Sandbox: "/proc/3553/ns/net",
+						Name:       "eth0",
+						Mac:        "00:11:22:33:44:55",
+						Sandbox:    "/proc/3553/ns/net",
+						PciID:      "8086:9a01",
+						SocketPath: "/path/to/vhost/fd",
 					},
 				},
 				IPs: []*cniv1.IPConfig{