Repository: containernetworking/cni (go)

Issue:
Add Table ID in CNI Result and Attribute
Using Source Based Routing and VRF meta plugins, routes are configured in different tables that are not represented in the result.

In a chained CNI configuration, the first main cni will create the requested routes and currently, VRF and SBR plugins are moving some of those routes to newly created routing table(s). Adding the `Table` property in the Result would help with the clarity of what and how networks are being configured. 

Routes could be configured in a specific table by the "main" CNI in the chain, and the SBR plugin could just create the policy route towards this table without moving any route. The current behavior could be also kept, the SBR and VRF plugin would have to modify the result to specify in which table routes have been moved.

Adding the option to pass the table number is described as future enhancement in the SBR plugin documentation: https://www.cni.dev/plugins/current/meta/sbr/#future-enhancements-and-known-limitations
Here is what would be the changes in the SBR plugin with the table ID without moving routes: https://github.com/LionelJouin/plugins/commit/098774320801f8e0ccc04240771cbb382937ce65

Pull request:
Add table ID property for routes
Fixes #1061

Related to #640 

Patch:
diff --git a/SPEC.md b/SPEC.md
index ef3fe73..a6235af 100644
--- a/SPEC.md
+++ b/SPEC.md
@@ -580,6 +580,7 @@ Plugins must output a JSON object with the following keys upon a successful `ADD
     - `mtu` (uint): The MTU (Maximum transmission unit) along the path to the destination.
     - `advmss` (uint): The MSS (Maximal Segment Size) to advertise to these destinations when establishing TCP connections.
     - `priority` (uint): The priority of route, lower is higher.
+    - `table` (uint): The table to add the route to.
 - `dns`: a dictionary consisting of DNS configuration information
     - `nameservers` (list of strings): list of a priority-ordered list of DNS nameservers that this network is aware of. Each entry in the list is a string containing either an IPv4 or an IPv6 address.
     - `domain` (string): the local domain used for short hostname lookups.
diff --git a/pkg/types/types.go b/pkg/types/types.go
index 58b5794..ea307e4 100644
--- a/pkg/types/types.go
+++ b/pkg/types/types.go
@@ -170,10 +170,16 @@ type Route struct {
 	MTU      int
 	AdvMSS   int
 	Priority int
+	Table    *int
 }
 
 func (r *Route) String() string {
-	return fmt.Sprintf("%+v", *r)
+	table := "<nil>"
+	if r.Table != nil {
+		table = fmt.Sprintf("%d", *r.Table)
+	}
+
+	return fmt.Sprintf("{Dst:%+v GW:%v MTU:%d AdvMSS:%d Priority:%d Table:%s}", r.Dst, r.GW, r.MTU, r.AdvMSS, r.Priority, table)
 }
 
 func (r *Route) Copy() *Route {
@@ -181,13 +187,20 @@ func (r *Route) Copy() *Route {
 		return nil
 	}
 
-	return &Route{
+	route := &Route{
 		Dst:      r.Dst,
 		GW:       r.GW,
 		MTU:      r.MTU,
 		AdvMSS:   r.AdvMSS,
 		Priority: r.Priority,
 	}
+
+	if r.Table != nil {
+		table := *r.Table
+		route.Table = &table
+	}
+
+	return route
 }
 
 // Well known error codes
@@ -242,6 +255,7 @@ type route struct {
 	MTU      int    `json:"mtu,omitempty"`
 	AdvMSS   int    `json:"advmss,omitempty"`
 	Priority int    `json:"priority,omitempty"`
+	Table    *int   `json:"table,omitempty"`
 }
 
 func (r *Route) UnmarshalJSON(data []byte) error {
@@ -255,6 +269,7 @@ func (r *Route) UnmarshalJSON(data []byte) error {
 	r.MTU = rt.MTU
 	r.AdvMSS = rt.AdvMSS
 	r.Priority = rt.Priority
+	r.Table = rt.Table
 
 	return nil
 }
@@ -266,6 +281,7 @@ func (r Route) MarshalJSON() ([]byte, error) {
 		MTU:      r.MTU,
 		AdvMSS:   r.AdvMSS,
 		Priority: r.Priority,
+		Table:    r.Table,
 	}
 
 	return json.Marshal(rt)


Test patch:
diff --git a/pkg/types/types_test.go b/pkg/types/types_test.go
index 235f08c..149c2bb 100644
--- a/pkg/types/types_test.go
+++ b/pkg/types/types_test.go
@@ -22,6 +22,7 @@ import (
 	. "github.com/onsi/gomega"
 
 	"github.com/containernetworking/cni/pkg/types"
+	types040 "github.com/containernetworking/cni/pkg/types/040"
 	current "github.com/containernetworking/cni/pkg/types/100"
 )
 
@@ -91,13 +92,14 @@ var _ = Describe("Types", func() {
 				MTU:      1500,
 				AdvMSS:   1340,
 				Priority: 100,
+				Table:    types040.Int(50),
 			}
 		})
 
 		It("marshals and unmarshals to JSON", func() {
 			jsonBytes, err := json.Marshal(example)
 			Expect(err).NotTo(HaveOccurred())
-			Expect(jsonBytes).To(MatchJSON(`{ "dst": "1.2.3.0/24", "gw": "1.2.3.1", "mtu": 1500, "advmss": 1340, "priority": 100 }`))
+			Expect(jsonBytes).To(MatchJSON(`{ "dst": "1.2.3.0/24", "gw": "1.2.3.1", "mtu": 1500, "advmss": 1340, "priority": 100, "table": 50 }`))
 
 			var unmarshaled types.Route
 			Expect(json.Unmarshal(jsonBytes, &unmarshaled)).To(Succeed())
@@ -113,7 +115,7 @@ var _ = Describe("Types", func() {
 		})
 
 		It("formats as a string with a hex mask", func() {
-			Expect(example.String()).To(Equal(`{Dst:{IP:1.2.3.0 Mask:ffffff00} GW:1.2.3.1 MTU:1500 AdvMSS:1340 Priority:100}`))
+			Expect(example.String()).To(Equal(`{Dst:{IP:1.2.3.0 Mask:ffffff00} GW:1.2.3.1 MTU:1500 AdvMSS:1340 Priority:100 Table:50}`))
 		})
 	})