Repository: ovn-kubernetes/ovn-kubernetes
Pull request #5010: crds,cudn: Support localnet topology
Author: login: ormergi
Labels: name: kind/documentation
description: All issues related to documentation
name: area/unit-testing
description: Issues related to adding/updating unit tests
name: area/e2e-testing
Created: 2025-01-28T16:09:57Z | Merged: 2025-04-16T19:19:22Z

<!--
Please make sure you've read and understood our contributing guidelines;
https://github.com/ovn-kubernetes/ovn-kubernetes/blob/master/CONTRIBUTING.md

** Make sure all your commits include a signature generated with `git commit -s` **

All changes must adhere to this template to make it easy for reviewers
and preserve rationale/history behind every change
-->

## 📑 Description
<!-- Add a brief description of the pr -->
This PR implements localnet support on top of the ClusterUserDefinedNetwork CRD, following https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5087
Tracking issue https://github.com/ovn-kubernetes/ovn-kubernetes/issues/5085

With this PR changes the CDUN CRD will feature option for creating localnet topology: 
```
apiVersion: k8s.ovn.org/v1
kind: ClusterUserDefinedNetwork
metadata:
  name: test-net
spec:
  namespaceSelector:
    matchExpressions:
    - key: kubernetes.io/metadata.name
      operator: In
      values: ["red", "blue"]
  network:
    topology: Localnet
    localnet:
      role: Secondary
      physicalNetworkName: tenantblue
      subnets: ["192.168.0.0/16", "2001:dbb::/64"]
      excludeSubnets: ["192.168.50.0/24", "2001:dbb::0/128"]
      vlan: 
        mode: Access
        access: {id: 200}
      ipam:
        lifecycle: Persistent
```
The CUDN CRD controller will create the following NAD should be create in the selected namespaces "red":
```
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
  name: test-net
  namespace: red
  labels:
    k8s.ovn.org/user-defined-network: ""
  finalizers:
  - k8s.ovn.org/user-defined-network-protection
  ownerReferences:
  - apiVersion: k8s.ovn.org/v1
    blockOwnerDeletion: true
    controller: true
    kind: ClusterUserDefinedNetwork
    name: test-net
    uid: c27b7c6d-440e-4b31-b7b6-30a09aa2f378
spec:
  config: '{
    "cniVersion":"1.0.0",
    "type":"ovn-k8s-cni-overlay",
    "netAttachDefName":"red/test-net",
    "name":"cluster_udn_test-net",
    "role":"secondary",
    "topology":"localnet",
    "physicalNetworkName":"tenantblue",
    "subnets":"192.168.0.0/16,2001:dbb::/64",
    "excludeSubnets":"192.168.50.0/24,2001:dbb::0/128",
    "allowPersistentIPs":true,
    "mtu":1500,
    "vlanID":200
  }'
```

<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #

## Additional Information for reviewers
<!--
What exactly did you change - you may also defer to information
contained in commit messages. At a bare minimum it's worth highlighting
which areas of the code were changed as it's easier to assign reviewers
-->

- Since localnet topology is not part of the SDN infran the MTU is not required to be adjusted to compensate for the SDN overhead, and use the default MTU - 1500.
In case MTU is not specified in the CUDN `network.spec`, the controller will generate the NAD setting MTU 1500 explicitly.

- This PR add e2e test for verifying creating localnet topology is possible using CUDN CRD.
The localnet topology implemention is verified by e2e tests in multi-homing test suite.

- Enabling spec mutation for the introduced Localnet topology will be done if follow-up PR.

## ✅ Checks
<!-- Make sure your pr passes the CI checks and do check the following fields as needed - -->
- [ ] My code requires changes to the documentation
- [ ] if so, I have updated the documentation as required
- [ ] My code requires tests
- [ ] if so, I have added and/or updated the tests as required
- [ ] All the tests have passed in the CI <!-- If not leave a comment as to why the CI is red and if you need help understanding what's wrong -->

## How to verify it
<!--
Did you include unit tests? or end-to-end tests?
How can I manually verify that this patch achieves its objective
-->



Diff:
diff --git a/dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2 b/dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
index 0dcd04943be..8d832810d02 100644
--- a/dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
+++ b/dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
@@ -103,7 +103,7 @@ spec:
                             description: |-
                               Lifecycle controls IP addresses management lifecycle.
 
-                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              The only allowed value is Persistent. When set, the IP addresses assigned by OVN Kubernetes will be persisted in an
                               `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
                               Only supported when mode is `Enabled`.
                             enum:
@@ -209,7 +209,7 @@ spec:
                       rule: '!has(self.joinSubnets) || has(self.role) && self.role
                         == ''Primary'''
                     - message: MTU should be greater than or equal to 1280 when IPv6
-                        subent is used
+                        subnet is used
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280'
                   layer3:
@@ -310,20 +310,213 @@ spec:
                       rule: '!has(self.joinSubnets) || has(self.role) && self.role
                         == ''Primary'''
                     - message: MTU should be greater than or equal to 1280 when IPv6
-                        subent is used
+                        subnet is used
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP addresses specified in `subnets`
+                          are subject to assignment.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          When `physicalNetworkName` points to OVS bridge mapping of a network with reserved IP addresses
+                          (which shouldn't be assigned by OVN-Kubernetes), the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SDN infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assigns MAC addresses, and provides layer2 communication,
+                          and enables users\n   to configure IP addresses on the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          to 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, the IP addresses assigned by OVN Kubernetes will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional. When omitted, the configured value in OVN-Kubernetes (defaults to 1500 for localnet topology)
+                          is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enables configuring the same MTU on pod interface, having the pod MTU aligned with the network MTU.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
+                        minimum: 576
+                        type: integer
+                      physicalNetworkName:
+                        description: |-
+                          physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+                          Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+                          In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+                          this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+                        maxLength: 253
+                        minLength: 1
+                        type: string
+                        x-kubernetes-validations:
+                        - message: physicalNetworkName cannot contain `,` or `:` characters
+                          rule: self.matches('^[^,:]+$')
+                      role:
+                        description: |-
+                          role describes the network role in the pod, required.
+                          Controls whether the pod interface will act as primary or secondary.
+                          Localnet topology supports `Secondary` only.
+                          The network will be assigned to pods that have the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+                          to subject.
+                        enum:
+                        - Secondary
+                        type: string
+                      subnets:
+                        description: |-
+                          subnets is a list of subnets used for pods in this localnet network across the cluster.
+                          The list may be either 1 IPv4 subnet, 1 IPv6 subnet, or 1 of each IP family.
+                          When set, OVN-Kubernetes assigns an IP address from the specified CIDRs to the connected pod,
+                          eliminating the need for manual IP assignment or reliance on an external IPAM service (e.g., a DHCP server).
+                          subnets is optional. When omitted OVN-Kubernetes won't assign IP address automatically.
+                          Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `ipam.mode` is `Disabled`.
+                          When physicalNetworkName points to the OVS bridge mapping of a network that provides IPAM services
+                          (e.g., a DHCP server), ipam.mode should be set to Disabled. This turns off OVN-Kubernetes IPAM and avoids
+                          conflicts with the existing IPAM services on this localnet network.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 2
+                        minItems: 1
+                        type: array
+                        x-kubernetes-validations:
+                        - message: When 2 CIDRs are set, they must be from different
+                            IP families
+                          rule: size(self) != 2 || !isCIDR(self[0]) || !isCIDR(self[1])
+                            || cidr(self[0]).ip().family() != cidr(self[1]).ip().family()
+                      vlan:
+                        description: |-
+                          vlan configuration for the network.
+                          vlan.mode is the VLAN mode.
+                            When "Access" is set, OVN-Kubernetes configures the network logical switch port in access mode.
+                          vlan.access is the access VLAN configuration.
+                          vlan.access.id is the VLAN ID (VID) to be set on the network logical switch port.
+                          vlan is optional, when omitted the underlying network default VLAN will be used (usually `1`).
+                          When set, OVN-Kubernetes will apply VLAN configuration to the SDN infra and to the connected pods.
+                        properties:
+                          access:
+                            description: Access is the access VLAN configuration
+                            properties:
+                              id:
+                                description: |-
+                                  id is the VLAN ID (VID) to be set for the network.
+                                  id should be higher than 0 and lower than 4095.
+                                format: int32
+                                maximum: 4094
+                                minimum: 1
+                                type: integer
+                            required:
+                            - id
+                            type: object
+                          mode:
+                            description: |-
+                              mode describe the network VLAN mode.
+                              Allowed value is "Access".
+                              Access sets the network logical switch port in access mode, according to the config.
+                            enum:
+                            - Access
+                            type: string
+                        required:
+                        - mode
+                        type: object
+                        x-kubernetes-validations:
+                        - message: vlan access config is required when vlan mode is
+                            'Access', and forbidden otherwise
+                          rule: 'has(self.mode) && self.mode == ''Access'' ? has(self.access):
+                            !has(self.access)'
+                    required:
+                    - physicalNetworkName
+                    - role
+                    type: object
+                    x-kubernetes-validations:
+                    - message: Subnets is required with ipam.mode is Enabled or unset,
+                        and forbidden otherwise
+                      rule: '!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode
+                        == ''Enabled'' ? has(self.subnets) : !has(self.subnets)'
+                    - message: excludeSubnets must be unset when subnets is unset
+                      rule: '!has(self.excludeSubnets) || has(self.subnets)'
+                    - message: MTU should be greater than or equal to 1280 when an
+                        IPv6 subnet is used
+                      rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
+                        isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280'
                   topology:
                     description: |-
                       Topology describes network configuration.
 
-                      Allowed values are "Layer3", "Layer2".
+                      Allowed values are "Layer3", "Layer2" and "Localnet".
                       Layer3 topology creates a layer 2 segment per node, each with a different subnet. Layer 3 routing is used to interconnect node subnets.
                       Layer2 topology creates one logical switch shared by all nodes.
+                      Localnet topology is based on layer 2 topology, but also allows connecting to an existent (configured) physical network to provide north-south traffic to the workloads.
                     enum:
                     - Layer2
                     - Layer3
+                    - Localnet
                     type: string
                 required:
                 - topology
@@ -337,6 +530,10 @@ spec:
                     otherwise
                   rule: 'has(self.topology) && self.topology == ''Layer2'' ? has(self.layer2):
                     !has(self.layer2)'
+                - message: spec.localnet is required when topology is Localnet and
+                    forbidden otherwise
+                  rule: 'has(self.topology) && self.topology == ''Localnet'' ? has(self.localnet):
+                    !has(self.localnet)'
                 - message: Network spec is immutable
                   rule: self == oldSelf
             required:
diff --git a/dist/templates/k8s.ovn.org_userdefinednetworks.yaml.j2 b/dist/templates/k8s.ovn.org_userdefinednetworks.yaml.j2
index 75683141587..bc121ebe9b6 100644
--- a/dist/templates/k8s.ovn.org_userdefinednetworks.yaml.j2
+++ b/dist/templates/k8s.ovn.org_userdefinednetworks.yaml.j2
@@ -51,7 +51,7 @@ spec:
                         description: |-
                           Lifecycle controls IP addresses management lifecycle.
 
-                          The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                          The only allowed value is Persistent. When set, the IP addresses assigned by OVN Kubernetes will be persisted in an
                           `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
                           Only supported when mode is `Enabled`.
                         enum:
@@ -155,7 +155,7 @@ spec:
                 - message: JoinSubnets is only supported for Primary network
                   rule: '!has(self.joinSubnets) || has(self.role) && self.role ==
                     ''Primary'''
-                - message: MTU should be greater than or equal to 1280 when IPv6 subent
+                - message: MTU should be greater than or equal to 1280 when IPv6 subnet
                     is used
                   rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                     isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280'
@@ -255,7 +255,7 @@ spec:
                 - message: JoinSubnets is only supported for Primary network
                   rule: '!has(self.joinSubnets) || has(self.role) && self.role ==
                     ''Primary'''
-                - message: MTU should be greater than or equal to 1280 when IPv6 subent
+                - message: MTU should be greater than or equal to 1280 when IPv6 subnet
                     is used
                   rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                     isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
diff --git a/docs/api-reference/userdefinednetwork-api-spec.md b/docs/api-reference/userdefinednetwork-api-spec.md
index b5f843aa729..e053dcb1f76 100644
--- a/docs/api-reference/userdefinednetwork-api-spec.md
+++ b/docs/api-reference/userdefinednetwork-api-spec.md
@@ -16,6 +16,22 @@ Package v1 contains API Schema definitions for the network v1 API group
 
 
 
+#### AccessVLANConfig
+
+
+
+AccessVLANConfig describes an access VLAN configuration.
+
+
+
+_Appears in:_
+- [VLANConfig](#vlanconfig)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `id` _integer_ | id is the VLAN ID (VID) to be set for the network.<br />id should be higher than 0 and lower than 4095. |  | Maximum: 4094 <br />Minimum: 1 <br /> |
+
+
 #### CIDR
 
 _Underlying type:_ _string_
@@ -28,6 +44,7 @@ _Validation:_
 _Appears in:_
 - [DualStackCIDRs](#dualstackcidrs)
 - [Layer3Subnet](#layer3subnet)
+- [LocalnetConfig](#localnetconfig)
 
 
 
@@ -116,6 +133,7 @@ _Validation:_
 _Appears in:_
 - [Layer2Config](#layer2config)
 - [Layer3Config](#layer3config)
+- [LocalnetConfig](#localnetconfig)
 
 
 
@@ -130,11 +148,12 @@ _Validation:_
 
 _Appears in:_
 - [Layer2Config](#layer2config)
+- [LocalnetConfig](#localnetconfig)
 
 | Field | Description | Default | Validation |
 | --- | --- | --- | --- |
 | `mode` _[IPAMMode](#ipammode)_ | Mode controls how much of the IP configuration will be managed by OVN.<br />`Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs<br />from the selected subnet to the individual pods.<br />`Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users<br />configure IP addresses for the pods.<br />`Disabled` is only available for Secondary networks.<br />By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function<br />(such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.<br />Defaults to `Enabled`. |  | Enum: [Enabled Disabled] <br /> |
-| `lifecycle` _[NetworkIPAMLifecycle](#networkipamlifecycle)_ | Lifecycle controls IP addresses management lifecycle.<br />The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an<br />`ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.<br />Only supported when mode is `Enabled`. |  | Enum: [Persistent] <br /> |
+| `lifecycle` _[NetworkIPAMLifecycle](#networkipamlifecycle)_ | Lifecycle controls IP addresses management lifecycle.<br /><br />The only allowed value is Persistent. When set, the IP addresses assigned by OVN Kubernetes will be persisted in an<br />`ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.<br />Only supported when mode is `Enabled`. |  | Enum: [Persistent] <br /> |
 
 
 #### IPAMMode
@@ -169,10 +188,10 @@ _Appears in:_
 
 | Field | Description | Default | Validation |
 | --- | --- | --- | --- |
-| `role` _[NetworkRole](#networkrole)_ | Role describes the network role in the pod.<br />Allowed value is "Secondary".<br />Secondary network is only assigned to pods that use `k8s.v1.cni.cncf.io/networks` annotation to select given network. |  | Enum: [Primary Secondary] <br />Required: \{\} <br /> |
+| `role` _[NetworkRole](#networkrole)_ | Role describes the network role in the pod.<br /><br />Allowed value is "Secondary".<br />Secondary network is only assigned to pods that use `k8s.v1.cni.cncf.io/networks` annotation to select given network. |  | Enum: [Primary Secondary] <br />Required: \{\} <br /> |
 | `mtu` _integer_ | MTU is the maximum transmission unit for a network.<br />MTU is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network. |  | Maximum: 65536 <br />Minimum: 576 <br /> |
-| `subnets` _[DualStackCIDRs](#dualstackcidrs)_ | Subnets are used for the pod network across the cluster.<br />Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.<br />The format should match standard CIDR notation (for example, "10.128.0.0/16").<br />This field must be omitted if `ipam.mode` is `Disabled`. |  | MaxItems: 2 <br />MaxLength: 43 <br />MinItems: 1 <br /> |
-| `joinSubnets` _[DualStackCIDRs](#dualstackcidrs)_ | JoinSubnets are used inside the OVN network topology.<br />Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.<br />This field is only allowed for "Primary" network.<br />It is not recommended to set this field without explicit need and understanding of the OVN network topology.<br />When omitted, the platform will choose a reasonable default which is subject to change over time. |  | MaxItems: 2 <br />MaxLength: 43 <br />MinItems: 1 <br /> |
+| `subnets` _[DualStackCIDRs](#dualstackcidrs)_ | Subnets are used for the pod network across the cluster.<br />Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.<br /><br />The format should match standard CIDR notation (for example, "10.128.0.0/16").<br />This field must be omitted if `ipam.mode` is `Disabled`. |  | MaxItems: 2 <br />MaxLength: 43 <br />MinItems: 1 <br /> |
+| `joinSubnets` _[DualStackCIDRs](#dualstackcidrs)_ | JoinSubnets are used inside the OVN network topology.<br /><br />Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.<br />This field is only allowed for "Primary" network.<br />It is not recommended to set this field without explicit need and understanding of the OVN network topology.<br />When omitted, the platform will choose a reasonable default which is subject to change over time. |  | MaxItems: 2 <br />MaxLength: 43 <br />MinItems: 1 <br /> |
 | `ipam` _[IPAMConfig](#ipamconfig)_ | IPAM section contains IPAM-related configuration for the network. |  | MinProperties: 1 <br /> |
 
 
@@ -190,10 +209,10 @@ _Appears in:_
 
 | Field | Description | Default | Validation |
 | --- | --- | --- | --- |
-| `role` _[NetworkRole](#networkrole)_ | Role describes the network role in the pod.<br />Allowed values are "Primary" and "Secondary".<br />Primary network is automatically assigned to every pod created in the same namespace.<br />Secondary network is only assigned to pods that use `k8s.v1.cni.cncf.io/networks` annotation to select given network. |  | Enum: [Primary Secondary] <br />Required: \{\} <br /> |
-| `mtu` _integer_ | MTU is the maximum transmission unit for a network.<br />MTU is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network. |  | Maximum: 65536 <br />Minimum: 576 <br /> |
-| `subnets` _[Layer3Subnet](#layer3subnet) array_ | Subnets are used for the pod network across the cluster.<br />Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.<br />Given subnet is split into smaller subnets for every node. |  | MaxItems: 2 <br />MinItems: 1 <br /> |
-| `joinSubnets` _[DualStackCIDRs](#dualstackcidrs)_ | JoinSubnets are used inside the OVN network topology.<br />Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.<br />This field is only allowed for "Primary" network.<br />It is not recommended to set this field without explicit need and understanding of the OVN network topology.<br />When omitted, the platform will choose a reasonable default which is subject to change over time. |  | MaxItems: 2 <br />MaxLength: 43 <br />MinItems: 1 <br /> |
+| `role` _[NetworkRole](#networkrole)_ | Role describes the network role in the pod.<br /><br />Allowed values are "Primary" and "Secondary".<br />Primary network is automatically assigned to every pod created in the same namespace.<br />Secondary network is only assigned to pods that use `k8s.v1.cni.cncf.io/networks` annotation to select given network. |  | Enum: [Primary Secondary] <br />Required: \{\} <br /> |
+| `mtu` _integer_ | MTU is the maximum transmission unit for a network.<br /><br />MTU is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network. |  | Maximum: 65536 <br />Minimum: 576 <br /> |
+| `subnets` _[Layer3Subnet](#layer3subnet) array_ | Subnets are used for the pod network across the cluster.<br /><br />Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.<br />Given subnet is split into smaller subnets for every node. |  | MaxItems: 2 <br />MinItems: 1 <br /> |
+| `joinSubnets` _[DualStackCIDRs](#dualstackcidrs)_ | JoinSubnets are used inside the OVN network topology.<br /><br />Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.<br />This field is only allowed for "Primary" network.<br />It is not recommended to set this field without explicit need and understanding of the OVN network topology.<br />When omitted, the platform will choose a reasonable default which is subject to change over time. |  | MaxItems: 2 <br />MaxLength: 43 <br />MinItems: 1 <br /> |
 
 
 #### Layer3Subnet
@@ -210,7 +229,29 @@ _Appears in:_
 | Field | Description | Default | Validation |
 | --- | --- | --- | --- |
 | `cidr` _[CIDR](#cidr)_ | CIDR specifies L3Subnet, which is split into smaller subnets for every node. |  | MaxLength: 43 <br /> |
-| `hostSubnet` _integer_ | HostSubnet specifies the subnet size for every node.<br />When not set, it will be assigned automatically. |  | Maximum: 127 <br />Minimum: 1 <br /> |
+| `hostSubnet` _integer_ | HostSubnet specifies the subnet size for every node.<br /><br />When not set, it will be assigned automatically. |  | Maximum: 127 <br />Minimum: 1 <br /> |
+
+
+#### LocalnetConfig
+
+
+
+
+
+
+
+_Appears in:_
+- [NetworkSpec](#networkspec)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `role` _[NetworkRole](#networkrole)_ | role describes the network role in the pod, required.<br />Controls whether the pod interface will act as primary or secondary.<br />Localnet topology supports `Secondary` only.<br />The network will be assigned to pods that have the `k8s.v1.cni.cncf.io/networks` annotation in place pointing<br />to subject. |  | Enum: [Secondary] <br /> |
+| `physicalNetworkName` _string_ | physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.<br />Min length is 1, max length is 253, cannot contain `,` or `:` characters.<br />In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),<br />this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value. |  | MaxLength: 253 <br />MinLength: 1 <br /> |
+| `subnets` _[DualStackCIDRs](#dualstackcidrs)_ | subnets is a list of subnets used for pods in this localnet network across the cluster.<br />The list may be either 1 IPv4 subnet, 1 IPv6 subnet, or 1 of each IP family.<br />When set, OVN-Kubernetes assigns an IP address from the specified CIDRs to the connected pod,<br />eliminating the need for manual IP assignment or reliance on an external IPAM service (e.g., a DHCP server).<br />subnets is optional. When omitted OVN-Kubernetes won't assign IP address automatically.<br />Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.<br />The format should match standard CIDR notation (for example, "10.128.0.0/16").<br />This field must be omitted if `ipam.mode` is `Disabled`.<br />When physicalNetworkName points to the OVS bridge mapping of a network that provides IPAM services<br />(e.g., a DHCP server), ipam.mode should be set to Disabled. This turns off OVN-Kubernetes IPAM and avoids<br />conflicts with the existing IPAM services on this localnet network. |  | MaxItems: 2 <br />MaxLength: 43 <br />MinItems: 1 <br /> |
+| `excludeSubnets` _[CIDR](#cidr) array_ | excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.<br />The CIDRs in this list must be in range of at least one subnet specified in `subnets`.<br />excludeSubnets is optional. When omitted no IP address is excluded and all IP addresses specified in `subnets`<br />are subject to assignment.<br />The format should match standard CIDR notation (for example, "10.128.0.0/16").<br />This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.<br />When `physicalNetworkName` points to OVS bridge mapping of a network with reserved IP addresses<br />(which shouldn't be assigned by OVN-Kubernetes), the specified CIDRs will not be assigned. For example:<br />Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned<br />to pods: `10.0.0.201`, `10.0.0.202`. |  | MaxItems: 25 <br />MaxLength: 43 <br />MinItems: 1 <br /> |
+| `ipam` _[IPAMConfig](#ipamconfig)_ | ipam configurations for the network.<br />ipam is optional. When omitted, `subnets` must be specified.<br />When `ipam.mode` is `Disabled`, `subnets` must be omitted.<br />`ipam.mode` controls how much of the IP configuration will be managed by OVN.<br />   When `Enabled`, OVN-Kubernetes will apply IP configuration to the SDN infra and assign IPs from the selected<br />   subnet to the pods.<br />   When `Disabled`, OVN-Kubernetes only assigns MAC addresses, and provides layer2 communication, and enables users<br />   to configure IP addresses on the pods.<br />`ipam.lifecycle` controls IP addresses management lifecycle.<br />   When set to 'Persistent', the assigned IP addresses will be persisted in `ipamclaims.k8s.cni.cncf.io` object.<br />	  Useful for VMs, IP address will be persistent after restarts and migrations. Supported when `ipam.mode` is `Enabled`. |  | MinProperties: 1 <br /> |
+| `mtu` _integer_ | mtu is the maximum transmission unit for a network.<br />mtu is optional. When omitted, the configured value in OVN-Kubernetes (defaults to 1500 for localnet topology)<br />is used for the network.<br />Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.<br />Maximum value is 65536.<br />In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,<br />this field enables configuring the same MTU on pod interface, having the pod MTU aligned with the network MTU.<br />Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions<br />and bad performance. |  | Maximum: 65536 <br />Minimum: 576 <br /> |
+| `vlan` _[VLANConfig](#vlanconfig)_ | vlan configuration for the network.<br />vlan.mode is the VLAN mode.<br />  When "Access" is set, OVN-Kubernetes configures the network logical switch port in access mode.<br />vlan.access is the access VLAN configuration.<br />vlan.access.id is the VLAN ID (VID) to be set on the network logical switch port.<br />vlan is optional, when omitted the underlying network default VLAN will be used (usually `1`).<br />When set, OVN-Kubernetes will apply VLAN configuration to the SDN infra and to the connected pods. |  |  |
 
 
 #### NetworkIPAMLifecycle
@@ -236,12 +277,12 @@ _Underlying type:_ _string_
 
 
 
-_Validation:_
-- Enum: [Primary Secondary]
+
 
 _Appears in:_
 - [Layer2Config](#layer2config)
 - [Layer3Config](#layer3config)
+- [LocalnetConfig](#localnetconfig)
 
 | Field | Description |
 | --- | --- |
@@ -262,9 +303,10 @@ _Appears in:_
 
 | Field | Description | Default | Validation |
 | --- | --- | --- | --- |
-| `topology` _[NetworkTopology](#networktopology)_ | Topology describes network configuration.<br />Allowed values are "Layer3", "Layer2".<br />Layer3 topology creates a layer 2 segment per node, each with a different subnet. Layer 3 routing is used to interconnect node subnets.<br />Layer2 topology creates one logical switch shared by all nodes. |  | Enum: [Layer2 Layer3] <br />Required: \{\} <br /> |
+| `topology` _[NetworkTopology](#networktopology)_ | Topology describes network configuration.<br /><br />Allowed values are "Layer3", "Layer2" and "Localnet".<br />Layer3 topology creates a layer 2 segment per node, each with a different subnet. Layer 3 routing is used to interconnect node subnets.<br />Layer2 topology creates one logical switch shared by all nodes.<br />Localnet topology is based on layer 2 topology, but also allows connecting to an existent (configured) physical network to provide north-south traffic to the workloads. |  | Enum: [Layer2 Layer3 Localnet] <br />Required: \{\} <br /> |
 | `layer3` _[Layer3Config](#layer3config)_ | Layer3 is the Layer3 topology configuration. |  |  |
 | `layer2` _[Layer2Config](#layer2config)_ | Layer2 is the Layer2 topology configuration. |  |  |
+| `localnet` _[LocalnetConfig](#localnetconfig)_ | Localnet is the Localnet topology configuration. |  |  |
 
 
 #### NetworkTopology
@@ -273,8 +315,7 @@ _Underlying type:_ _string_
 
 
 
-_Validation:_
-- Enum: [Layer2 Layer3]
+
 
 _Appears in:_
 - [NetworkSpec](#networkspec)
@@ -282,6 +323,7 @@ _Appears in:_
 
 | Field | Description |
 | --- | --- |
+| `Localnet` |  |
 | `Layer2` |  |
 | `Layer3` |  |
 
@@ -337,7 +379,7 @@ _Appears in:_
 
 | Field | Description | Default | Validation |
 | --- | --- | --- | --- |
-| `topology` _[NetworkTopology](#networktopology)_ | Topology describes network configuration.<br />Allowed values are "Layer3", "Layer2".<br />Layer3 topology creates a layer 2 segment per node, each with a different subnet. Layer 3 routing is used to interconnect node subnets.<br />Layer2 topology creates one logical switch shared by all nodes. |  | Enum: [Layer2 Layer3] <br />Required: \{\} <br /> |
+| `topology` _[NetworkTopology](#networktopology)_ | Topology describes network configuration.<br /><br />Allowed values are "Layer3", "Layer2".<br />Layer3 topology creates a layer 2 segment per node, each with a different subnet. Layer 3 routing is used to interconnect node subnets.<br />Layer2 topology creates one logical switch shared by all nodes. |  | Enum: [Layer2 Layer3] <br />Required: \{\} <br /> |
 | `layer3` _[Layer3Config](#layer3config)_ | Layer3 is the Layer3 topology configuration. |  |  |
 | `layer2` _[Layer2Config](#layer2config)_ | Layer2 is the Layer2 topology configuration. |  |  |
 
@@ -358,3 +400,37 @@ _Appears in:_
 | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#condition-v1-meta) array_ |  |  |  |
 
 
+#### VLANConfig
+
+
+
+VLANConfig describes the network VLAN configuration.
+
+
+
+_Appears in:_
+- [LocalnetConfig](#localnetconfig)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `mode` _[VLANMode](#vlanmode)_ | mode describe the network VLAN mode.<br />Allowed value is "Access".<br />Access sets the network logical switch port in access mode, according to the config. |  | Enum: [Access] <br /> |
+| `access` _[AccessVLANConfig](#accessvlanconfig)_ | Access is the access VLAN configuration |  |  |
+
+
+#### VLANMode
+
+_Underlying type:_ _string_
+
+
+
+_Validation:_
+- Enum: [Access]
+
+_Appears in:_
+- [VLANConfig](#vlanconfig)
+
+| Field | Description |
+| --- | --- |
+| `Access` |  |
+
+
diff --git a/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go b/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go
index 0d00aaa8555..8d11c0960b8 100644
--- a/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go
+++ b/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go
@@ -13,6 +13,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	ovncnitypes "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/cni/types"
+	"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config"
 	userdefinednetworkv1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1"
 	"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types"
 	"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util"
@@ -31,6 +32,7 @@ type SpecGetter interface {
 	GetTopology() userdefinednetworkv1.NetworkTopology
 	GetLayer3() *userdefinednetworkv1.Layer3Config
 	GetLayer2() *userdefinednetworkv1.Layer2Config
+	GetLocalnet() *userdefinednetworkv1.LocalnetConfig
 }
 
 func RenderNetAttachDefManifest(obj client.Object, targetNamespace string) (*netv1.NetworkAttachmentDefinition, error) {
@@ -109,7 +111,8 @@ func renderNADLabels(obj client.Object) map[string]string {
 
 func validateTopology(spec SpecGetter) error {
 	if spec.GetTopology() == userdefinednetworkv1.NetworkTopologyLayer3 && spec.GetLayer3() == nil ||
-		spec.GetTopology() == userdefinednetworkv1.NetworkTopologyLayer2 && spec.GetLayer2() == nil {
+		spec.GetTopology() == userdefinednetworkv1.NetworkTopologyLayer2 && spec.GetLayer2() == nil ||
+		spec.GetTopology() == userdefinednetworkv1.NetworkTopologyLocalnet && spec.GetLocalnet() == nil {
 		return fmt.Errorf("topology %[1]s is specified but %[1]s config is nil", spec.GetTopology())
 	}
 	return nil
@@ -138,17 +141,30 @@ func renderCNINetworkConfig(networkName, nadName string, spec SpecGetter) (map[s
 		if err := validateIPAM(cfg.IPAM); err != nil {
 			return nil, err
 		}
-		netConfSpec.Role = strings.ToLower(string(cfg.Role))
-		netConfSpec.MTU = int(cfg.MTU)
-		netConfSpec.AllowPersistentIPs = cfg.IPAM != nil && cfg.IPAM.Lifecycle == userdefinednetworkv1.IPAMLifecyclePersistent
 		if ipamEnabled(cfg.IPAM) && len(cfg.Subnets) == 0 {
 			return nil, fmt.Errorf("subnets is required with ipam.mode is Enabled or unset")
 		}
 		if !ipamEnabled(cfg.IPAM) && len(cfg.Subnets) > 0 {
 			return nil, fmt.Errorf("subnets must be unset when ipam.mode is Disabled")
 		}
+
+		netConfSpec.Role = strings.ToLower(string(cfg.Role))
+		netConfSpec.MTU = int(cfg.MTU)
+		netConfSpec.AllowPersistentIPs = cfg.IPAM != nil && cfg.IPAM.Lifecycle == userdefinednetworkv1.IPAMLifecyclePersistent
 		netConfSpec.Subnets = cidrString(cfg.Subnets)
 		netConfSpec.JoinSubnet = cidrString(renderJoinSubnets(cfg.Role, cfg.JoinSubnets))
+	case userdefinednetworkv1.NetworkTopologyLocalnet:
+		cfg := spec.GetLocalnet()
+		netConfSpec.Role = strings.ToLower(string(cfg.Role))
+		netConfSpec.MTU = localnetMTU(cfg.MTU)
+		netConfSpec.AllowPersistentIPs = cfg.IPAM != nil && cfg.IPAM.Lifecycle == userdefinednetworkv1.IPAMLifecyclePersistent
+		netConfSpec.Subnets = cidrString(cfg.Subnets)
+		netConfSpec.ExcludeSubnets = cidrString(cfg.ExcludeSubnets)
+		netConfSpec.PhysicalNetworkName = cfg.PhysicalNetworkName
+
+		if cfg.VLAN != nil && cfg.VLAN.Access != nil {
+			netConfSpec.VLANID = int(cfg.VLAN.Access.ID)
+		}
 	}
 
 	if err := util.ValidateNetConf(nadName, netConfSpec); err != nil {
@@ -184,10 +200,29 @@ func renderCNINetworkConfig(networkName, nadName string, spec SpecGetter) (map[s
 	if netConfSpec.AllowPersistentIPs {
 		cniNetConf["allowPersistentIPs"] = netConfSpec.AllowPersistentIPs
 	}
-
+	if netConfSpec.PhysicalNetworkName != "" {
+		cniNetConf["physicalNetworkName"] = netConfSpec.PhysicalNetworkName
+	}
+	if len(netConfSpec.ExcludeSubnets) > 0 {
+		cniNetConf["excludeSubnets"] = netConfSpec.ExcludeSubnets
+	}
+	if netConfSpec.VLANID != 0 {
+		cniNetConf["vlanID"] = netConfSpec.VLANID
+	}
 	return cniNetConf, nil
 }
 
+func localnetMTU(desiredMTU int32) int {
+	// The MTU for localnet topology should be as the default MTU (1500) because the underlay
+	// is not part of the SDN and compensating for the SDN overhead (100) is not required.
+	mtu := config.Default.MTU + 100
+	if desiredMTU > 0 {
+		mtu = int(desiredMTU)
+	}
+
+	return mtu
+}
+
 func ipamEnabled(ipam *userdefinednetworkv1.IPAMConfig) bool {
 	return ipam == nil || ipam.Mode == "" || ipam.Mode == userdefinednetworkv1.IPAMEnabled
 }
diff --git a/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go b/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go
index d8192afe31e..0c06f3a2705 100644
--- a/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go
+++ b/go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go
@@ -169,7 +169,7 @@ var _ = Describe("NetAttachDefTemplate", func() {
 		),
 	)
 
-	DescribeTable("should fail to render NAD, given",
+	DescribeTable("should fail to render NAD manifest, given",
 		func(obj client.Object) {
 			_, err := RenderNetAttachDefManifest(obj, "test")
 			Expect(err).To(HaveOccurred())
@@ -223,10 +223,35 @@ var _ = Describe("NetAttachDefTemplate", func() {
 			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
 				Topology: udnv1.NetworkTopologyLayer2, Layer3: &udnv1.Layer3Config{}}}},
 		),
+		Entry("CUDN, invalid topology: topology layer2 & localnet config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLayer2, Localnet: &udnv1.LocalnetConfig{}}}},
+		),
 		Entry("CUDN, invalid topology: topology layer3 & layer2 config",
 			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
 				Topology: udnv1.NetworkTopologyLayer3, Layer2: &udnv1.Layer2Config{}}}},
 		),
+		Entry("CUDN, invalid topology: topology layer3 & localnet config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLayer3, Localnet: &udnv1.LocalnetConfig{}}}},
+		),
+		Entry("CUDN, invalid topology: topology localnet & layer2 config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Layer2: &udnv1.Layer2Config{}}}},
+		),
+		Entry("CUDN, invalid topology: topology localnet & layer3 config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Layer3: &udnv1.Layer3Config{}}}},
+		),
+		Entry("CUDN, localnet: excludeSubnets not in range of subnets",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{Role: udnv1.NetworkRoleSecondary, PhysicalNetworkName: "localnet1",
+					Subnets:        udnv1.DualStackCIDRs{"192.168.0.0/16", "2001:dbb::/64"},
+					ExcludeSubnets: []udnv1.CIDR{"192.200.0.0/30", "2001:aaa::/127", "192.300.0.1/32", "2001:bbb::1/120"},
+				},
+			}}},
+		),
 	)
 
 	It("should return no error given no UDN", func() {
@@ -492,7 +517,7 @@ var _ = Describe("NetAttachDefTemplate", func() {
 			  "allowPersistentIPs": true
 			}`,
 		),
-		Entry("secondary network",
+		Entry("secondary network, layer2",
 			udnv1.NetworkSpec{
 				Topology: udnv1.NetworkTopologyLayer2,
 				Layer2: &udnv1.Layer2Config{
@@ -516,5 +541,64 @@ var _ = Describe("NetAttachDefTemplate", func() {
 			  "allowPersistentIPs": true
 			}`,
 		),
+		Entry("secondary network, localnet",
+			udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRoleSecondary,
+					PhysicalNetworkName: "mylocalnet1",
+					MTU:                 1600,
+					VLAN:                &udnv1.VLANConfig{Mode: udnv1.VLANModeAccess, Access: &udnv1.AccessVLANConfig{ID: 200}},
+					Subnets:             udnv1.DualStackCIDRs{"192.168.100.0/24", "2001:dbb::/64"},
+					ExcludeSubnets:      []udnv1.CIDR{"192.168.100.1/32", "2001:dbb::0/128"},
+					IPAM: &udnv1.IPAMConfig{
+						Lifecycle: udnv1.IPAMLifecyclePersistent,
+					},
+				},
+			},
+			`{
+			  "cniVersion": "1.0.0",
+			  "type": "ovn-k8s-cni-overlay",
+			  "name": "cluster_udn_test-net",
+			  "netAttachDefName": "mynamespace/test-net",
+			  "role": "secondary",
+			  "topology": "localnet",
+		      "physicalNetworkName": "mylocalnet1",
+			  "subnets": "192.168.100.0/24,2001:dbb::/64",
+              "excludeSubnets": "192.168.100.1/32,2001:dbb::0/128",
+			  "mtu": 1600,
+              "vlanID": 200, 
+			  "allowPersistentIPs": true
+			}`,
+		),
+		Entry("secondary network, localnet, when MTU is unset it should set default MTU",
+			udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRoleSecondary,
+					PhysicalNetworkName: "mylocalnet1",
+					VLAN:                &udnv1.VLANConfig{Mode: udnv1.VLANModeAccess, Access: &udnv1.AccessVLANConfig{ID: 200}},
+					Subnets:             udnv1.DualStackCIDRs{"192.168.100.0/24", "2001:dbb::/64"},
+					ExcludeSubnets:      []udnv1.CIDR{"192.168.100.1/32", "2001:dbb::0/128"},
+					IPAM: &udnv1.IPAMConfig{
+						Lifecycle: udnv1.IPAMLifecyclePersistent,
+					},
+				},
+			},
+			`{
+			  "cniVersion": "1.0.0",
+			  "type": "ovn-k8s-cni-overlay",
+			  "name": "cluster_udn_test-net",
+			  "netAttachDefName": "mynamespace/test-net",
+			  "role": "secondary",
+			  "topology": "localnet",
+		      "physicalNetworkName": "mylocalnet1",
+			  "subnets": "192.168.100.0/24,2001:dbb::/64",
+              "excludeSubnets": "192.168.100.1/32,2001:dbb::0/128",
+			  "mtu": 1500,
+              "vlanID": 200, 
+			  "allowPersistentIPs": true
+			}`,
+		),
 	)
 })
diff --git a/go-controller/pkg/crd/userdefinednetwork/v1/apis/applyconfiguration/userdefinednetwork/v1/accessvlanconfig.go b/go-controller/pkg/crd/userdefinednetwork/v1/apis/applyconfiguration/userdefinednetwork/v1/accessvlanconfig.go
new file mode 100644
index 00000000000..72aa7a9fa52
--- /dev/null
+++ b/go-controller/pkg/crd/userdefinednetwork/v1/apis/applyconfiguration/userdefinednetwork/v1/accessvlanconfig.go
@@ -0,0 +1,38 @@
+/*
+
+
+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.
+*/
+// Code generated by applyconfiguration-gen. DO NOT EDIT.
+
+package v1
+
+// AccessVLANConfigApplyConfiguration represents a declarative configuration of the AccessVLANConfig type for use
+// with apply.
+type AccessVLANConfigApplyConfiguration struct {
+	ID *int32 `json:"id,omitempty"`
+}
+
+// AccessVLANConfigApplyConfiguration constructs a declarative configuration of the AccessVLANConfig type for use with
+// apply.
+func AccessVLANConfig() *AccessVLANConfigApplyConfiguration {
+	return &AccessVLANConfigApplyConfiguration{}
+}
+
+// WithID sets the ID field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the ID field is set to the value of the last call.
+func (b *AccessVLANConfigApplyConfiguration) WithID(value int32) *AccessVLANConfigApplyConfiguration {
+	b.ID = &value
+	return b
+}
diff --git a/go-controller/pkg/crd/userdefinednetwork/v1/apis/applyconfiguration/userdefinednetwork/v1/localnetconfig.go b/go-controller/pkg/crd/userdefinednetwork/v1/apis/applyconfiguration/userdefinednetwork/v1/localnetconfig.go
new file mode 100644
index 00000000000..9da3ff00106
--- /dev/null
+++ b/go-controller/pkg/crd/userdefinednetwork/v1/apis/applyconfiguration/userdefinednetwork/v1/localnetconfig.go
@@ -0,0 +1,98 @@
+/*
+
+
+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.
+*/
+// Code generated by applyconfiguration-gen. DO NOT EDIT.
+
+package v1
+
+import (
+	userdefinednetworkv1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1"
+)
+
+// LocalnetConfigApplyConfiguration represents a declarative configuration of the LocalnetConfig type for use
+// with apply.
+type LocalnetConfigApplyConfiguration struct {
+	Role                *userdefinednetworkv1.NetworkRole    `json:"role,omitempty"`
+	PhysicalNetworkName *string                              `json:"physicalNetworkName,omitempty"`
+	Subnets             *userdefinednetworkv1.DualStackCIDRs `json:"subnets,omitempty"`
+	ExcludeSubnets      []userdefinednetworkv1.CIDR          `json:"excludeSubnets,omitempty"`
+	IPAM                *IPAMConfigApplyConfiguration        `json:"ipam,omitempty"`
+	MTU                 *int32                               `json:"mtu,omitempty"`
+	VLAN                *VLANConfigApplyConfiguration        `json:"vlan,omitempty"`
+}
+
+// LocalnetConfigApplyConfiguration constructs a declarative configuration of the LocalnetConfig type for use with
+// apply.
+func LocalnetConfig() *LocalnetConfigApplyConfiguration {
+	return &LocalnetConfigApplyConfiguration{}
+}
+
+// WithRole sets the Role field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Role field is set to the value of the last call.
+func (b *LocalnetConfigApplyConfiguration) WithRole(value userdefinednetworkv1.NetworkRole) *LocalnetConfigApplyConfiguration {
+	b.Role = &value
+	return b
+}
+
+// WithPhysicalNetworkName sets the PhysicalNetworkName field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the PhysicalNetworkName field is set to the value of the last call.
+func (b *LocalnetConfigApplyConfiguration) WithPhysicalNetworkName(value string) *LocalnetConfigApplyConfiguration {
+	b.PhysicalNetworkName = &value
+	return b
+}
+
+// WithSubnets sets the Subnets field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Subnets field is set to the value of the last call.
+func (b *LocalnetConfigApplyConfiguration) WithSubnets(value userdefinednetworkv1.DualStackCIDRs) *LocalnetConfigApplyConfiguration {
+	b.Subnets = &value
+	return b
+}
+
+// WithExcludeSubnets adds the given value to the ExcludeSubnets field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, values provided by each call will be appended to the ExcludeSubnets field.
+func (b *LocalnetConfigApplyConfiguration) WithExcludeSubnets(values ...userdefinednetworkv1.CIDR) *LocalnetConfigApplyConfiguration {
+	for i := range values {
+		b.ExcludeSubnets = append(b.ExcludeSubnets, values[i])
+	}
+	return b
+}
+
+// WithIPAM sets the IPAM field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the IPAM field is set to the value of the last call.
+func (b *LocalnetConfigApplyConfiguration) WithIPAM(value *IPAMConfigApplyConfiguration) *LocalnetConfigApplyConfiguration {
+	b.IPAM = value
+	return b
+}
+
+// WithMTU sets the MTU field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the MTU field is set to the value of the last call.
+func (b *LocalnetConfigApplyConfiguration) WithMTU(value int32) *LocalnetConfigApplyConfiguration {
+	b.MTU = &value
+	return b
+}
+
+// WithVLAN sets the VLAN field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the VLAN field is set to the value of the last call.
+func (b *LocalnetConfigApplyConfiguration) WithVLAN(value *VLANConfigApplyConfiguration) *LocalnetConfigApplyConfiguration {
+	b.VLAN = value
+	return b
+}
diff --git a/go-controller/pkg/crd/userdefinednetwork/v1/apis/applyconfiguration/userdefinednetwork/v1/networkspec.go b/go-controller/pkg/crd/userdefinednetwork/v1/apis/applyconfiguration/userdefinednetwork/v1/networkspec.go
index 24cd32d1301..ea298e1e6cf 100644
--- a/go-controller/pkg/crd/userdefinednetwork/v1/apis/applyconfiguration/userdefinednetwork/v1/networkspec.go
+++ b/go-controller/pkg/crd/userdefinednetwork/v1/apis/applyconfiguration/userdefinednetwork/v1/networkspec.go
@@ -27,6 +27,7 @@ type NetworkSpecApplyConfiguration struct {
 	Topology *userdefinednetworkv1.NetworkTopology `json:"topology,omitempty"`
 	Layer3   *Layer3ConfigApplyConfiguration       `json:"layer3,omitempty"`
 	Layer2   *Layer2ConfigApplyConfiguration       `json:"layer2,omitempty"`
+	Localnet *LocalnetConfigApplyConfiguration     `json:"localnet,omitempty"`
 }
 
 // NetworkSpecApplyConfiguration constructs a declarative configuration of the NetworkSpec type for use with
@@ -58,3 +59,11 @@ func (b *NetworkSpecApplyConfiguration) WithLayer2(value *Layer2ConfigApplyConfi
 	b.Layer2 = value
 	return b
 }
+
+// WithLocalnet sets the Localnet field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Localnet field is set to the value of the last call.
+func (b *NetworkSpecApplyConfiguration) WithLocalnet(value *LocalnetConfigApplyConfiguration) *NetworkSpecApplyConfiguration {
+	b.Localnet = value
+	return b
+}
diff --git a/go-controller/pkg/crd/userdefinednetwork/v1/apis/applyconfiguration/userdefinednetwork/v1/vlanconfig.go b/go-controller/pkg/crd/userdefinednetwork/v1/apis/applyconfiguration/userdefinednetwork/v1/vlanconfig.go
new file mode 100644
index 00000000000..8cb8fc5b87b
--- /dev/null
+++ b/go-controller/pkg/crd/userdefinednetwork/v1/apis/applyconfiguration/userdefinednetwork/v1/vlanconfig.go
@@ -0,0 +1,51 @@
+/*
+
+
+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.
+*/
+// Code generated by applyconfiguration-gen. DO NOT EDIT.
+
+package v1
+
+import (
+	userdefinednetworkv1 "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/userdefinednetwork/v1"
+)
+
+// VLANConfigApplyConfiguration represents a declarative configuration of the VLANConfig type for use
+// with apply.
+type VLANConfigApplyConfiguration struct {
+	Mode   *userdefinednetworkv1.VLANMode      `json:"mode,omitempty"`
+	Access *AccessVLANConfigApplyConfiguration `json:"access,omitempty"`
+}
+
+// VLANConfigApplyConfiguration constructs a declarative configuration of the VLANConfig type for use with
+// apply.
+func VLANConfig() *VLANConfigApplyConfiguration {
+	return &VLANConfigApplyConfiguration{}
+}
+
+// WithMode sets the Mode field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Mode field is set to the value of the last call.
+func (b *VLANConfigApplyConfiguration) WithMode(value userdefinednetworkv1.VLANMode) *VLANConfigApplyConfiguration {
+	b.Mode = &value
+	return b
+}
+
+// WithAccess sets the Access field in the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" function invocations.
+// If called multiple times, the Access field is set to the value of the last call.
+func (b *VLANConfigApplyConfiguration) WithAccess(value *AccessVLANConfigApplyConfiguration) *VLANConfigApplyConfiguration {
+	b.Access = value
+	return b
+}
diff --git a/go-controller/pkg/crd/userdefinednetwork/v1/apis/applyconfiguration/utils.go b/go-controller/pkg/crd/userdefinednetwork/v1/apis/applyconfiguration/utils.go
index 83c79739ffd..4942f0f927a 100644
--- a/go-controller/pkg/crd/userdefinednetwork/v1/apis/applyconfiguration/utils.go
+++ b/go-controller/pkg/crd/userdefinednetwork/v1/apis/applyconfiguration/utils.go
@@ -31,6 +31,8 @@ import (
 func ForKind(kind schema.GroupVersionKind) interface{} {
 	switch kind {
 	// Group=k8s.ovn.org, Version=v1
+	case v1.SchemeGroupVersion.WithKind("AccessVLANConfig"):
+		return &userdefinednetworkv1.AccessVLANConfigApplyConfiguration{}
 	case v1.SchemeGroupVersion.WithKind("ClusterUserDefinedNetwork"):
 		return &userdefinednetworkv1.ClusterUserDefinedNetworkApplyConfiguration{}
 	case v1.SchemeGroupVersion.WithKind("ClusterUserDefinedNetworkSpec"):
@@ -45,6 +47,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
 		return &userdefinednetworkv1.Layer3ConfigApplyConfiguration{}
 	case v1.SchemeGroupVersion.WithKind("Layer3Subnet"):
 		return &userdefinednetworkv1.Layer3SubnetApplyConfiguration{}
+	case v1.SchemeGroupVersion.WithKind("LocalnetConfig"):
+		return &userdefinednetworkv1.LocalnetConfigApplyConfiguration{}
 	case v1.SchemeGroupVersion.WithKind("NetworkSpec"):
 		return &userdefinednetworkv1.NetworkSpecApplyConfiguration{}
 	case v1.SchemeGroupVersion.WithKind("UserDefinedNetwork"):
@@ -53,6 +57,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
 		return &userdefinednetworkv1.UserDefinedNetworkSpecApplyConfiguration{}
 	case v1.SchemeGroupVersion.WithKind("UserDefinedNetworkStatus"):
 		return &userdefinednetworkv1.UserDefinedNetworkStatusApplyConfiguration{}
+	case v1.SchemeGroupVersion.WithKind("VLANConfig"):
+		return &userdefinednetworkv1.VLANConfigApplyConfiguration{}
 
 	}
 	return nil
diff --git a/go-controller/pkg/crd/userdefinednetwork/v1/cudn.go b/go-controller/pkg/crd/userdefinednetwork/v1/cudn.go
index 15443c8d091..ace64a06c95 100644
--- a/go-controller/pkg/crd/userdefinednetwork/v1/cudn.go
+++ b/go-controller/pkg/crd/userdefinednetwork/v1/cudn.go
@@ -32,6 +32,7 @@ type ClusterUserDefinedNetworkSpec struct {
 	// +kubebuilder:validation:Required
 	// +kubebuilder:validation:XValidation:rule="has(self.topology) && self.topology == 'Layer3' ? has(self.layer3): !has(self.layer3)", message="spec.layer3 is required when topology is Layer3 and forbidden otherwise"
 	// +kubebuilder:validation:XValidation:rule="has(self.topology) && self.topology == 'Layer2' ? has(self.layer2): !has(self.layer2)", message="spec.layer2 is required when topology is Layer2 and forbidden otherwise"
+	// +kubebuilder:validation:XValidation:rule="has(self.topology) && self.topology == 'Localnet' ? has(self.localnet): !has(self.localnet)", message="spec.localnet is required when topology is Localnet and forbidden otherwise"
 	// +kubebuilder:validation:XValidation:rule="self == oldSelf", message="Network spec is immutable"
 	// +required
 	Network NetworkSpec `json:"network"`
@@ -42,10 +43,12 @@ type ClusterUserDefinedNetworkSpec struct {
 type NetworkSpec struct {
 	// Topology describes network configuration.
 	//
-	// Allowed values are "Layer3", "Layer2".
+	// Allowed values are "Layer3", "Layer2" and "Localnet".
 	// Layer3 topology creates a layer 2 segment per node, each with a different subnet. Layer 3 routing is used to interconnect node subnets.
 	// Layer2 topology creates one logical switch shared by all nodes.
+	// Localnet topology is based on layer 2 topology, but also allows connecting to an existent (configured) physical network to provide north-south traffic to the workloads.
 	//
+	// +kubebuilder:validation:Enum=Layer2;Layer3;Localnet
 	// +kubebuilder:validation:Required
 	// +required
 	// +unionDiscriminator
@@ -58,6 +61,10 @@ type NetworkSpec struct {
 	// Layer2 is the Layer2 topology configuration.
 	// +optional
 	Layer2 *Layer2Config `json:"layer2,omitempty"`
+
+	// Localnet is the Localnet topology configuration.
+	// +optional
+	Localnet *LocalnetConfig `json:"localnet,omitempty"`
 }
 
 // ClusterUserDefinedNetworkStatus contains the observed status of the ClusterUserDefinedNetwork.
@@ -77,3 +84,137 @@ type ClusterUserDefinedNetworkList struct {
 	metav1.ListMeta `json:"metadata,omitempty"`
 	Items           []ClusterUserDefinedNetwork `json:"items"`
 }
+
+const NetworkTopologyLocalnet NetworkTopology = "Localnet"
+
+// +kubebuilder:validation:XValidation:rule="!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode == 'Enabled' ? has(self.subnets) : !has(self.subnets)", message="Subnets is required with ipam.mode is Enabled or unset, and forbidden otherwise"
+// +kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || has(self.subnets)", message="excludeSubnets must be unset when subnets is unset"
+// +kubebuilder:validation:XValidation:rule="!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280", message="MTU should be greater than or equal to 1280 when an IPv6 subnet is used"
+// + ---
+// + TODO: enable the below validation once the following issue is resolved https://github.com/kubernetes/kubernetes/issues/130441
+// + kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || self.excludeSubnets.all(e, self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))",message="excludeSubnets must be subnetworks of the networks specified in the subnets field",fieldPath=".excludeSubnets"
+type LocalnetConfig struct {
+	// role describes the network role in the pod, required.
+	// Controls whether the pod interface will act as primary or secondary.
+	// Localnet topology supports `Secondary` only.
+	// The network will be assigned to pods that have the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+	// to subject.
+	//
+	// +kubebuilder:validation:Enum=Secondary
+	// +required
+	Role NetworkRole `json:"role"`
+
+	// physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+	// Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+	// In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+	// this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+	//
+	// +kubebuilder:validation:MinLength=1
+	// +kubebuilder:validation:MaxLength=253
+	// +kubebuilder:validation:XValidation:rule="self.matches('^[^,:]+$')", message="physicalNetworkName cannot contain `,` or `:` characters"
+	// +required
+	PhysicalNetworkName string `json:"physicalNetworkName"`
+
+	// subnets is a list of subnets used for pods in this localnet network across the cluster.
+	// The list may be either 1 IPv4 subnet, 1 IPv6 subnet, or 1 of each IP family.
+	// When set, OVN-Kubernetes assigns an IP address from the specified CIDRs to the connected pod,
+	// eliminating the need for manual IP assignment or reliance on an external IPAM service (e.g., a DHCP server).
+	// subnets is optional. When omitted OVN-Kubernetes won't assign IP address automatically.
+	// Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.
+	// The format should match standard CIDR notation (for example, "10.128.0.0/16").
+	// This field must be omitted if `ipam.mode` is `Disabled`.
+	// When physicalNetworkName points to the OVS bridge mapping of a network that provides IPAM services
+	// (e.g., a DHCP server), ipam.mode should be set to Disabled. This turns off OVN-Kubernetes IPAM and avoids
+	// conflicts with the existing IPAM services on this localnet network.
+	//
+	// +optional
+	Subnets DualStackCIDRs `json:"subnets,omitempty"`
+
+	// excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+	// The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+	// excludeSubnets is optional. When omitted no IP address is excluded and all IP addresses specified in `subnets`
+	// are subject to assignment.
+	// The format should match standard CIDR notation (for example, "10.128.0.0/16").
+	// This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+	// When `physicalNetworkName` points to OVS bridge mapping of a network with reserved IP addresses
+	// (which shouldn't be assigned by OVN-Kubernetes), the specified CIDRs will not be assigned. For example:
+	// Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+	// to pods: `10.0.0.201`, `10.0.0.202`.
+	//
+	// +optional
+	// +kubebuilder:validation:MinItems=1
+	// +kubebuilder:validation:MaxItems=25
+	ExcludeSubnets []CIDR `json:"excludeSubnets,omitempty"`
+
+	// ipam configurations for the network.
+	// ipam is optional. When omitted, `subnets` must be specified.
+	// When `ipam.mode` is `Disabled`, `subnets` must be omitted.
+	// `ipam.mode` controls how much of the IP configuration will be managed by OVN.
+	//    When `Enabled`, OVN-Kubernetes will apply IP configuration to the SDN infra and assign IPs from the selected
+	//    subnet to the pods.
+	//    When `Disabled`, OVN-Kubernetes only assigns MAC addresses, and provides layer2 communication, and enables users
+	//    to configure IP addresses on the pods.
+	// `ipam.lifecycle` controls IP addresses management lifecycle.
+	//    When set to 'Persistent', the assigned IP addresses will be persisted in `ipamclaims.k8s.cni.cncf.io` object.
+	// 	  Useful for VMs, IP address will be persistent after restarts and migrations. Supported when `ipam.mode` is `Enabled`.
+	//
+	// +optional
+	IPAM *IPAMConfig `json:"ipam,omitempty"`
+
+	// mtu is the maximum transmission unit for a network.
+	// mtu is optional. When omitted, the configured value in OVN-Kubernetes (defaults to 1500 for localnet topology)
+	// is used for the network.
+	// Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+	// Maximum value is 65536.
+	// In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+	// this field enables configuring the same MTU on pod interface, having the pod MTU aligned with the network MTU.
+	// Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+	// and bad performance.
+	//
+	// +kubebuilder:validation:Minimum=576
+	// +kubebuilder:validation:Maximum=65536
+	// +optional
+	MTU int32 `json:"mtu,omitempty"`
+
+	// vlan configuration for the network.
+	// vlan.mode is the VLAN mode.
+	//   When "Access" is set, OVN-Kubernetes configures the network logical switch port in access mode.
+	// vlan.access is the access VLAN configuration.
+	// vlan.access.id is the VLAN ID (VID) to be set on the network logical switch port.
+	// vlan is optional, when omitted the underlying network default VLAN will be used (usually `1`).
+	// When set, OVN-Kubernetes will apply VLAN configuration to the SDN infra and to the connected pods.
+	//
+	// +optional
+	VLAN *VLANConfig `json:"vlan,omitempty"`
+}
+
+// AccessVLANConfig describes an access VLAN configuration.
+type AccessVLANConfig struct {
+	// id is the VLAN ID (VID) to be set for the network.
+	// id should be higher than 0 and lower than 4095.
+	// +required
+	// +kubebuilder:validation:Minimum=1
+	// +kubebuilder:validation:Maximum=4094
+	ID int32 `json:"id"`
+}
+
+// +kubebuilder:validation:Enum=Access
+type VLANMode string
+
+const VLANModeAccess VLANMode = "Access"
+
+// VLANConfig describes the network VLAN configuration.
+// +union
+// +kubebuilder:validation:XValidation:rule="has(self.mode) && self.mode == 'Access' ? has(self.access): !has(self.access)", message="vlan access config is required when vlan mode is 'Access', and forbidden otherwise"
+type VLANConfig struct {
+	// mode describe the network VLAN mode.
+	// Allowed value is "Access".
+	// Access sets the network logical switch port in access mode, according to the config.
+	// +required
+	// +unionDiscriminator
+	Mode VLANMode `json:"mode"`
+
+	// Access is the access VLAN configuration
+	// +optional
+	Access *AccessVLANConfig `json:"access"`
+}
diff --git a/go-controller/pkg/crd/userdefinednetwork/v1/shared.go b/go-controller/pkg/crd/userdefinednetwork/v1/shared.go
index 03821d196bb..b3a32b18886 100644
--- a/go-controller/pkg/crd/userdefinednetwork/v1/shared.go
+++ b/go-controller/pkg/crd/userdefinednetwork/v1/shared.go
@@ -16,7 +16,6 @@ limitations under the License.
 
 package v1
 
-// +kubebuilder:validation:Enum=Layer2;Layer3
 type NetworkTopology string
 
 const (
@@ -25,7 +24,7 @@ const (
 )
 
 // +kubebuilder:validation:XValidation:rule="!has(self.joinSubnets) || has(self.role) && self.role == 'Primary'", message="JoinSubnets is only supported for Primary network"
-// +kubebuilder:validation:XValidation:rule="!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu >= 1280", message="MTU should be greater than or equal to 1280 when IPv6 subent is used"
+// +kubebuilder:validation:XValidation:rule="!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu >= 1280", message="MTU should be greater than or equal to 1280 when IPv6 subnet is used"
 type Layer3Config struct {
 	// Role describes the network role in the pod.
 	//
@@ -33,6 +32,7 @@ type Layer3Config struct {
 	// Primary network is automatically assigned to every pod created in the same namespace.
 	// Secondary network is only assigned to pods that use `k8s.v1.cni.cncf.io/networks` annotation to select given network.
 	//
+	// +kubebuilder:validation:Enum=Primary;Secondary
 	// +kubebuilder:validation:Required
 	// +required
 	Role NetworkRole `json:"role"`
@@ -90,13 +90,14 @@ type Layer3Subnet struct {
 // +kubebuilder:validation:XValidation:rule="!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode != 'Disabled' || !has(self.subnets)", message="Subnets must be unset when ipam.mode is Disabled"
 // +kubebuilder:validation:XValidation:rule="!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode != 'Disabled' || self.role == 'Secondary'", message="Disabled ipam.mode is only supported for Secondary network"
 // +kubebuilder:validation:XValidation:rule="!has(self.joinSubnets) || has(self.role) && self.role == 'Primary'", message="JoinSubnets is only supported for Primary network"
-// +kubebuilder:validation:XValidation:rule="!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280", message="MTU should be greater than or equal to 1280 when IPv6 subent is used"
+// +kubebuilder:validation:XValidation:rule="!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280", message="MTU should be greater than or equal to 1280 when IPv6 subnet is used"
 type Layer2Config struct {
 	// Role describes the network role in the pod.
 	//
 	// Allowed value is "Secondary".
 	// Secondary network is only assigned to pods that use `k8s.v1.cni.cncf.io/networks` annotation to select given network.
 	//
+	// +kubebuilder:validation:Enum=Primary;Secondary
 	// +kubebuilder:validation:Required
 	// +required
 	Role NetworkRole `json:"role"`
@@ -150,7 +151,7 @@ type IPAMConfig struct {
 
 	// Lifecycle controls IP addresses management lifecycle.
 	//
-	// The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+	// The only allowed value is Persistent. When set, the IP addresses assigned by OVN Kubernetes will be persisted in an
 	// `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
 	// Only supported when mode is `Enabled`.
 	//
@@ -166,7 +167,6 @@ const (
 	IPAMDisabled IPAMMode = "Disabled"
 )
 
-// +kubebuilder:validation:Enum=Primary;Secondary
 type NetworkRole string
 
 const (
diff --git a/go-controller/pkg/crd/userdefinednetwork/v1/spec.go b/go-controller/pkg/crd/userdefinednetwork/v1/spec.go
index f868f564236..cd65f082233 100644
--- a/go-controller/pkg/crd/userdefinednetwork/v1/spec.go
+++ b/go-controller/pkg/crd/userdefinednetwork/v1/spec.go
@@ -12,6 +12,11 @@ func (s *UserDefinedNetworkSpec) GetLayer2() *Layer2Config {
 	return s.Layer2
 }
 
+func (s *UserDefinedNetworkSpec) GetLocalnet() *LocalnetConfig {
+	// localnet is not supported
+	return nil
+}
+
 func (s *NetworkSpec) GetTopology() NetworkTopology {
 	return s.Topology
 }
@@ -23,3 +28,7 @@ func (s *NetworkSpec) GetLayer3() *Layer3Config {
 func (s *NetworkSpec) GetLayer2() *Layer2Config {
 	return s.Layer2
 }
+
+func (s *NetworkSpec) GetLocalnet() *LocalnetConfig {
+	return s.Localnet
+}
diff --git a/go-controller/pkg/crd/userdefinednetwork/v1/udn.go b/go-controller/pkg/crd/userdefinednetwork/v1/udn.go
index c5f77f199c7..241e6fac878 100644
--- a/go-controller/pkg/crd/userdefinednetwork/v1/udn.go
+++ b/go-controller/pkg/crd/userdefinednetwork/v1/udn.go
@@ -32,6 +32,7 @@ type UserDefinedNetworkSpec struct {
 	// Layer3 topology creates a layer 2 segment per node, each with a different subnet. Layer 3 routing is used to interconnect node subnets.
 	// Layer2 topology creates one logical switch shared by all nodes.
 	//
+	// +kubebuilder:validation:Enum=Layer2;Layer3
 	// +kubebuilder:validation:Required
 	// +required
 	// +unionDiscriminator
diff --git a/go-controller/pkg/crd/userdefinednetwork/v1/zz_generated.deepcopy.go b/go-controller/pkg/crd/userdefinednetwork/v1/zz_generated.deepcopy.go
index 355bc6aaee3..2ec5e6b77bb 100644
--- a/go-controller/pkg/crd/userdefinednetwork/v1/zz_generated.deepcopy.go
+++ b/go-controller/pkg/crd/userdefinednetwork/v1/zz_generated.deepcopy.go
@@ -25,6 +25,22 @@ import (
 	runtime "k8s.io/apimachinery/pkg/runtime"
 )
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AccessVLANConfig) DeepCopyInto(out *AccessVLANConfig) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessVLANConfig.
+func (in *AccessVLANConfig) DeepCopy() *AccessVLANConfig {
+	if in == nil {
+		return nil
+	}
+	out := new(AccessVLANConfig)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ClusterUserDefinedNetwork) DeepCopyInto(out *ClusterUserDefinedNetwork) {
 	*out = *in
@@ -236,6 +252,42 @@ func (in *Layer3Subnet) DeepCopy() *Layer3Subnet {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *LocalnetConfig) DeepCopyInto(out *LocalnetConfig) {
+	*out = *in
+	if in.Subnets != nil {
+		in, out := &in.Subnets, &out.Subnets
+		*out = make(DualStackCIDRs, len(*in))
+		copy(*out, *in)
+	}
+	if in.ExcludeSubnets != nil {
+		in, out := &in.ExcludeSubnets, &out.ExcludeSubnets
+		*out = make([]CIDR, len(*in))
+		copy(*out, *in)
+	}
+	if in.IPAM != nil {
+		in, out := &in.IPAM, &out.IPAM
+		*out = new(IPAMConfig)
+		**out = **in
+	}
+	if in.VLAN != nil {
+		in, out := &in.VLAN, &out.VLAN
+		*out = new(VLANConfig)
+		(*in).DeepCopyInto(*out)
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalnetConfig.
+func (in *LocalnetConfig) DeepCopy() *LocalnetConfig {
+	if in == nil {
+		return nil
+	}
+	out := new(LocalnetConfig)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *NetworkSpec) DeepCopyInto(out *NetworkSpec) {
 	*out = *in
@@ -249,6 +301,11 @@ func (in *NetworkSpec) DeepCopyInto(out *NetworkSpec) {
 		*out = new(Layer2Config)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.Localnet != nil {
+		in, out := &in.Localnet, &out.Localnet
+		*out = new(LocalnetConfig)
+		(*in).DeepCopyInto(*out)
+	}
 	return
 }
 
@@ -371,3 +428,24 @@ func (in *UserDefinedNetworkStatus) DeepCopy() *UserDefinedNetworkStatus {
 	in.DeepCopyInto(out)
 	return out
 }
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *VLANConfig) DeepCopyInto(out *VLANConfig) {
+	*out = *in
+	if in.Access != nil {
+		in, out := &in.Access, &out.Access
+		*out = new(AccessVLANConfig)
+		**out = **in
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VLANConfig.
+func (in *VLANConfig) DeepCopy() *VLANConfig {
+	if in == nil {
+		return nil
+	}
+	out := new(VLANConfig)
+	in.DeepCopyInto(out)
+	return out
+}
diff --git a/test/e2e/network_segmentation_api_validations.go b/test/e2e/network_segmentation_api_validations.go
new file mode 100644
index 00000000000..0608485b3d1
--- /dev/null
+++ b/test/e2e/network_segmentation_api_validations.go
@@ -0,0 +1,62 @@
+package e2e
+
+import (
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
+
+	"github.com/ovn-org/ovn-kubernetes/test/e2e/testdata"
+	testdatacudn "github.com/ovn-org/ovn-kubernetes/test/e2e/testdata/cudn"
+)
+
+var _ = Describe("Network Segmentation: API validations", func() {
+	DescribeTable("api-server should reject invalid CRs",
+		func(scenarios []testdata.ValidateCRScenario) {
+			DeferCleanup(func() {
+				cleanupValidateCRsTest(scenarios)
+			})
+			for _, s := range scenarios {
+				By(s.Description)
+				_, stderr, err := runKubectlInputWithFullOutput("", s.Manifest, "create", "-f", "-")
+				Expect(err).To(HaveOccurred(), "should fail to create invalid CR")
+				Expect(stderr).To(ContainSubstring(s.ExpectedErr))
+			}
+		},
+		Entry("ClusterUserDefinedNetwork, mismatch topology and config", testdatacudn.MismatchTopologyConfig),
+		Entry("ClusterUserDefinedNetwork, localnet, invalid role", testdatacudn.LocalnetInvalidRole),
+		Entry("ClusterUserDefinedNetwork, localnet, invalid physicalNetworkName", testdatacudn.LocalnetInvalidPhyNetName),
+		Entry("ClusterUserDefinedNetwork, localnet, invalid subnets", testdatacudn.LocalnetInvalidSubnets),
+		Entry("ClusterUserDefinedNetwork, localnet, invalid mtu", testdatacudn.LocalnetInvalidMTU),
+		Entry("ClusterUserDefinedNetwork, localnet, invalid vlan", testdatacudn.LocalnetInvalidVLAN),
+	)
+
+	DescribeTable("api-server should accept valid CRs",
+		func(scenarios []testdata.ValidateCRScenario) {
+			DeferCleanup(func() {
+				cleanupValidateCRsTest(scenarios)
+			})
+			for _, s := range scenarios {
+				By(s.Description)
+				_, err := e2ekubectl.RunKubectlInput("", s.Manifest, "apply", "-f", "-")
+				Expect(err).NotTo(HaveOccurred(), "should create valid CR successfully")
+			}
+		},
+		Entry("ClusterUserDefinedNetwork, localnet", testdatacudn.LocalnetValid),
+	)
+})
+
+// runKubectlInputWithFullOutput is a convenience wrapper over kubectlBuilder that takes input to stdin
+// It will also return the command's stderr.
+func runKubectlInputWithFullOutput(namespace string, data string, args ...string) (string, string, error) {
+	return e2ekubectl.NewKubectlCommand(namespace, args...).WithStdinData(data).ExecWithFullOutput()
+}
+
+func cleanupValidateCRsTest(scenarios []testdata.ValidateCRScenario) {
+	for _, s := range scenarios {
+		e2ekubectl.RunKubectlInput("", s.Manifest, "delete", "-f", "-")
+	}
+	_, stderr, err := e2ekubectl.RunKubectlWithFullOutput("", "get", "clusteruserdefinednetworks")
+	Expect(err).NotTo(HaveOccurred())
+	Expect(stderr).To(Equal("No resources found\n"))
+}
diff --git a/test/e2e/network_segmentation_localnet.go b/test/e2e/network_segmentation_localnet.go
new file mode 100644
index 00000000000..ce67c08b26c
--- /dev/null
+++ b/test/e2e/network_segmentation_localnet.go
@@ -0,0 +1,168 @@
+package e2e
+
+import (
+	"context"
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/rand"
+
+	nadapi "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
+
+	e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
+	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
+)
+
+var _ = Describe("Network Segmentation: Localnet", func() {
+	f := wrappedTestFramework("network-segmentation-localnet")
+	f.SkipNamespaceCreation = true
+
+	It("using ClusterUserDefinedNetwork CR, pods in different namespaces, should communicate over localnet topology", func() {
+		const (
+			vlan               = 200
+			testPort           = 9000
+			subnetIPv4         = "192.168.100.0/24"
+			subnetIPv6         = "2001:dbb::/64"
+			excludeSubnetIPv4  = "192.168.100.0/29"
+			excludeSubnetIPv6  = "2001:dbb::/120"
+			secondaryIfaceName = "eth1"
+			ovsBrName          = "ovsbr-eth1"
+		)
+		// use unique names to avoid conflicts with tests running in parallel
+		nsBlue := uniqueMetaName("blue")
+		nsRed := uniqueMetaName("red")
+		cudnName := uniqueMetaName("localnet-test")
+		physicalNetworkName := uniqueMetaName("localnet1")
+
+		By("setup the localnet underlay")
+		ovsPods := ovsPods(f.ClientSet)
+		Expect(ovsPods).NotTo(BeEmpty())
+		DeferCleanup(func() {
+			By("teardown the localnet underlay")
+			Expect(teardownUnderlay(ovsPods, ovsBrName)).To(Succeed())
+		})
+		c := networkAttachmentConfig{networkAttachmentConfigParams: networkAttachmentConfigParams{networkName: physicalNetworkName, vlanID: vlan}}
+		Expect(setupUnderlay(ovsPods, ovsBrName, secondaryIfaceName, c)).To(Succeed())
+
+		By("create test namespaces")
+		_, err := f.ClientSet.CoreV1().Namespaces().Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: nsRed}}, metav1.CreateOptions{})
+		Expect(err).NotTo(HaveOccurred())
+		_, err = f.ClientSet.CoreV1().Namespaces().Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: nsBlue}}, metav1.CreateOptions{})
+		Expect(err).NotTo(HaveOccurred())
+		DeferCleanup(func() {
+			By("cleanup test namespaces")
+			Expect(f.ClientSet.CoreV1().Namespaces().Delete(context.Background(), nsBlue, metav1.DeleteOptions{})).To(Succeed())
+			Expect(f.ClientSet.CoreV1().Namespaces().Delete(context.Background(), nsRed, metav1.DeleteOptions{})).To(Succeed())
+		})
+
+		By("create CR selecting the test namespaces")
+		netConf := networkAttachmentConfigParams{
+			name:                cudnName,
+			physicalNetworkName: physicalNetworkName,
+			vlanID:              vlan,
+			cidr:                correctCIDRFamily(subnetIPv4, subnetIPv6),
+			excludeCIDRs:        selectCIDRs(excludeSubnetIPv4, excludeSubnetIPv6),
+		}
+		cudnYAML := newLocalnetCUDNYaml(netConf, nsBlue, nsRed)
+		cleanup, err := createManifest("", cudnYAML)
+		Expect(err).NotTo(HaveOccurred())
+		DeferCleanup(func() {
+			By("cleanup CUDN CR")
+			cleanup()
+			By(fmt.Sprintf("delete pods in namespace %q to unblock CUDN CR & associate NAD deletion", nsBlue))
+			Expect(f.ClientSet.CoreV1().Pods(nsBlue).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})).To(Succeed())
+			By(fmt.Sprintf("delete pods in namespace %q to unblock CUDN CR & associate NAD deletion", nsRed))
+			Expect(f.ClientSet.CoreV1().Pods(nsRed).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{})).To(Succeed())
+			_, err := e2ekubectl.RunKubectl("", "delete", "clusteruserdefinednetwork", cudnName, "--wait", fmt.Sprintf("--timeout=%ds", 120))
+			Expect(err).NotTo(HaveOccurred())
+		})
+		Eventually(clusterUserDefinedNetworkReadyFunc(f.DynamicClient, cudnName)).WithTimeout(5*time.Second).WithPolling(time.Second).
+			Should(Succeed(), "CUDN CR is not ready")
+
+		By("create test pods")
+		serverPodCfg := podConfiguration{
+			name:         "test-server",
+			namespace:    nsBlue,
+			attachments:  []nadapi.NetworkSelectionElement{{Name: cudnName}},
+			containerCmd: httpServerContainerCmd(testPort),
+		}
+		clientPodCfg := podConfiguration{
+			name:        "test-client",
+			namespace:   nsRed,
+			attachments: []nadapi.NetworkSelectionElement{{Name: cudnName}},
+		}
+		serverPod, err := f.ClientSet.CoreV1().Pods(serverPodCfg.namespace).Create(context.Background(), generatePodSpec(serverPodCfg), metav1.CreateOptions{})
+		Expect(err).NotTo(HaveOccurred())
+		clientPod, err := f.ClientSet.CoreV1().Pods(clientPodCfg.namespace).Create(context.Background(), generatePodSpec(clientPodCfg), metav1.CreateOptions{})
+		Expect(err).NotTo(HaveOccurred())
+		Expect(e2epod.WaitForPodNameRunningInNamespace(context.Background(), f.ClientSet, clientPod.Name, clientPod.Namespace)).To(Succeed())
+		Expect(e2epod.WaitForPodNameRunningInNamespace(context.Background(), f.ClientSet, serverPod.Name, serverPod.Namespace)).To(Succeed())
+
+		By("assert pods interface's MTU is set with default MTU (1500)")
+		for _, cfg := range []podConfiguration{serverPodCfg, clientPodCfg} {
+			mtuRAW, err := e2ekubectl.RunKubectl(cfg.namespace, "exec", cfg.name, "--", "cat", "/sys/class/net/net1/mtu")
+			Expect(err).NotTo(HaveOccurred())
+			Expect(mtuRAW).To(ContainSubstring("1500"))
+		}
+
+		By("assert pods IPs not in exclude range")
+		serverIPs, err := podIPsForAttachment(f.ClientSet, serverPodCfg.namespace, serverPodCfg.name, cudnName)
+		Expect(err).NotTo(HaveOccurred())
+		clientIPs, err := podIPsForAttachment(f.ClientSet, clientPodCfg.namespace, clientPodCfg.name, cudnName)
+		Expect(err).NotTo(HaveOccurred())
+		podIPs := append(serverIPs, clientIPs...)
+		for _, excludedRange := range netConf.excludeCIDRs {
+			for _, podIP := range podIPs {
+				Expect(inRange(excludedRange, podIP)).To(
+					MatchError(fmt.Errorf("ip [%s] is NOT in range %s", podIP, excludedRange)))
+			}
+		}
+
+		for _, serverIP := range serverIPs {
+			By(fmt.Sprintf("asserting the *client* pod can contact the server pod exposed endpoint [%s:%d]", serverIP, testPort))
+			Eventually(func() error {
+				return reachServerPodFromClient(f.ClientSet, serverPodCfg, clientPodCfg, serverIP, testPort)
+			}).WithTimeout(2 * time.Minute).WithPolling(6 * time.Second).Should(Succeed())
+		}
+	})
+})
+
+func newLocalnetCUDNYaml(params networkAttachmentConfigParams, selectedNamespaces ...string) string {
+	selectedNs := strings.Join(selectedNamespaces, ",")
+	excludeSubnets := strings.Join(params.excludeCIDRs, ",")
+	return `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: ` + params.name + `
+spec:
+  namespaceSelector:
+    matchExpressions:
+    - key: kubernetes.io/metadata.name
+      operator: In
+      values: [ ` + selectedNs + `]
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: ` + params.physicalNetworkName + `
+      subnets: [` + params.cidr + `]
+      excludeSubnets: [` + excludeSubnets + `]
+      vlan:
+        mode: Access
+        access: {id: ` + strconv.Itoa(params.vlanID) + `}
+`
+}
+
+// uniqueMetaName generate unique name from given string that complies with metadata object name.
+func uniqueMetaName(originalName string) string {
+	const randomStringLength = 5
+	return fmt.Sprintf("%s-%s", originalName, rand.String(randomStringLength))
+}
diff --git a/test/e2e/testdata/cudn/invalid-scenarios-localnet-mtu.go b/test/e2e/testdata/cudn/invalid-scenarios-localnet-mtu.go
new file mode 100644
index 00000000000..e1ce9e8c703
--- /dev/null
+++ b/test/e2e/testdata/cudn/invalid-scenarios-localnet-mtu.go
@@ -0,0 +1,82 @@
+package cudn
+
+import "github.com/ovn-org/ovn-kubernetes/test/e2e/testdata"
+
+var LocalnetInvalidMTU = []testdata.ValidateCRScenario{
+	{
+		Description: "invalid MTU - higher than 65536",
+		ExpectedErr: `spec.network.localnet.mtu in body should be less than or equal to 65536`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-mtu-exceed-max-ipam-disabled-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      mtu: 65537
+      ipam: {mode: Disabled}
+`,
+	},
+	{
+		Description: "invalid MTU - lower than 576",
+		ExpectedErr: `spec.network.localnet.mtu in body should be greater than or equal to 576`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-ipv4-mtu-below-min-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      subnets: [10.0.0.0/24]
+      mtu: 575
+`,
+	},
+	{
+		Description: "invalid MTU - when IPv6 subnet is set, should be at least 1280",
+		ExpectedErr: `MTU should be greater than or equal to 1280 when an IPv6 subnet is used`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-ipv6-mtu-below-min-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      subnets: [2001:dbb::/64]
+      mtu: 1279
+`,
+	},
+	{
+		Description: "invalid MTU - when dualstack subnet is set, should be at least 1280",
+		ExpectedErr: `MTU should be greater than or equal to 1280 when an IPv6 subnet is used`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-dualstack-mtu-below-min-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      subnets: [192.168.0.0/16, 2001:dbb::/64]
+      mtu: 1279
+`,
+	},
+}
diff --git a/test/e2e/testdata/cudn/invalid-scenarios-localnet-phynetname.go b/test/e2e/testdata/cudn/invalid-scenarios-localnet-phynetname.go
new file mode 100644
index 00000000000..83c6664804c
--- /dev/null
+++ b/test/e2e/testdata/cudn/invalid-scenarios-localnet-phynetname.go
@@ -0,0 +1,161 @@
+package cudn
+
+import "github.com/ovn-org/ovn-kubernetes/test/e2e/testdata"
+
+var LocalnetInvalidPhyNetName = []testdata.ValidateCRScenario{
+	{
+		Description: "unset PhysicalNetworkName",
+		ExpectedErr: `spec.network.localnet.physicalNetworkName: Required value`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-phynetname-unset-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      subnets: [10.0.0.0/24]`,
+	},
+	{
+		Description: "invalid PhysicalNetworkName - empty string",
+		ExpectedErr: `spec.network.localnet.physicalNetworkName in body should be at least 1 chars long`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-invalid-phynetname-empty-string-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: ""
+      subnets: [10.0.0.0/24]`,
+	},
+	{
+		Description: "invalid PhysicalNetworkName - too long",
+		ExpectedErr: `spec.network.localnet.physicalNetworkName: Too long: may not be more than 253 bytes`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-invalid-phynetname-exceed-253-chars-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: "ycx7b6dhkhytzva3wrma0cu6mjhqpo2ty20cmpdg9ptvmt1mo9dfnrs56nr0bvg6z6zha5y208js6e2iwk6xb97sp2sojg48lu9d5vxbzzq40rwj1wchae3ju3dpj6qfsjbzjzmc0k489bloe49z4857kds43rqpeca3p5z2dfz562qu59qqb8qa3vo6pmwuaume581dqhlsz57yvbvgu5hmmmzremac7w7l4rmuirkk91767llw0vskanlc33"
+      subnets: [10.0.0.0/24]`,
+	},
+	{
+		Description: "invalid PhysicalNetworkName - contain `:` chars",
+		ExpectedErr: "physicalNetworkName cannot contain `,` or `:` characters",
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-invalid-phynetname-has-colon-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: "t:est"
+      subnets: [10.0.0.0/24]`,
+	},
+	{
+		Description: "invalid PhysicalNetworkName - contain `,` chars",
+		ExpectedErr: "physicalNetworkName cannot contain `,` or `:` characters",
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-invalid-phynetname-has-comma-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: "tes,t"
+      subnets: [10.0.0.0/24]
+`,
+	},
+	{
+		Description: "invalid PhysicalNetworkName - start with `:` char",
+		ExpectedErr: "physicalNetworkName cannot contain `,` or `:` characters",
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-invalid-phynetname-start-with-colon-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: ":test"
+      subnets: [10.0.0.0/24]
+`,
+	},
+	{
+		Description: "invalid PhysicalNetworkName - start with `,` char",
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-invalid-phynetname-start-with-comma-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: ",test"
+      subnets: [10.0.0.0/24]
+`,
+		ExpectedErr: "physicalNetworkName cannot contain `,` or `:` characters",
+	},
+	{
+		Description: "invalid PhysicalNetworkName - ends with `:` char",
+		ExpectedErr: "physicalNetworkName cannot contain `,` or `:` characters",
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-invalid-phynetname-end-with-colon-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: "test:"
+      subnets: [10.0.0.0/24]`,
+	},
+	{
+		Description: "invalid PhysicalNetworkName - ends with `,` char",
+		ExpectedErr: "physicalNetworkName cannot contain `,` or `:` characters",
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-invalid-phynetname-end-with-comma-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: "test,"
+      subnets: [10.0.0.0/24]`,
+	},
+}
diff --git a/test/e2e/testdata/cudn/invalid-scenarios-localnet-role.go b/test/e2e/testdata/cudn/invalid-scenarios-localnet-role.go
new file mode 100644
index 00000000000..fad452da04c
--- /dev/null
+++ b/test/e2e/testdata/cudn/invalid-scenarios-localnet-role.go
@@ -0,0 +1,40 @@
+package cudn
+
+import "github.com/ovn-org/ovn-kubernetes/test/e2e/testdata"
+
+var LocalnetInvalidRole = []testdata.ValidateCRScenario{
+	{
+		Description: "role unset",
+		ExpectedErr: `spec.network.localnet.role: Required value`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-role-unset-should-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      physicalNetworkName: test
+      subnets: [10.0.0.0/24]`,
+	},
+	{
+		Description: "role is primary",
+		ExpectedErr: `spec.network.localnet.role: Unsupported value: "Primary": supported values: "Secondary"`,
+		Manifest: `
+---
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-role-primary-should-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Primary
+      physicalNetworkName: test
+      subnets: [10.0.0.0/24]`,
+	},
+}
diff --git a/test/e2e/testdata/cudn/invalid-scenarios-localnet-subnets.go b/test/e2e/testdata/cudn/invalid-scenarios-localnet-subnets.go
new file mode 100644
index 00000000000..d62a216d48f
--- /dev/null
+++ b/test/e2e/testdata/cudn/invalid-scenarios-localnet-subnets.go
@@ -0,0 +1,282 @@
+package cudn
+
+import "github.com/ovn-org/ovn-kubernetes/test/e2e/testdata"
+
+var LocalnetInvalidSubnets = []testdata.ValidateCRScenario{
+	{
+		Description: "unset subnets, and ipam.mode is unset",
+		ExpectedErr: `Subnets is required with ipam.mode is Enabled or unset, and forbidden otherwise`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-subnets-and-ipam-disabled-unset-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+`,
+	},
+	{
+		Description: "ipam.mode is Enabled but subnets is unset",
+		ExpectedErr: `Subnets is required with ipam.mode is Enabled or unset, and forbidden otherwise`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-ipam-mode-disabled-ipam-lifecycle-persistent-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      ipam:
+        mode: Enabled
+`,
+	},
+	{
+		Description: "subnets is set but ipam.mode is Disabled",
+		ExpectedErr: `Subnets is required with ipam.mode is Enabled or unset, and forbidden otherwise`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-subnet-and-ipam-disabled-set-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: "asd"
+      subnets: [10.0.0.0/24]
+      ipam: {mode: Disabled}
+`,
+	},
+	{
+		Description: "subnets is empty",
+		ExpectedErr: `The ClusterUserDefinedNetwork "localnet-empty-subnets-fail" is invalid: spec.network.localnet.subnets: Invalid value: 0: spec.network.localnet.subnets in body should have at least 1 items`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-empty-subnets-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      subnets: []
+`,
+	},
+	{
+		Description: "excludeSubnets is set, subnets is unset",
+		ExpectedErr: `excludeSubnets must be unset when subnets is unset`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-subnets-unset-and-excludesubnets-set-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      excludeSubnets: [10.0.0.1/24]
+`,
+	},
+	{
+		Description: "excludeSubnets is set, ipam.mode is Disabled",
+		ExpectedErr: `excludeSubnets must be unset when subnets is unset`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-ipam-disabled-and-excludesubnets-set-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      excludeSubnets: [10.0.0.1/24]
+      ipam: {mode: Disabled}
+`,
+	},
+	{
+		Description: "subnets & excludeSubnets are set, but ipam.mode is Disabled",
+		ExpectedErr: "Subnets is required with ipam.mode is Enabled or unset, and forbidden otherwise",
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-ipam-disabled-subnetes-excludesubnetes-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: "asd"
+      subnets: [10.0.0.0/24]
+      excludeSubnets: [10.0.0.1/32]
+      ipam: {mode: Disabled}
+`,
+	},
+	{
+		Description: "invalid subnets - invalid IPv4 CIDR",
+		ExpectedErr: `The ClusterUserDefinedNetwork "localnet-subnets-invalid-ipv4-cidr-fail" is invalid: spec.network.localnet.subnets[0]: Invalid value: "string": CIDR is invalid`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-subnets-invalid-ipv4-cidr-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      subnets: [300.0.0.0/24]`,
+	},
+	{
+		Description: "invalid subnets - invalid IPv6 CIDR",
+		ExpectedErr: `The ClusterUserDefinedNetwork "localnet-subnets-invalid-ipv6-cidr-fail" is invalid: spec.network.localnet.subnets[0]: Invalid value: "string": CIDR is invalid`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-subnets-invalid-ipv6-cidr-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      subnets: [2014:100:200::0/300]`,
+	},
+	{
+		Description: "invalid subnets - two IPv4 CIDRs",
+		ExpectedErr: `The ClusterUserDefinedNetwork "localnet-subnets-multiple-ipv4-cidrs-fail" is invalid: spec.network.localnet.subnets: Invalid value: "array": When 2 CIDRs are set, they must be from different IP families`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-subnets-multiple-ipv4-cidrs-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      subnets: [10.10.0.0/24, 10.20.0.0/24]
+`,
+	},
+	{
+		Description: "invalid subnets - two IPv6 CIDRs",
+		ExpectedErr: `The ClusterUserDefinedNetwork "localnet-subnets-multiple-ipv6-cidrs-fail" is invalid: spec.network.localnet.subnets: Invalid value: "array": When 2 CIDRs are set, they must be from different IP families`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-subnets-multiple-ipv6-cidrs-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      subnets: [2001:aaa::/64, 2001:bbb::/64]
+`,
+	},
+	{
+		Description: "invalid excludeSubnets - empty slice",
+		ExpectedErr: `The ClusterUserDefinedNetwork "localnet-excludesubnet-empty-slice-fail" is invalid: spec.network.localnet.excludeSubnets: Invalid value: 0: spec.network.localnet.excludeSubnets in body should have at least 1 items`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-excludesubnet-empty-slice-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      subnets: [10.0.0.0/24]
+      excludeSubnets: []`,
+	},
+	{
+		Description: "invalid excludeSubnets - invalid IPv4 CIDR",
+		ExpectedErr: `The ClusterUserDefinedNetwork "localnet-excludesubnet-invalid-ipv4-cidr-fail" is invalid: spec.network.localnet.excludeSubnets[0]: Invalid value: "string": CIDR is invalid`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-excludesubnet-invalid-ipv4-cidr-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      subnets: [10.0.0.0/24]
+      excludeSubnets: [10.0.0.0/300]
+`,
+	},
+	{
+		Description: "invalid excludeSubnets - invalid IPv6 CIDR",
+		ExpectedErr: `The ClusterUserDefinedNetwork "localnet-excludesubnet-invalid-ipv6-cidr-fail" is invalid: spec.network.localnet.excludeSubnets[0]: Invalid value: "string": CIDR is invalid`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-excludesubnet-invalid-ipv6-cidr-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      subnets: [10.0.0.0/24]
+      excludeSubnets: [2014:100:200::0/300]
+`,
+	},
+	{
+		Description: "ipam.lifecycle is Persistent but ipam.mode is Disabled",
+		ExpectedErr: `lifecycle Persistent is only supported when ipam.mode is Enabled`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-ipam-mode-disabled-ipam-lifecycle-persistent-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      ipam:
+        mode: Disabled
+        lifecycle: Persistent
+`,
+	},
+}
diff --git a/test/e2e/testdata/cudn/invalid-scenarios-localnet-vlan.go b/test/e2e/testdata/cudn/invalid-scenarios-localnet-vlan.go
new file mode 100644
index 00000000000..daa393acdb9
--- /dev/null
+++ b/test/e2e/testdata/cudn/invalid-scenarios-localnet-vlan.go
@@ -0,0 +1,109 @@
+package cudn
+
+import "github.com/ovn-org/ovn-kubernetes/test/e2e/testdata"
+
+var LocalnetInvalidVLAN = []testdata.ValidateCRScenario{
+	{
+		Description: "invalid VLAN - invalid mode",
+		ExpectedErr: `spec.network.localnet.vlan.mode: Unsupported value: "Disabled": supported values: "Access`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-vlan-invalid-mode-fail
+spec:
+  namespaceSelector: {matchLabels: { kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      subnets: [192.168.0.0/16]
+      vlan:
+        mode: Disabled
+`,
+	},
+	{
+		Description: "invalid VLAN -mode is 'Access' but vlan access config is unset",
+		ExpectedErr: `vlan access config is required when vlan mode is 'Access', and forbidden otherwise`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-vlan--no-access-config-fail
+spec:
+  namespaceSelector: {matchLabels: { kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      subnets: [192.168.0.0/16]
+      vlan:
+        mode: Access
+`,
+	},
+	{
+		Description: "invalid VLAN - vlan access config is unset",
+		ExpectedErr: `spec.network.localnet.vlan.access.id: Required value`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-vlan-access-config-unset-fail
+spec:
+  namespaceSelector: {matchLabels: { kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      subnets: [192.168.0.0/16]
+      vlan:
+        mode: Access
+        access: {} 
+`,
+	},
+	{
+		Description: "invalid VLAN - vlan access id is 0",
+		ExpectedErr: `spec.network.localnet.vlan.access.id in body should be greater than or equal to 1`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-vlan-id-lower-then-2-fail
+spec:
+  namespaceSelector: {matchLabels: { kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      subnets: [192.168.0.0/16]
+      vlan:
+        mode: Access
+        access: {id: 0} 
+`,
+	},
+	{
+		Description: "invalid VLAN - vlan access id is 4095",
+		ExpectedErr: `spec.network.localnet.vlan.access.id in body should be less than or equal to 4094`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-vlan-id-higher-then-4094-fail
+spec:
+  namespaceSelector: {matchLabels: { kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      subnets: [192.168.0.0/16]
+      vlan:
+        mode: Access
+        access: {id: 4095} 
+`,
+	},
+}
diff --git a/test/e2e/testdata/cudn/invalid-scenarios-mismatch-topo-conf.go b/test/e2e/testdata/cudn/invalid-scenarios-mismatch-topo-conf.go
new file mode 100644
index 00000000000..80551a94cd2
--- /dev/null
+++ b/test/e2e/testdata/cudn/invalid-scenarios-mismatch-topo-conf.go
@@ -0,0 +1,96 @@
+package cudn
+
+import "github.com/ovn-org/ovn-kubernetes/test/e2e/testdata"
+
+var MismatchTopologyConfig = []testdata.ValidateCRScenario{
+	{
+		Description: "topology is localnet but topology config is layer2",
+		ExpectedErr: `spec.localnet is required when topology is Localnet and forbidden otherwise`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: topology-localnet-mismatch-topology-config-layer2-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    layer2:
+`,
+	},
+	{
+		Description: "topology is localnet but topology config is layer3",
+		ExpectedErr: `spec.localnet is required when topology is Localnet and forbidden otherwise`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: topology-localnet-mismatch-topology-config-layer3-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Localnet
+    layer3:
+`,
+	},
+	{
+		Description: "topology is layer2 but topology config is localnet",
+		ExpectedErr: `spec.layer2 is required when topology is Layer2 and forbidden otherwise`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: topology-layer2-mismatch-topology-config-localnet-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Layer2
+    localnet:
+`,
+	},
+	{
+		Description: "topology is layer2 but topology config is layer3",
+		ExpectedErr: `spec.layer2 is required when topology is Layer2 and forbidden otherwise`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: topology-layer2-mismatch-topology-config-layer3-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Layer2
+    layer3:
+`,
+	},
+	{
+		Description: "topology is layer3 but topology config is localnet",
+		ExpectedErr: `spec.layer3 is required when topology is Layer3 and forbidden otherwise`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: topology-layer3-mismatch-topology-config-localnet-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Layer3
+    localnet:
+`,
+	},
+	{
+		Description: "topology is layer3 but topology config is layer2",
+		ExpectedErr: `spec.layer3 is required when topology is Layer3 and forbidden otherwise`,
+		Manifest: `
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: topology-layer3-mismatch-topology-config-layer2-fail
+spec:
+  namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: red}}
+  network:
+    topology: Layer3
+    layer2:
+`,
+	},
+}
diff --git a/test/e2e/testdata/cudn/valid-scenarios-localnet.go b/test/e2e/testdata/cudn/valid-scenarios-localnet.go
new file mode 100644
index 00000000000..a5b188bbfd0
--- /dev/null
+++ b/test/e2e/testdata/cudn/valid-scenarios-localnet.go
@@ -0,0 +1,80 @@
+package cudn
+
+import "github.com/ovn-org/ovn-kubernetes/test/e2e/testdata"
+
+var LocalnetValid = []testdata.ValidateCRScenario{
+	{
+		Description: "should create localnet topology successfully - minimal",
+		Manifest: `
+---
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-minimal-success
+spec:
+  namespaceSelector:
+    matchLabels:
+      kubernetes.io/metadata.name: red
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      subnets: ["10.0.0.0/24"]
+`,
+	},
+	{
+		Description: "should create localnet topology successfully - ipam persistent",
+		Manifest: `
+---
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-ipam-lifecycle-persistent-success
+spec:
+  namespaceSelector:
+    matchExpressions:
+      - key: kubernetes.io/metadata.name
+        operator: In
+        values: ["red", "blue"]
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      ipam: {lifecycle: Persistent}
+      subnets: ["192.168.0.0/16", "2001:dbb::/64"]
+      excludeSubnets: ["192.168.0.1/32", "2001:dbb::1/128"]
+      vlan: 
+        mode: Access
+        access: {id: 3}
+      mtu: 9000
+`,
+	},
+	{
+		Description: "should create localnet topology successfully - ipam disabled",
+		Manifest: `
+---
+apiVersion: k8s.ovn.org/v1
+kind: ClusterUserDefinedNetwork
+metadata:
+  name: localnet-ipam-disabled-success
+spec:
+  namespaceSelector:
+    matchExpressions:
+      - key: kubernetes.io/metadata.name
+        operator: In
+        values: ["red", "blue"]
+  network:
+    topology: Localnet
+    localnet:
+      role: Secondary
+      physicalNetworkName: test
+      ipam: {mode: Disabled}
+      vlan: 
+        mode: Access
+        access: {id: 4094}
+      mtu: 9000
+`,
+	},
+}
diff --git a/test/e2e/testdata/scenario.go b/test/e2e/testdata/scenario.go
new file mode 100644
index 00000000000..db96d3b50b8
--- /dev/null
+++ b/test/e2e/testdata/scenario.go
@@ -0,0 +1,8 @@
+package testdata
+
+// ValidateCRScenario represent test scenario where a manifest is applied and failed with the expected error
+type ValidateCRScenario struct {
+	Description string
+	Manifest    string
+	ExpectedErr string
+}


Reviews:
user: login: npinaeva
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T13:52:27Z
user: login: ormergi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T00:09:29Z
user: login: npinaeva
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T16:57:34Z
user: login: ormergi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T18:47:41Z
user: login: npinaeva
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T15:37:13Z
user: login: ormergi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T15:49:55Z
user: login: npinaeva
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T14:40:43Z
user: login: ricky-rav
body: I've looked at the first commit only. Please apply the same suggested changes also to `go-controller/pkg/crd/userdefinednetwork/v1/cudn.go`. Thanks! :) 
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T11:28:35Z
user: login: ormergi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T13:27:46Z
user: login: ormergi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T13:34:30Z
user: login: ormergi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T13:43:54Z
user: login: ormergi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T13:44:31Z
user: login: ormergi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T13:53:46Z
user: login: ricky-rav
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T14:44:49Z
user: login: ricky-rav
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T14:46:34Z
user: login: ricky-rav
body: Reviewed until commit 7. One more commit to go!
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T17:43:02Z
user: login: ormergi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T07:28:46Z
user: login: ormergi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T07:31:36Z
user: login: ormergi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T07:45:02Z
user: login: ormergi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T07:49:37Z
user: login: ricky-rav
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T08:11:30Z
user: login: ricky-rav
body: I've gone through all commits. What's left is:
- maximum MTU size: shouldn't it be 65535?
- unit test testing invalid vlan config: it should check vlanID=0, as you did in the e2e tests, instead of vlanID=1 :) 


state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T10:01:50Z
user: login: ormergi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T13:03:03Z
user: login: ricky-rav
body: One last comment. And then let's see about the e2e tests on slack.
Thanks, Or!
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T17:02:43Z
user: login: ormergi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T07:46:40Z
user: login: ricky-rav
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T08:50:17Z
user: login: ricky-rav
body: /LGTM
Thanks @ormergi !
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T18:12:51Z
user: login: trozet
body: overall looks good
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T21:46:31Z
user: login: ormergi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T09:18:43Z
user: login: ormergi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T09:18:54Z
user: login: ormergi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T09:20:57Z
user: login: ormergi
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T09:48:50Z
user: login: trozet
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T12:58:41Z
user: login: trozet
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T19:19:09Z

Review comments:
diff_hunk: @@ -42,9 +43,10 @@ type ClusterUserDefinedNetworkSpec struct {
 type NetworkSpec struct {
 	// Topology describes network configuration.
 	//
-	// Allowed values are "Layer3", "Layer2".
+	// Allowed values are "Layer3", "Layer2" and "Localnet".
 	// Layer3 topology creates a layer 2 segment per node, each with a different subnet. Layer 3 routing is used to interconnect node subnets.
 	// Layer2 topology creates one logical switch shared by all nodes.
+	// Localnet topology attach to the overlay local network. Enables egress to the provider's physical network.
path: go-controller/pkg/crd/userdefinednetwork/v1/cudn.go
user: login: npinaeva
body: I think this one needs some more work, our current docs say
"A localnet topology is based on layer 2 topology, but also allows connecting to an existent (configured) physical network to provide north-south traffic to the workloads." which I think gives a bit more info. Maybe @maiqueb is the right person to review this part?
created_at: 2025-02-28T15:10:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,3 +83,101 @@ type ClusterUserDefinedNetworkList struct {
 	metav1.ListMeta `json:"metadata,omitempty"`
 	Items           []ClusterUserDefinedNetwork `json:"items"`
 }
+
+// +kubebuilder:validation:XValidation:rule="has(self.ipam) && has(self.ipam.mode) && self.ipam.mode != 'Enabled' || has(self.subnets)", message="subnets is required with ipam.mode is Enabled or unset"
+// +kubebuilder:validation:XValidation:rule="!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode != 'Disabled' || !has(self.subnets)", message="subnets must be unset when ipam.mode is Disabled"
+// +kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || has(self.excludeSubnets) && has(self.subnets)", message="excludeSubnets must be unset when subents is unset"
path: go-controller/pkg/crd/userdefinednetwork/v1/cudn.go
user: login: npinaeva
body: this can be simplified
`!has(self.excludeSubnets) || has(self.subnets)`
created_at: 2025-02-28T16:00:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -163,7 +163,7 @@ type LocalnetConfig struct {
 	IPAM *IPAMConfig `json:"ipam,omitempty"`
 
 	// mtu is the maximum transmission unit for a network.
-	// mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+	// mtu is optional. When omitted, the default MTU will be used for the network (1500).
path: go-controller/pkg/crd/userdefinednetwork/v1/cudn.go
user: login: npinaeva
body: I think this sounds like an API default, which (I think?) we want to avoid. Maybe better change the message to be close to the previous one, like
`mtu is optional, if not provided, the configured value in OVN-Kubernetes (defaults to 1500 for localnet topology) is used for the network.`
created_at: 2025-02-28T16:38:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1743,6 +1744,123 @@ var _ = Describe("Multi Homing", func() {
 			Expect(inRange(secondaryFlatL2NetworkCIDR, netStatus[1].IPs[0]))
 		})
 	})
+
+	It("using ClusterUserDefinedNetwork CR, pods in different namespaces & different nodes, should be able to communicate over Localnet topology", func() {
path: test/e2e/multihoming.go
user: login: npinaeva
body: I thought this test suite is only about NAD secondary networks?
And network_segmentation is what we use for CUDN tests
created_at: 2025-03-04T09:26:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,55 @@
+package e2e
+
+import (
+	"fmt"
+
+	"github.com/onsi/ginkgo/v2"
+	"github.com/onsi/gomega"
+
+	e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
+)
+
+var _ = ginkgo.Describe("Network Segmentation: API validations", func() {
+	ginkgo.Context("ClusterUserDefinedNetwork", func() {
+		ginkgo.DescribeTable("api-server should block invalid CRs",
+			func(invalidManifestPath string) {
+				ginkgo.DeferCleanup(func() {
+					gomega.Expect(cleanupCRsFromManifest(invalidManifestPath)).To(gomega.Succeed())
+				})
+
+				_, stderr, err := e2ekubectl.RunKubectlWithFullOutput("", "create", "-f", invalidManifestPath)
+				gomega.Expect(err).To(gomega.HaveOccurred(), "invalid CRs creation should fail")
path: test/e2e/network_segementation_api_validitions.go
user: login: npinaeva
body: I think it would be nice to check error messages too, to make sure they fail as we expect. Maybe you could represent current yamls as `map[yaml]: error substring` in the code or something like that?
created_at: 2025-03-04T09:37:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,3 +83,101 @@ type ClusterUserDefinedNetworkList struct {
 	metav1.ListMeta `json:"metadata,omitempty"`
 	Items           []ClusterUserDefinedNetwork `json:"items"`
 }
+
+// +kubebuilder:validation:XValidation:rule="has(self.ipam) && has(self.ipam.mode) && self.ipam.mode != 'Enabled' || has(self.subnets)", message="subnets is required with ipam.mode is Enabled or unset"
+// +kubebuilder:validation:XValidation:rule="!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode != 'Disabled' || !has(self.subnets)", message="subnets must be unset when ipam.mode is Disabled"
+// +kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || has(self.excludeSubnets) && has(self.subnets)", message="excludeSubnets must be unset when subents is unset"
path: go-controller/pkg/crd/userdefinednetwork/v1/cudn.go
user: login: ormergi
body: Done
created_at: 2025-03-04T23:05:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -42,9 +43,10 @@ type ClusterUserDefinedNetworkSpec struct {
 type NetworkSpec struct {
 	// Topology describes network configuration.
 	//
-	// Allowed values are "Layer3", "Layer2".
+	// Allowed values are "Layer3", "Layer2" and "Localnet".
 	// Layer3 topology creates a layer 2 segment per node, each with a different subnet. Layer 3 routing is used to interconnect node subnets.
 	// Layer2 topology creates one logical switch shared by all nodes.
+	// Localnet topology attach to the overlay local network. Enables egress to the provider's physical network.
path: go-controller/pkg/crd/userdefinednetwork/v1/cudn.go
user: login: ormergi
body: I changed it to the text you suggest.

Lets see if Miguel approves :smiley: 
created_at: 2025-03-04T23:21:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -163,7 +163,7 @@ type LocalnetConfig struct {
 	IPAM *IPAMConfig `json:"ipam,omitempty"`
 
 	// mtu is the maximum transmission unit for a network.
-	// mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+	// mtu is optional. When omitted, the default MTU will be used for the network (1500).
path: go-controller/pkg/crd/userdefinednetwork/v1/cudn.go
user: login: ormergi
body: Done
created_at: 2025-03-04T23:29:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1743,6 +1744,123 @@ var _ = Describe("Multi Homing", func() {
 			Expect(inRange(secondaryFlatL2NetworkCIDR, netStatus[1].IPs[0]))
 		})
 	})
+
+	It("using ClusterUserDefinedNetwork CR, pods in different namespaces & different nodes, should be able to communicate over Localnet topology", func() {
path: test/e2e/multihoming.go
user: login: ormergi
body: From what I understand from Miguel its about the multi-homing feature, currently it use only NADs.
Given the network-segmentation test suite is mostly about primary, we tought multi-homing test suite is good fit for testing CUDN/UDN for secondary networks.
WDYT?
created_at: 2025-03-04T23:36:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,55 @@
+package e2e
+
+import (
+	"fmt"
+
+	"github.com/onsi/ginkgo/v2"
+	"github.com/onsi/gomega"
+
+	e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
+)
+
+var _ = ginkgo.Describe("Network Segmentation: API validations", func() {
+	ginkgo.Context("ClusterUserDefinedNetwork", func() {
+		ginkgo.DescribeTable("api-server should block invalid CRs",
+			func(invalidManifestPath string) {
+				ginkgo.DeferCleanup(func() {
+					gomega.Expect(cleanupCRsFromManifest(invalidManifestPath)).To(gomega.Succeed())
+				})
+
+				_, stderr, err := e2ekubectl.RunKubectlWithFullOutput("", "create", "-f", invalidManifestPath)
+				gomega.Expect(err).To(gomega.HaveOccurred(), "invalid CRs creation should fail")
path: test/e2e/network_segementation_api_validitions.go
user: login: ormergi
body: Done

The YAML files content is moved to Go files, where it has a complex type holding the YAML and expected error.
Tests were adjusted accordingly, and now assert on the expected error.
created_at: 2025-03-04T23:37:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1743,6 +1744,123 @@ var _ = Describe("Multi Homing", func() {
 			Expect(inRange(secondaryFlatL2NetworkCIDR, netStatus[1].IPs[0]))
 		})
 	})
+
+	It("using ClusterUserDefinedNetwork CR, pods in different namespaces & different nodes, should be able to communicate over Localnet topology", func() {
path: test/e2e/multihoming.go
user: login: npinaeva
body: I think currently it work like this:
1. multihoming - all tests for NADs, e.g. we already have connectivity tests for localnet https://github.com/ovn-kubernetes/ovn-kubernetes/blob/master/test/e2e/multihoming.go#L605-L623, stuff that should already work
2. network_segmentation - all (C)UDN related functionality

Since in this PR you are just adding the CUDN part, I would expect maybe a copy of https://github.com/ovn-kubernetes/ovn-kubernetes/blob/ab2bbed40acccf88e56cf9bfa8ff1452acfa72fc/test/e2e/network_segmentation.go#L914 for localnet, or if you want to add an actual connectivity tests for secondary (C)UDNs, that makes sense to do for other topologies too, not just localnet, wdyt?
created_at: 2025-03-05T16:57:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1743,6 +1744,123 @@ var _ = Describe("Multi Homing", func() {
 			Expect(inRange(secondaryFlatL2NetworkCIDR, netStatus[1].IPs[0]))
 		})
 	})
+
+	It("using ClusterUserDefinedNetwork CR, pods in different namespaces & different nodes, should be able to communicate over Localnet topology", func() {
path: test/e2e/multihoming.go
user: login: ormergi
body: The [tests](https://github.com/ovn-kubernetes/ovn-kubernetes/blob/ab2bbed40acccf88e56cf9bfa8ff1452acfa72fc/test/e2e/network_segmentation.go#L914) verifies basic functionally of the CRD controller, I think they are agnostic to topology, we could pick other topology for them.
(FWIW originally the Context description didn't mention the topology being used).

From what I saw conn. tests in network_segmentation suite verifies primary networking, while mutlihoming verifies secondary networking.   
This is why I thought multihoming could be better fit for this tests, and  also for other tests that make use CUDN/UDN CRDs for creating secondary networks.
But you know better, so please let me know what you think, and where this test should go :smile: 

As as side note, the CRD controller functionality and localnet implementation already tested thoroughly, this change comes down to having another flavor for NAD, maybe unit-test could be sufficient for it.
created_at: 2025-03-05T18:47:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1743,6 +1744,123 @@ var _ = Describe("Multi Homing", func() {
 			Expect(inRange(secondaryFlatL2NetworkCIDR, netStatus[1].IPs[0]))
 		})
 	})
+
+	It("using ClusterUserDefinedNetwork CR, pods in different namespaces & different nodes, should be able to communicate over Localnet topology", func() {
path: test/e2e/multihoming.go
user: login: npinaeva
body: I think it is more intuitive to put all (C)UDN-related stuff into `network_segmentation` (then e.g. https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5010/commits/6f8fc5893f1a6dde6258fec575d51fd6bafda5d9 won't be needed)
for secondary (C)UDNs, it should be enough to make sure correct NADs are created (and then consider NAD functionality is covered by the multihoming test suite). So maybe we should have https://github.com/ovn-kubernetes/ovn-kubernetes/blob/ab2bbed40acccf88e56cf9bfa8ff1452acfa72fc/test/e2e/network_segmentation.go#L914 parametrized for all secondary network types?
created_at: 2025-03-11T15:37:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1743,6 +1744,123 @@ var _ = Describe("Multi Homing", func() {
 			Expect(inRange(secondaryFlatL2NetworkCIDR, netStatus[1].IPs[0]))
 		})
 	})
+
+	It("using ClusterUserDefinedNetwork CR, pods in different namespaces & different nodes, should be able to communicate over Localnet topology", func() {
path: test/e2e/multihoming.go
user: login: ormergi
body: > for secondary (C)UDNs, it should be enough to make sure correct NADs are created (and then consider NAD functionality is covered by the multihoming test suite). 

+1 for relaying  on multinetwork.go tests covering functionality.
Generating the NAD is covered by unit tests, do you think it should be tested e2e?

>  So maybe we should have parametrized for all secondary network types? 

These tests verify the controller logic that isnt relay or depend on the network topology type, I am not sure we should add more to them and parameterize them by topology. WDYT?
created_at: 2025-03-17T15:49:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1743,6 +1744,123 @@ var _ = Describe("Multi Homing", func() {
 			Expect(inRange(secondaryFlatL2NetworkCIDR, netStatus[1].IPs[0]))
 		})
 	})
+
+	It("using ClusterUserDefinedNetwork CR, pods in different namespaces & different nodes, should be able to communicate over Localnet topology", func() {
path: test/e2e/multihoming.go
user: login: npinaeva
body: yeah I double-checked what we have, and it looks like we don't really need new e2es for localnet :) 
created_at: 2025-03-19T14:40:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -243,6 +243,17 @@ func validateLocalnetConfig(cfg userdefinednetworkv1.LocalnetConfig) error {
 	return nil
 }
 
+func loclanetMTU(desiredMTU int32) int {
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go
user: login: ricky-rav
body: ```suggestion
func localnetMTU(desiredMTU int32) int {
```
created_at: 2025-04-04T17:06:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -162,7 +162,7 @@ func renderCNINetworkConfig(networkName, nadName string, spec SpecGetter) (map[s
 		}
 
 		netConfSpec.Role = strings.ToLower(string(cfg.Role))
-		netConfSpec.MTU = int(cfg.MTU)
+		netConfSpec.MTU = loclanetMTU(cfg.MTU)
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go
user: login: ricky-rav
body: `localnetMTU`
created_at: 2025-04-04T17:06:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                          excludeSubnets is optional. When omitted no IP address is excluded and all IP addresses specified in `subnets`
```
created_at: 2025-04-07T09:41:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                          are subject to assignment.
```
created_at: 2025-04-07T09:45:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                          When `physicalNetworkName` points to the OVS bridge mapping of a network with reserved IP addresses
```
created_at: 2025-04-07T09:51:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                          (which should not be assigned by OVN-Kubernetes), the specified CIDRs will not be assigned. For example:
```
created_at: 2025-04-07T09:52:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: are the new lines and tabs intentional here and below?
created_at: 2025-04-07T09:58:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                          configuration to the SDN infra and assign IPs from the selected\n
```
created_at: 2025-04-07T09:59:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                          only assigns MAC addresses, provides layer2 communication,
```
created_at: 2025-04-07T10:01:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                          and enables users to configure IP addresses on the pods.\n`ipam.lifecycle`
```
created_at: 2025-04-07T10:08:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                          to 'Persistent', the assigned IP addresses will be persisted
```
created_at: 2025-04-07T10:10:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                              The only allowed value is Persistent. When set, the IP addresses assigned by OVN-Kubernetes will be persisted in an
```
created_at: 2025-04-07T10:11:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                          In a scenario where `physicalNetworkName` points to the OVS bridge mapping of a network configured with certain MTU settings,
```
created_at: 2025-04-07T10:28:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                          this field enables configuring the same MTU on the pod interface, having the pod MTU aligned with the network MTU.
```
created_at: 2025-04-07T10:29:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: In the PR description you're saying that we're using 1500 as default and not the OVNK MTU of 1500:

> Since localnet topology is not part of the SDN infran the MTU is not required to be adjusted to compensate for the SDN overhead, and use the default MTU - 1500.
> In case MTU is not specified in the CUDN network.spec, the controller will generate the NAD setting MTU 1500 explicitly.


created_at: 2025-04-07T10:30:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: shouldn't it be `65535`?
created_at: 2025-04-07T10:34:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
+                        minimum: 576
+                        type: integer
+                      physicalNetworkName:
+                        description: |-
+                          physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+                          Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+                          In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+                          this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+                        maxLength: 253
+                        minLength: 1
+                        type: string
+                        x-kubernetes-validations:
+                        - message: physicalNetworkName cannot contain `,` or `:` characters
+                          rule: self.matches('^[^,:]+$')
+                      role:
+                        description: |-
+                          role describes the network role in the pod, required.
+                          Control whether the pod interface will act as primary or secondary.
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                          Controls whether the pod interface will act as primary or secondary.
```
created_at: 2025-04-07T10:37:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
+                        minimum: 576
+                        type: integer
+                      physicalNetworkName:
+                        description: |-
+                          physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+                          Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+                          In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+                          this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+                        maxLength: 253
+                        minLength: 1
+                        type: string
+                        x-kubernetes-validations:
+                        - message: physicalNetworkName cannot contain `,` or `:` characters
+                          rule: self.matches('^[^,:]+$')
+                      role:
+                        description: |-
+                          role describes the network role in the pod, required.
+                          Control whether the pod interface will act as primary or secondary.
+                          Localnet topology support `Secondary` only.
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                          Localnet topology supports `Secondary` only.
```
created_at: 2025-04-07T10:37:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
+                        minimum: 576
+                        type: integer
+                      physicalNetworkName:
+                        description: |-
+                          physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+                          Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+                          In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+                          this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+                        maxLength: 253
+                        minLength: 1
+                        type: string
+                        x-kubernetes-validations:
+                        - message: physicalNetworkName cannot contain `,` or `:` characters
+                          rule: self.matches('^[^,:]+$')
+                      role:
+                        description: |-
+                          role describes the network role in the pod, required.
+                          Control whether the pod interface will act as primary or secondary.
+                          Localnet topology support `Secondary` only.
+                          The network will be assigned to pods who has the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                          The network will be assigned to pods that have the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
```
created_at: 2025-04-07T10:39:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
+                        minimum: 576
+                        type: integer
+                      physicalNetworkName:
+                        description: |-
+                          physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+                          Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+                          In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+                          this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+                        maxLength: 253
+                        minLength: 1
+                        type: string
+                        x-kubernetes-validations:
+                        - message: physicalNetworkName cannot contain `,` or `:` characters
+                          rule: self.matches('^[^,:]+$')
+                      role:
+                        description: |-
+                          role describes the network role in the pod, required.
+                          Control whether the pod interface will act as primary or secondary.
+                          Localnet topology support `Secondary` only.
+                          The network will be assigned to pods who has the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+                          to subject.
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: Should this sentence be moved to the description of the `Localnet` subtree above instead of being under `role`? 
created_at: 2025-04-07T10:40:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
+                        minimum: 576
+                        type: integer
+                      physicalNetworkName:
+                        description: |-
+                          physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+                          Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+                          In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+                          this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+                        maxLength: 253
+                        minLength: 1
+                        type: string
+                        x-kubernetes-validations:
+                        - message: physicalNetworkName cannot contain `,` or `:` characters
+                          rule: self.matches('^[^,:]+$')
+                      role:
+                        description: |-
+                          role describes the network role in the pod, required.
+                          Control whether the pod interface will act as primary or secondary.
+                          Localnet topology support `Secondary` only.
+                          The network will be assigned to pods who has the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+                          to subject.
+                        enum:
+                        - Primary
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: Why are we allowing `Primary` for localnet if we don't implement localnets in primary networks?
created_at: 2025-04-07T10:42:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
+                        minimum: 576
+                        type: integer
+                      physicalNetworkName:
+                        description: |-
+                          physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+                          Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+                          In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+                          this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+                        maxLength: 253
+                        minLength: 1
+                        type: string
+                        x-kubernetes-validations:
+                        - message: physicalNetworkName cannot contain `,` or `:` characters
+                          rule: self.matches('^[^,:]+$')
+                      role:
+                        description: |-
+                          role describes the network role in the pod, required.
+                          Control whether the pod interface will act as primary or secondary.
+                          Localnet topology support `Secondary` only.
+                          The network will be assigned to pods who has the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+                          to subject.
+                        enum:
+                        - Primary
+                        - Secondary
+                        type: string
+                      subnets:
+                        description: |-
+                          subnets is a list of subnets used for the pod network across the cluster.
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                          subnets is a list of subnets used for pods in this localnet network across the cluster.
```
created_at: 2025-04-07T10:43:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
+                        minimum: 576
+                        type: integer
+                      physicalNetworkName:
+                        description: |-
+                          physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+                          Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+                          In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+                          this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+                        maxLength: 253
+                        minLength: 1
+                        type: string
+                        x-kubernetes-validations:
+                        - message: physicalNetworkName cannot contain `,` or `:` characters
+                          rule: self.matches('^[^,:]+$')
+                      role:
+                        description: |-
+                          role describes the network role in the pod, required.
+                          Control whether the pod interface will act as primary or secondary.
+                          Localnet topology support `Secondary` only.
+                          The network will be assigned to pods who has the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+                          to subject.
+                        enum:
+                        - Primary
+                        - Secondary
+                        type: string
+                      subnets:
+                        description: |-
+                          subnets is a list of subnets used for the pod network across the cluster.
+                          The list may be either 1 IPv4 subnet, 1 IPv6 subnet, or 1 of each IP family.
+                          When set, OVN-Kubernetes assign IP address of the specified CIDRs to the connected pod,
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                          When set, OVN-Kubernetes assigns an IP address from the specified CIDRs to the connected pod,
```
created_at: 2025-04-07T10:44:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
+                        minimum: 576
+                        type: integer
+                      physicalNetworkName:
+                        description: |-
+                          physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+                          Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+                          In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+                          this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+                        maxLength: 253
+                        minLength: 1
+                        type: string
+                        x-kubernetes-validations:
+                        - message: physicalNetworkName cannot contain `,` or `:` characters
+                          rule: self.matches('^[^,:]+$')
+                      role:
+                        description: |-
+                          role describes the network role in the pod, required.
+                          Control whether the pod interface will act as primary or secondary.
+                          Localnet topology support `Secondary` only.
+                          The network will be assigned to pods who has the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+                          to subject.
+                        enum:
+                        - Primary
+                        - Secondary
+                        type: string
+                      subnets:
+                        description: |-
+                          subnets is a list of subnets used for the pod network across the cluster.
+                          The list may be either 1 IPv4 subnet, 1 IPv6 subnet, or 1 of each IP family.
+                          When set, OVN-Kubernetes assign IP address of the specified CIDRs to the connected pod,
+                          saving manual IP assigning or relaying on external IPAM service (DHCP server).
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                          eliminating the need for manual IP assignment or reliance on an external IPAM service (e.g., a DHCP server).
```
created_at: 2025-04-07T10:46:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
+                        minimum: 576
+                        type: integer
+                      physicalNetworkName:
+                        description: |-
+                          physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+                          Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+                          In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+                          this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+                        maxLength: 253
+                        minLength: 1
+                        type: string
+                        x-kubernetes-validations:
+                        - message: physicalNetworkName cannot contain `,` or `:` characters
+                          rule: self.matches('^[^,:]+$')
+                      role:
+                        description: |-
+                          role describes the network role in the pod, required.
+                          Control whether the pod interface will act as primary or secondary.
+                          Localnet topology support `Secondary` only.
+                          The network will be assigned to pods who has the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+                          to subject.
+                        enum:
+                        - Primary
+                        - Secondary
+                        type: string
+                      subnets:
+                        description: |-
+                          subnets is a list of subnets used for the pod network across the cluster.
+                          The list may be either 1 IPv4 subnet, 1 IPv6 subnet, or 1 of each IP family.
+                          When set, OVN-Kubernetes assign IP address of the specified CIDRs to the connected pod,
+                          saving manual IP assigning or relaying on external IPAM service (DHCP server).
+                          subnets is optional. When omitted OVN-Kubernetes won't assign IP address automatically.
+                          Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who provide IPAM services
+                          (e.g.: DHCP server), `ipam.mode` should set with `Disabled, turning off OVN-Kubernetes IPAM and avoid
+                          conflicts with the existing IPAM services on the subject network.
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: `When physicalNetworkName points to the OVS bridge mapping of a network that provides IPAM services (e.g., a DHCP server), ipam.mode should be set to Disabled. This turns off OVN-Kubernetes IPAM and avoids conflicts with the existing IPAM services on this localnet network.`
created_at: 2025-04-07T10:48:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
+                        minimum: 576
+                        type: integer
+                      physicalNetworkName:
+                        description: |-
+                          physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+                          Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+                          In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+                          this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+                        maxLength: 253
+                        minLength: 1
+                        type: string
+                        x-kubernetes-validations:
+                        - message: physicalNetworkName cannot contain `,` or `:` characters
+                          rule: self.matches('^[^,:]+$')
+                      role:
+                        description: |-
+                          role describes the network role in the pod, required.
+                          Control whether the pod interface will act as primary or secondary.
+                          Localnet topology support `Secondary` only.
+                          The network will be assigned to pods who has the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+                          to subject.
+                        enum:
+                        - Primary
+                        - Secondary
+                        type: string
+                      subnets:
+                        description: |-
+                          subnets is a list of subnets used for the pod network across the cluster.
+                          The list may be either 1 IPv4 subnet, 1 IPv6 subnet, or 1 of each IP family.
+                          When set, OVN-Kubernetes assign IP address of the specified CIDRs to the connected pod,
+                          saving manual IP assigning or relaying on external IPAM service (DHCP server).
+                          subnets is optional. When omitted OVN-Kubernetes won't assign IP address automatically.
+                          Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who provide IPAM services
+                          (e.g.: DHCP server), `ipam.mode` should set with `Disabled, turning off OVN-Kubernetes IPAM and avoid
+                          conflicts with the existing IPAM services on the subject network.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 2
+                        minItems: 1
+                        type: array
+                        x-kubernetes-validations:
+                        - message: When 2 CIDRs are set, they must be from different
+                            IP families
+                          rule: size(self) != 2 || !isCIDR(self[0]) || !isCIDR(self[1])
+                            || cidr(self[0]).ip().family() != cidr(self[1]).ip().family()
+                      vlan:
+                        description: |-
+                          vlan configuration for the network.
+                          vlan.mode is the VLAN mode.
+                            When "Access" is set, OVN-Kuberentes configures the network logical switch port in access mode.
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                            When "Access" is set, OVN-Kubernetes configures the network logical switch port in access mode.
```
created_at: 2025-04-07T10:51:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
+                        minimum: 576
+                        type: integer
+                      physicalNetworkName:
+                        description: |-
+                          physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+                          Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+                          In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+                          this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+                        maxLength: 253
+                        minLength: 1
+                        type: string
+                        x-kubernetes-validations:
+                        - message: physicalNetworkName cannot contain `,` or `:` characters
+                          rule: self.matches('^[^,:]+$')
+                      role:
+                        description: |-
+                          role describes the network role in the pod, required.
+                          Control whether the pod interface will act as primary or secondary.
+                          Localnet topology support `Secondary` only.
+                          The network will be assigned to pods who has the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+                          to subject.
+                        enum:
+                        - Primary
+                        - Secondary
+                        type: string
+                      subnets:
+                        description: |-
+                          subnets is a list of subnets used for the pod network across the cluster.
+                          The list may be either 1 IPv4 subnet, 1 IPv6 subnet, or 1 of each IP family.
+                          When set, OVN-Kubernetes assign IP address of the specified CIDRs to the connected pod,
+                          saving manual IP assigning or relaying on external IPAM service (DHCP server).
+                          subnets is optional. When omitted OVN-Kubernetes won't assign IP address automatically.
+                          Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who provide IPAM services
+                          (e.g.: DHCP server), `ipam.mode` should set with `Disabled, turning off OVN-Kubernetes IPAM and avoid
+                          conflicts with the existing IPAM services on the subject network.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 2
+                        minItems: 1
+                        type: array
+                        x-kubernetes-validations:
+                        - message: When 2 CIDRs are set, they must be from different
+                            IP families
+                          rule: size(self) != 2 || !isCIDR(self[0]) || !isCIDR(self[1])
+                            || cidr(self[0]).ip().family() != cidr(self[1]).ip().family()
+                      vlan:
+                        description: |-
+                          vlan configuration for the network.
+                          vlan.mode is the VLAN mode.
+                            When "Access" is set, OVN-Kuberentes configures the network logical switch port in access mode.
+                          vlan.access is the access VLAN configuration.
+                          vlan.access.id is the VLAN ID (VID) to be set on the network logical switch port.
+                          vlan is optional, when omitted the underlying network default VLAN will be used (usually `1`).
+                          When set, OVN-Kubernetes will apply VLAN configuration to the SDN infra and to the connected pods.
+                        properties:
+                          access:
+                            description: Access is the access VLAN configuration
+                            properties:
+                              id:
+                                description: |-
+                                  id is the VLAN ID (VID) to be set for the network.
+                                  id should be higher than 0 and lower than 4095.
+                                format: int32
+                                maximum: 4094
+                                minimum: 1
+                                type: integer
+                            required:
+                            - id
+                            type: object
+                          mode:
+                            description: |-
+                              mode describe the network VLAN mode.
+                              Allowed value is "Access".
+                              Access sets the network logical switch port in access mode, according to the config.
+                            enum:
+                            - Access
+                            type: string
+                        required:
+                        - mode
+                        type: object
+                        x-kubernetes-validations:
+                        - message: vlan access config is required when vlan mode is
+                            'Access', and forbidden otherwise
+                          rule: 'has(self.mode) && self.mode == ''Access'' ? has(self.access):
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: Does `has(self.access)` automatically mean that `has(self.access.id)` is true or should we add `&& has(self.access.id)`?
created_at: 2025-04-07T10:59:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
+                        minimum: 576
+                        type: integer
+                      physicalNetworkName:
+                        description: |-
+                          physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+                          Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+                          In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+                          this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+                        maxLength: 253
+                        minLength: 1
+                        type: string
+                        x-kubernetes-validations:
+                        - message: physicalNetworkName cannot contain `,` or `:` characters
+                          rule: self.matches('^[^,:]+$')
+                      role:
+                        description: |-
+                          role describes the network role in the pod, required.
+                          Control whether the pod interface will act as primary or secondary.
+                          Localnet topology support `Secondary` only.
+                          The network will be assigned to pods who has the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+                          to subject.
+                        enum:
+                        - Primary
+                        - Secondary
+                        type: string
+                      subnets:
+                        description: |-
+                          subnets is a list of subnets used for the pod network across the cluster.
+                          The list may be either 1 IPv4 subnet, 1 IPv6 subnet, or 1 of each IP family.
+                          When set, OVN-Kubernetes assign IP address of the specified CIDRs to the connected pod,
+                          saving manual IP assigning or relaying on external IPAM service (DHCP server).
+                          subnets is optional. When omitted OVN-Kubernetes won't assign IP address automatically.
+                          Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who provide IPAM services
+                          (e.g.: DHCP server), `ipam.mode` should set with `Disabled, turning off OVN-Kubernetes IPAM and avoid
+                          conflicts with the existing IPAM services on the subject network.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 2
+                        minItems: 1
+                        type: array
+                        x-kubernetes-validations:
+                        - message: When 2 CIDRs are set, they must be from different
+                            IP families
+                          rule: size(self) != 2 || !isCIDR(self[0]) || !isCIDR(self[1])
+                            || cidr(self[0]).ip().family() != cidr(self[1]).ip().family()
+                      vlan:
+                        description: |-
+                          vlan configuration for the network.
+                          vlan.mode is the VLAN mode.
+                            When "Access" is set, OVN-Kuberentes configures the network logical switch port in access mode.
+                          vlan.access is the access VLAN configuration.
+                          vlan.access.id is the VLAN ID (VID) to be set on the network logical switch port.
+                          vlan is optional, when omitted the underlying network default VLAN will be used (usually `1`).
+                          When set, OVN-Kubernetes will apply VLAN configuration to the SDN infra and to the connected pods.
+                        properties:
+                          access:
+                            description: Access is the access VLAN configuration
+                            properties:
+                              id:
+                                description: |-
+                                  id is the VLAN ID (VID) to be set for the network.
+                                  id should be higher than 0 and lower than 4095.
+                                format: int32
+                                maximum: 4094
+                                minimum: 1
+                                type: integer
+                            required:
+                            - id
+                            type: object
+                          mode:
+                            description: |-
+                              mode describe the network VLAN mode.
+                              Allowed value is "Access".
+                              Access sets the network logical switch port in access mode, according to the config.
+                            enum:
+                            - Access
+                            type: string
+                        required:
+                        - mode
+                        type: object
+                        x-kubernetes-validations:
+                        - message: vlan access config is required when vlan mode is
+                            'Access', and forbidden otherwise
+                          rule: 'has(self.mode) && self.mode == ''Access'' ? has(self.access):
+                            !has(self.access)'
+                    required:
+                    - physicalNetworkName
+                    - role
+                    type: object
+                    x-kubernetes-validations:
+                    - message: Subnets is required with ipam.mode is Enabled or unset,
+                        and forbidden otherwise
+                      rule: '!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode
+                        == ''Enabled'' ? has(self.subnets) : !has(self.subnets)'
+                    - message: excludeSubnets must be unset when subents is unset
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                    - message: excludeSubnets must be unset when subnets is unset
```
created_at: 2025-04-07T11:01:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
+                        minimum: 576
+                        type: integer
+                      physicalNetworkName:
+                        description: |-
+                          physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+                          Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+                          In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+                          this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+                        maxLength: 253
+                        minLength: 1
+                        type: string
+                        x-kubernetes-validations:
+                        - message: physicalNetworkName cannot contain `,` or `:` characters
+                          rule: self.matches('^[^,:]+$')
+                      role:
+                        description: |-
+                          role describes the network role in the pod, required.
+                          Control whether the pod interface will act as primary or secondary.
+                          Localnet topology support `Secondary` only.
+                          The network will be assigned to pods who has the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+                          to subject.
+                        enum:
+                        - Primary
+                        - Secondary
+                        type: string
+                      subnets:
+                        description: |-
+                          subnets is a list of subnets used for the pod network across the cluster.
+                          The list may be either 1 IPv4 subnet, 1 IPv6 subnet, or 1 of each IP family.
+                          When set, OVN-Kubernetes assign IP address of the specified CIDRs to the connected pod,
+                          saving manual IP assigning or relaying on external IPAM service (DHCP server).
+                          subnets is optional. When omitted OVN-Kubernetes won't assign IP address automatically.
+                          Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who provide IPAM services
+                          (e.g.: DHCP server), `ipam.mode` should set with `Disabled, turning off OVN-Kubernetes IPAM and avoid
+                          conflicts with the existing IPAM services on the subject network.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 2
+                        minItems: 1
+                        type: array
+                        x-kubernetes-validations:
+                        - message: When 2 CIDRs are set, they must be from different
+                            IP families
+                          rule: size(self) != 2 || !isCIDR(self[0]) || !isCIDR(self[1])
+                            || cidr(self[0]).ip().family() != cidr(self[1]).ip().family()
+                      vlan:
+                        description: |-
+                          vlan configuration for the network.
+                          vlan.mode is the VLAN mode.
+                            When "Access" is set, OVN-Kuberentes configures the network logical switch port in access mode.
+                          vlan.access is the access VLAN configuration.
+                          vlan.access.id is the VLAN ID (VID) to be set on the network logical switch port.
+                          vlan is optional, when omitted the underlying network default VLAN will be used (usually `1`).
+                          When set, OVN-Kubernetes will apply VLAN configuration to the SDN infra and to the connected pods.
+                        properties:
+                          access:
+                            description: Access is the access VLAN configuration
+                            properties:
+                              id:
+                                description: |-
+                                  id is the VLAN ID (VID) to be set for the network.
+                                  id should be higher than 0 and lower than 4095.
+                                format: int32
+                                maximum: 4094
+                                minimum: 1
+                                type: integer
+                            required:
+                            - id
+                            type: object
+                          mode:
+                            description: |-
+                              mode describe the network VLAN mode.
+                              Allowed value is "Access".
+                              Access sets the network logical switch port in access mode, according to the config.
+                            enum:
+                            - Access
+                            type: string
+                        required:
+                        - mode
+                        type: object
+                        x-kubernetes-validations:
+                        - message: vlan access config is required when vlan mode is
+                            'Access', and forbidden otherwise
+                          rule: 'has(self.mode) && self.mode == ''Access'' ? has(self.access):
+                            !has(self.access)'
+                    required:
+                    - physicalNetworkName
+                    - role
+                    type: object
+                    x-kubernetes-validations:
+                    - message: Subnets is required with ipam.mode is Enabled or unset,
+                        and forbidden otherwise
+                      rule: '!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode
+                        == ''Enabled'' ? has(self.subnets) : !has(self.subnets)'
+                    - message: excludeSubnets must be unset when subents is unset
+                      rule: '!has(self.excludeSubnets) || has(self.subnets)'
+                    - message: MTU should be greater than or equal to 1280 when IPv6
+                        subent is used
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: ```suggestion
                        subnet is used
```
created_at: 2025-04-07T11:06:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,3 +83,129 @@ type ClusterUserDefinedNetworkList struct {
 	metav1.ListMeta `json:"metadata,omitempty"`
 	Items           []ClusterUserDefinedNetwork `json:"items"`
 }
+
+// +kubebuilder:validation:XValidation:rule="!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode == 'Enabled' ? has(self.subnets) : !has(self.subnets)", message="Subnets is required with ipam.mode is Enabled or unset, and forbidden otherwise"
+// +kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || has(self.subnets)", message="excludeSubnets must be unset when subents is unset"
path: go-controller/pkg/crd/userdefinednetwork/v1/cudn.go
user: login: ricky-rav
body: ```suggestion
// +kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || has(self.subnets)", message="excludeSubnets must be unset when subnets is unset"
```
created_at: 2025-04-07T11:11:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,3 +83,129 @@ type ClusterUserDefinedNetworkList struct {
 	metav1.ListMeta `json:"metadata,omitempty"`
 	Items           []ClusterUserDefinedNetwork `json:"items"`
 }
+
+// +kubebuilder:validation:XValidation:rule="!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode == 'Enabled' ? has(self.subnets) : !has(self.subnets)", message="Subnets is required with ipam.mode is Enabled or unset, and forbidden otherwise"
+// +kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || has(self.subnets)", message="excludeSubnets must be unset when subents is unset"
+// +kubebuilder:validation:XValidation:rule="!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280", message="MTU should be greater than or equal to 1280 when IPv6 subent is used"
path: go-controller/pkg/crd/userdefinednetwork/v1/cudn.go
user: login: ricky-rav
body: ```suggestion
// +kubebuilder:validation:XValidation:rule="!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280", message="MTU should be greater than or equal to 1280 when an IPv6 subnet is used"
```
created_at: 2025-04-07T11:13:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,3 +83,129 @@ type ClusterUserDefinedNetworkList struct {
 	metav1.ListMeta `json:"metadata,omitempty"`
 	Items           []ClusterUserDefinedNetwork `json:"items"`
 }
+
+// +kubebuilder:validation:XValidation:rule="!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode == 'Enabled' ? has(self.subnets) : !has(self.subnets)", message="Subnets is required with ipam.mode is Enabled or unset, and forbidden otherwise"
+// +kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || has(self.subnets)", message="excludeSubnets must be unset when subents is unset"
+// +kubebuilder:validation:XValidation:rule="!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280", message="MTU should be greater than or equal to 1280 when IPv6 subent is used"
+// + ---
+// + TODO: enable the below validation once the following issue is resolved https://github.com/kubernetes/kubernetes/issues/130441
+// + kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || self.excludeSubnets.all(e, self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))",message="excludeSubnets must be subnetworks of the networks specified in the subnets field",fieldPath=".excludeSubnets"
+type LocalnetConfig struct {
+	// role describes the network role in the pod, required.
+	// Control whether the pod interface will act as primary or secondary.
path: go-controller/pkg/crd/userdefinednetwork/v1/cudn.go
user: login: ricky-rav
body: ```suggestion
	// Controls whether the pod interface will act as primary or secondary.
```
created_at: 2025-04-07T11:15:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,3 +83,129 @@ type ClusterUserDefinedNetworkList struct {
 	metav1.ListMeta `json:"metadata,omitempty"`
 	Items           []ClusterUserDefinedNetwork `json:"items"`
 }
+
+// +kubebuilder:validation:XValidation:rule="!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode == 'Enabled' ? has(self.subnets) : !has(self.subnets)", message="Subnets is required with ipam.mode is Enabled or unset, and forbidden otherwise"
+// +kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || has(self.subnets)", message="excludeSubnets must be unset when subents is unset"
+// +kubebuilder:validation:XValidation:rule="!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280", message="MTU should be greater than or equal to 1280 when IPv6 subent is used"
+// + ---
+// + TODO: enable the below validation once the following issue is resolved https://github.com/kubernetes/kubernetes/issues/130441
+// + kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || self.excludeSubnets.all(e, self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))",message="excludeSubnets must be subnetworks of the networks specified in the subnets field",fieldPath=".excludeSubnets"
+type LocalnetConfig struct {
+	// role describes the network role in the pod, required.
+	// Control whether the pod interface will act as primary or secondary.
+	// Localnet topology support `Secondary` only.
path: go-controller/pkg/crd/userdefinednetwork/v1/cudn.go
user: login: ricky-rav
body: ```suggestion
	// Localnet topology supports `Secondary` only.
```
created_at: 2025-04-07T11:15:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,3 +83,129 @@ type ClusterUserDefinedNetworkList struct {
 	metav1.ListMeta `json:"metadata,omitempty"`
 	Items           []ClusterUserDefinedNetwork `json:"items"`
 }
+
+// +kubebuilder:validation:XValidation:rule="!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode == 'Enabled' ? has(self.subnets) : !has(self.subnets)", message="Subnets is required with ipam.mode is Enabled or unset, and forbidden otherwise"
+// +kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || has(self.subnets)", message="excludeSubnets must be unset when subents is unset"
+// +kubebuilder:validation:XValidation:rule="!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280", message="MTU should be greater than or equal to 1280 when IPv6 subent is used"
+// + ---
+// + TODO: enable the below validation once the following issue is resolved https://github.com/kubernetes/kubernetes/issues/130441
+// + kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || self.excludeSubnets.all(e, self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))",message="excludeSubnets must be subnetworks of the networks specified in the subnets field",fieldPath=".excludeSubnets"
+type LocalnetConfig struct {
+	// role describes the network role in the pod, required.
+	// Control whether the pod interface will act as primary or secondary.
+	// Localnet topology support `Secondary` only.
+	// The network will be assigned to pods who has the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
path: go-controller/pkg/crd/userdefinednetwork/v1/cudn.go
user: login: ricky-rav
body: ```suggestion
	// The network will be assigned to pods with the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
```
created_at: 2025-04-07T11:16:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ormergi
body: Note that this is a generated file
created_at: 2025-04-07T13:27:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ormergi
body: The MTU setting changes in a follow-up commit and the this text as well
"udn, nad template: Set explicit default MTU for locanet topology"
created_at: 2025-04-07T13:34:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
+                        minimum: 576
+                        type: integer
+                      physicalNetworkName:
+                        description: |-
+                          physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+                          Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+                          In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+                          this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+                        maxLength: 253
+                        minLength: 1
+                        type: string
+                        x-kubernetes-validations:
+                        - message: physicalNetworkName cannot contain `,` or `:` characters
+                          rule: self.matches('^[^,:]+$')
+                      role:
+                        description: |-
+                          role describes the network role in the pod, required.
+                          Control whether the pod interface will act as primary or secondary.
+                          Localnet topology support `Secondary` only.
+                          The network will be assigned to pods who has the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+                          to subject.
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ormergi
body: We have similar text in other topologies Role attribute description, so I followed the same example.
I can add this to the Localnet subtree description (that is the GoDoc of LocalnetConfig{}) as well. 
WDYT?
created_at: 2025-04-07T13:43:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
+                        minimum: 576
+                        type: integer
+                      physicalNetworkName:
+                        description: |-
+                          physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+                          Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+                          In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+                          this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+                        maxLength: 253
+                        minLength: 1
+                        type: string
+                        x-kubernetes-validations:
+                        - message: physicalNetworkName cannot contain `,` or `:` characters
+                          rule: self.matches('^[^,:]+$')
+                      role:
+                        description: |-
+                          role describes the network role in the pod, required.
+                          Control whether the pod interface will act as primary or secondary.
+                          Localnet topology support `Secondary` only.
+                          The network will be assigned to pods who has the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+                          to subject.
+                        enum:
+                        - Primary
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ormergi
body: This is changed in follow-up commit "crds,udn: Allow NetworkRole Secondary only for Localnet topology"
created_at: 2025-04-07T13:44:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
+                        minimum: 576
+                        type: integer
+                      physicalNetworkName:
+                        description: |-
+                          physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+                          Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+                          In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+                          this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+                        maxLength: 253
+                        minLength: 1
+                        type: string
+                        x-kubernetes-validations:
+                        - message: physicalNetworkName cannot contain `,` or `:` characters
+                          rule: self.matches('^[^,:]+$')
+                      role:
+                        description: |-
+                          role describes the network role in the pod, required.
+                          Control whether the pod interface will act as primary or secondary.
+                          Localnet topology support `Secondary` only.
+                          The network will be assigned to pods who has the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+                          to subject.
+                        enum:
+                        - Primary
+                        - Secondary
+                        type: string
+                      subnets:
+                        description: |-
+                          subnets is a list of subnets used for the pod network across the cluster.
+                          The list may be either 1 IPv4 subnet, 1 IPv6 subnet, or 1 of each IP family.
+                          When set, OVN-Kubernetes assign IP address of the specified CIDRs to the connected pod,
+                          saving manual IP assigning or relaying on external IPAM service (DHCP server).
+                          subnets is optional. When omitted OVN-Kubernetes won't assign IP address automatically.
+                          Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who provide IPAM services
+                          (e.g.: DHCP server), `ipam.mode` should set with `Disabled, turning off OVN-Kubernetes IPAM and avoid
+                          conflicts with the existing IPAM services on the subject network.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 2
+                        minItems: 1
+                        type: array
+                        x-kubernetes-validations:
+                        - message: When 2 CIDRs are set, they must be from different
+                            IP families
+                          rule: size(self) != 2 || !isCIDR(self[0]) || !isCIDR(self[1])
+                            || cidr(self[0]).ip().family() != cidr(self[1]).ip().family()
+                      vlan:
+                        description: |-
+                          vlan configuration for the network.
+                          vlan.mode is the VLAN mode.
+                            When "Access" is set, OVN-Kuberentes configures the network logical switch port in access mode.
+                          vlan.access is the access VLAN configuration.
+                          vlan.access.id is the VLAN ID (VID) to be set on the network logical switch port.
+                          vlan is optional, when omitted the underlying network default VLAN will be used (usually `1`).
+                          When set, OVN-Kubernetes will apply VLAN configuration to the SDN infra and to the connected pods.
+                        properties:
+                          access:
+                            description: Access is the access VLAN configuration
+                            properties:
+                              id:
+                                description: |-
+                                  id is the VLAN ID (VID) to be set for the network.
+                                  id should be higher than 0 and lower than 4095.
+                                format: int32
+                                maximum: 4094
+                                minimum: 1
+                                type: integer
+                            required:
+                            - id
+                            type: object
+                          mode:
+                            description: |-
+                              mode describe the network VLAN mode.
+                              Allowed value is "Access".
+                              Access sets the network logical switch port in access mode, according to the config.
+                            enum:
+                            - Access
+                            type: string
+                        required:
+                        - mode
+                        type: object
+                        x-kubernetes-validations:
+                        - message: vlan access config is required when vlan mode is
+                            'Access', and forbidden otherwise
+                          rule: 'has(self.mode) && self.mode == ''Access'' ? has(self.access):
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ormergi
body: This check ensure when `mode=Access` the `access` subtree is set.
Since `access.id` is required field, when `access .id` is not specified the API should block the request. 
WDYT?
created_at: 2025-04-07T13:53:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
+                        minimum: 576
+                        type: integer
+                      physicalNetworkName:
+                        description: |-
+                          physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+                          Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+                          In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+                          this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+                        maxLength: 253
+                        minLength: 1
+                        type: string
+                        x-kubernetes-validations:
+                        - message: physicalNetworkName cannot contain `,` or `:` characters
+                          rule: self.matches('^[^,:]+$')
+                      role:
+                        description: |-
+                          role describes the network role in the pod, required.
+                          Control whether the pod interface will act as primary or secondary.
+                          Localnet topology support `Secondary` only.
+                          The network will be assigned to pods who has the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+                          to subject.
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: Ah ok. Yeah, I think in the localnet subtree description it definitely makes more sense than under "role", since you're explaining how a pod connects to a localnet. 
created_at: 2025-04-07T14:44:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -314,13 +314,205 @@ spec:
                       rule: '!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i,
                         isCIDR(i.cidr) && cidr(i.cidr).ip().family() == 6) || self.mtu
                         >= 1280'
+                  localnet:
+                    description: Localnet is the Localnet topology configuration.
+                    properties:
+                      excludeSubnets:
+                        description: |-
+                          excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+                          The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+                          excludeSubnets is optional. When omitted no IP address is excluded and all IP address specified in `subnets`
+                          list subject to be assigned.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who has reserved IP addresses
+                          that shouldn't be assigned by OVN-Kubernetes, the specified CIDRs will not be assigned. For example:
+                          Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+                          to pods: `10.0.0.201`, `10.0.0.202`.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 25
+                        minItems: 1
+                        type: array
+                      ipam:
+                        description: "ipam configurations for the network.\nipam is
+                          optional. When omitted, `subnets` must be specified.\nWhen
+                          `ipam.mode` is `Disabled`, `subnets` must be omitted.\n`ipam.mode`
+                          controls how much of the IP configuration will be managed
+                          by OVN.\n   When `Enabled`, OVN-Kubernetes will apply IP
+                          configuration to the SND infra and assign IPs from the selected\n
+                          \  subnet to the pods.\n   When `Disabled`, OVN-Kubernetes
+                          only assign MAC addresses and provides layer2 communication,
+                          enable users\n   configure IP addresses to the pods.\n`ipam.lifecycle`
+                          controls IP addresses management lifecycle.\n   When set
+                          with 'Persistent', the assigned IP addresses will be persisted
+                          in `ipamclaims.k8s.cni.cncf.io` object.\n\t  Useful for
+                          VMs, IP address will be persistent after restarts and migrations.
+                          Supported when `ipam.mode` is `Enabled`."
+                        minProperties: 1
+                        properties:
+                          lifecycle:
+                            description: |-
+                              Lifecycle controls IP addresses management lifecycle.
+
+                              The only allowed value is Persistent. When set, OVN Kubernetes assigned IP addresses will be persisted in an
+                              `ipamclaims.k8s.cni.cncf.io` object. These IP addresses will be reused by other pods if requested.
+                              Only supported when mode is `Enabled`.
+                            enum:
+                            - Persistent
+                            type: string
+                          mode:
+                            description: |-
+                              Mode controls how much of the IP configuration will be managed by OVN.
+                              `Enabled` means OVN-Kubernetes will apply IP configuration to the SDN infrastructure and it will also assign IPs
+                              from the selected subnet to the individual pods.
+                              `Disabled` means OVN-Kubernetes will only assign MAC addresses and provide layer 2 communication, letting users
+                              configure IP addresses for the pods.
+                              `Disabled` is only available for Secondary networks.
+                              By disabling IPAM, any Kubernetes features that rely on selecting pods by IP will no longer function
+                              (such as network policy, services, etc). Additionally, IP port security will also be disabled for interfaces attached to this network.
+                              Defaults to `Enabled`.
+                            enum:
+                            - Enabled
+                            - Disabled
+                            type: string
+                        type: object
+                        x-kubernetes-validations:
+                        - message: lifecycle Persistent is only supported when ipam.mode
+                            is Enabled
+                          rule: '!has(self.lifecycle) || self.lifecycle != ''Persistent''
+                            || !has(self.mode) || self.mode == ''Enabled'''
+                      mtu:
+                        description: |-
+                          mtu is the maximum transmission unit for a network.
+                          mtu is optional, if not provided, the globally configured value in OVN-Kubernetes (defaults to 1400) is used for the network.
+                          Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+                          Maximum value is 65536.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network configured with certain MTU settings,
+                          this field enable configuring the same MTU the pod interface, having the pod MTU aligned with the network.
+                          Misaligned MTU across the stack (e.g.: pod has MTU X, node NIC has MTU Y), could result in network disruptions
+                          and bad performance.
+                        format: int32
+                        maximum: 65536
+                        minimum: 576
+                        type: integer
+                      physicalNetworkName:
+                        description: |-
+                          physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+                          Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+                          In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+                          this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+                        maxLength: 253
+                        minLength: 1
+                        type: string
+                        x-kubernetes-validations:
+                        - message: physicalNetworkName cannot contain `,` or `:` characters
+                          rule: self.matches('^[^,:]+$')
+                      role:
+                        description: |-
+                          role describes the network role in the pod, required.
+                          Control whether the pod interface will act as primary or secondary.
+                          Localnet topology support `Secondary` only.
+                          The network will be assigned to pods who has the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+                          to subject.
+                        enum:
+                        - Primary
+                        - Secondary
+                        type: string
+                      subnets:
+                        description: |-
+                          subnets is a list of subnets used for the pod network across the cluster.
+                          The list may be either 1 IPv4 subnet, 1 IPv6 subnet, or 1 of each IP family.
+                          When set, OVN-Kubernetes assign IP address of the specified CIDRs to the connected pod,
+                          saving manual IP assigning or relaying on external IPAM service (DHCP server).
+                          subnets is optional. When omitted OVN-Kubernetes won't assign IP address automatically.
+                          Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.
+                          The format should match standard CIDR notation (for example, "10.128.0.0/16").
+                          This field must be omitted if `ipam.mode` is `Disabled`.
+                          In a scenario `physicalNetworkName` points to OVS bridge mapping of a network who provide IPAM services
+                          (e.g.: DHCP server), `ipam.mode` should set with `Disabled, turning off OVN-Kubernetes IPAM and avoid
+                          conflicts with the existing IPAM services on the subject network.
+                        items:
+                          maxLength: 43
+                          type: string
+                          x-kubernetes-validations:
+                          - message: CIDR is invalid
+                            rule: isCIDR(self)
+                        maxItems: 2
+                        minItems: 1
+                        type: array
+                        x-kubernetes-validations:
+                        - message: When 2 CIDRs are set, they must be from different
+                            IP families
+                          rule: size(self) != 2 || !isCIDR(self[0]) || !isCIDR(self[1])
+                            || cidr(self[0]).ip().family() != cidr(self[1]).ip().family()
+                      vlan:
+                        description: |-
+                          vlan configuration for the network.
+                          vlan.mode is the VLAN mode.
+                            When "Access" is set, OVN-Kuberentes configures the network logical switch port in access mode.
+                          vlan.access is the access VLAN configuration.
+                          vlan.access.id is the VLAN ID (VID) to be set on the network logical switch port.
+                          vlan is optional, when omitted the underlying network default VLAN will be used (usually `1`).
+                          When set, OVN-Kubernetes will apply VLAN configuration to the SDN infra and to the connected pods.
+                        properties:
+                          access:
+                            description: Access is the access VLAN configuration
+                            properties:
+                              id:
+                                description: |-
+                                  id is the VLAN ID (VID) to be set for the network.
+                                  id should be higher than 0 and lower than 4095.
+                                format: int32
+                                maximum: 4094
+                                minimum: 1
+                                type: integer
+                            required:
+                            - id
+                            type: object
+                          mode:
+                            description: |-
+                              mode describe the network VLAN mode.
+                              Allowed value is "Access".
+                              Access sets the network logical switch port in access mode, according to the config.
+                            enum:
+                            - Access
+                            type: string
+                        required:
+                        - mode
+                        type: object
+                        x-kubernetes-validations:
+                        - message: vlan access config is required when vlan mode is
+                            'Access', and forbidden otherwise
+                          rule: 'has(self.mode) && self.mode == ''Access'' ? has(self.access):
path: dist/templates/k8s.ovn.org_clusteruserdefinednetworks.yaml.j2
user: login: ricky-rav
body: oh I see. As long as you've tested it, I'm fine with it :) 
created_at: 2025-04-07T14:46:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -203,6 +257,22 @@ func validateIPAM(ipam *userdefinednetworkv1.IPAMConfig) error {
 	return nil
 }
 
+func validateVLANConfig(vlanConfig *userdefinednetworkv1.VLANConfig) error {
+	if vlanConfig == nil {
+		return nil
+	}
+	if vlanConfig.Mode != userdefinednetworkv1.VLANModeAccess {
+		return fmt.Errorf("vlan.mode support 'Access' mode only")
+	}
+	if vlanConfig.Access == nil {
+		return fmt.Errorf("when vlan.mode is 'Access', vlan.access is required")
+	}
+	if vlanConfig.Access.ID <= 0 || vlanConfig.Access.ID >= 4095 {
+		return fmt.Errorf("vlan.access.id should be higher then 0 and lower then 4095")
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go
user: login: ricky-rav
body: ```suggestion
		return fmt.Errorf("vlan.access.id should be higher than 0 and lower than 4095")
```
created_at: 2025-04-07T16:20:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,10 +223,151 @@ var _ = Describe("NetAttachDefTemplate", func() {
 			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
 				Topology: udnv1.NetworkTopologyLayer2, Layer3: &udnv1.Layer3Config{}}}},
 		),
+		Entry("CUDN, invalid topology: topology layer2 & localnet config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLayer2, Localnet: &udnv1.LocalnetConfig{}}}},
+		),
 		Entry("CUDN, invalid topology: topology layer3 & layer2 config",
 			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
 				Topology: udnv1.NetworkTopologyLayer3, Layer2: &udnv1.Layer2Config{}}}},
 		),
+		Entry("CUDN, invalid topology: topology layer3 & localnet config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLayer3, Localnet: &udnv1.LocalnetConfig{}}}},
+		),
+		Entry("CUDN, invalid topology: topology localnet & layer2 config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Layer2: &udnv1.Layer2Config{}}}},
+		),
+		Entry("CUDN, invalid topology: topology localnet & layer3 config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Layer3: &udnv1.Layer3Config{}}}},
+		),
+		Entry("CUDN, localnet, invalid IPAM config: IPAM lifecycle & disabled ipam mode",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:    udnv1.NetworkRoleSecondary,
+					Subnets: udnv1.DualStackCIDRs{"192.168.100.0/16"},
+					IPAM: &udnv1.IPAMConfig{
+						Lifecycle: udnv1.IPAMLifecyclePersistent,
+						Mode:      udnv1.IPAMDisabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid IPAM config: IPAM enabled & no subnet",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:    udnv1.NetworkRoleSecondary,
+					Subnets: udnv1.DualStackCIDRs{},
+					IPAM: &udnv1.IPAMConfig{
+						Mode: udnv1.IPAMEnabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid IPAM config: IPAM disabled & subnet",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:    udnv1.NetworkRoleSecondary,
+					Subnets: udnv1.DualStackCIDRs{"192.168.100.0/16"},
+					IPAM: &udnv1.IPAMConfig{
+						Mode: udnv1.IPAMDisabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid config: subnets is unset & excludeSubnet is set",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:           udnv1.NetworkRoleSecondary,
+					ExcludeSubnets: []udnv1.CIDR{"192.168.100.0/31", "192.168.100.10/31"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid config: IPAM disabled and excludeSubnet is set",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:           udnv1.NetworkRoleSecondary,
+					ExcludeSubnets: []udnv1.CIDR{"192.168.100.0/31", "192.168.100.10/31"},
+					IPAM: &udnv1.IPAMConfig{
+						Mode: udnv1.IPAMDisabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: invalid role",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRolePrimary,
+					PhysicalNetworkName: "mylocalnet1",
+					Subnets:             udnv1.DualStackCIDRs{"192.168.100.0/16"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: invalid physicalNetworkName: empty",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRoleSecondary,
+					PhysicalNetworkName: "",
+					Subnets:             udnv1.DualStackCIDRs{"192.168.100.0/16"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: invalid physicalNetworkName: too long",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRoleSecondary,
+					PhysicalNetworkName: "ycx7b6dhkhytzva3wrma0cu6mjhqpo2ty20cmpdg9ptvmt1mo9dfnrs56nr0bvg6z6zha5y208js6e2iwk6xb97sp2sojg48lu9d5vxbzzq40rwj1wchae3ju3dpj6qfsjbzjzmc0k489bloe49z4857kds43rqpeca3p5z2dfz562qu59qqb8qa3vo6pmwuaume581dqhlsz57yvbvgu5hmmmzremac7w7l4rmuirkk91767llw0vskanlc339876",
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go
user: login: ricky-rav
body: How about:
`PhysicalNetworkName:  strings.Repeat("a", 254)`
created_at: 2025-04-07T16:31:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,10 +223,151 @@ var _ = Describe("NetAttachDefTemplate", func() {
 			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
 				Topology: udnv1.NetworkTopologyLayer2, Layer3: &udnv1.Layer3Config{}}}},
 		),
+		Entry("CUDN, invalid topology: topology layer2 & localnet config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLayer2, Localnet: &udnv1.LocalnetConfig{}}}},
+		),
 		Entry("CUDN, invalid topology: topology layer3 & layer2 config",
 			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
 				Topology: udnv1.NetworkTopologyLayer3, Layer2: &udnv1.Layer2Config{}}}},
 		),
+		Entry("CUDN, invalid topology: topology layer3 & localnet config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLayer3, Localnet: &udnv1.LocalnetConfig{}}}},
+		),
+		Entry("CUDN, invalid topology: topology localnet & layer2 config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Layer2: &udnv1.Layer2Config{}}}},
+		),
+		Entry("CUDN, invalid topology: topology localnet & layer3 config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Layer3: &udnv1.Layer3Config{}}}},
+		),
+		Entry("CUDN, localnet, invalid IPAM config: IPAM lifecycle & disabled ipam mode",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:    udnv1.NetworkRoleSecondary,
+					Subnets: udnv1.DualStackCIDRs{"192.168.100.0/16"},
+					IPAM: &udnv1.IPAMConfig{
+						Lifecycle: udnv1.IPAMLifecyclePersistent,
+						Mode:      udnv1.IPAMDisabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid IPAM config: IPAM enabled & no subnet",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:    udnv1.NetworkRoleSecondary,
+					Subnets: udnv1.DualStackCIDRs{},
+					IPAM: &udnv1.IPAMConfig{
+						Mode: udnv1.IPAMEnabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid IPAM config: IPAM disabled & subnet",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:    udnv1.NetworkRoleSecondary,
+					Subnets: udnv1.DualStackCIDRs{"192.168.100.0/16"},
+					IPAM: &udnv1.IPAMConfig{
+						Mode: udnv1.IPAMDisabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid config: subnets is unset & excludeSubnet is set",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:           udnv1.NetworkRoleSecondary,
+					ExcludeSubnets: []udnv1.CIDR{"192.168.100.0/31", "192.168.100.10/31"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid config: IPAM disabled and excludeSubnet is set",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:           udnv1.NetworkRoleSecondary,
+					ExcludeSubnets: []udnv1.CIDR{"192.168.100.0/31", "192.168.100.10/31"},
+					IPAM: &udnv1.IPAMConfig{
+						Mode: udnv1.IPAMDisabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: invalid role",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRolePrimary,
+					PhysicalNetworkName: "mylocalnet1",
+					Subnets:             udnv1.DualStackCIDRs{"192.168.100.0/16"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: invalid physicalNetworkName: empty",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRoleSecondary,
+					PhysicalNetworkName: "",
+					Subnets:             udnv1.DualStackCIDRs{"192.168.100.0/16"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: invalid physicalNetworkName: too long",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRoleSecondary,
+					PhysicalNetworkName: "ycx7b6dhkhytzva3wrma0cu6mjhqpo2ty20cmpdg9ptvmt1mo9dfnrs56nr0bvg6z6zha5y208js6e2iwk6xb97sp2sojg48lu9d5vxbzzq40rwj1wchae3ju3dpj6qfsjbzjzmc0k489bloe49z4857kds43rqpeca3p5z2dfz562qu59qqb8qa3vo6pmwuaume581dqhlsz57yvbvgu5hmmmzremac7w7l4rmuirkk91767llw0vskanlc339876",
+					Subnets:             udnv1.DualStackCIDRs{"192.168.100.0/16"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: excludeSubnets not in range of subnets",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{Role: udnv1.NetworkRoleSecondary, PhysicalNetworkName: "localnet1",
+					Subnets:        udnv1.DualStackCIDRs{"192.168.0.0/16", "2001:dbb::/64"},
+					ExcludeSubnets: []udnv1.CIDR{"192.200.0.0/30", "2001:aaa::/127", "192.300.0.1/32", "2001:bbb::1/120"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: VLAN: invalid mode",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Localnet: &udnv1.LocalnetConfig{Role: udnv1.NetworkRoleSecondary, PhysicalNetworkName: "localnet1",
+					VLAN: &udnv1.VLANConfig{Mode: "off"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: VLAN: empty vlan access config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Localnet: &udnv1.LocalnetConfig{Role: udnv1.NetworkRoleSecondary, PhysicalNetworkName: "localnet1",
+					VLAN: &udnv1.VLANConfig{Mode: "Access"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: VLAN: invalid access vlan id - 1",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Localnet: &udnv1.LocalnetConfig{Role: udnv1.NetworkRoleSecondary, PhysicalNetworkName: "localnet1",
+					VLAN: &udnv1.VLANConfig{Access: &udnv1.AccessVLANConfig{ID: 1}},
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go
user: login: ricky-rav
body: wait,  1 is allowed! How can this unit test pass in CI?

```
	if vlanConfig.Access.ID <= 0 || vlanConfig.Access.ID >= 4095 {
		return fmt.Errorf("vlan.access.id should be higher then 0 and lower then 4095")
```
created_at: 2025-04-07T16:35:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,3 +83,129 @@ type ClusterUserDefinedNetworkList struct {
 	metav1.ListMeta `json:"metadata,omitempty"`
 	Items           []ClusterUserDefinedNetwork `json:"items"`
 }
+
+// +kubebuilder:validation:XValidation:rule="!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode == 'Enabled' ? has(self.subnets) : !has(self.subnets)", message="Subnets is required with ipam.mode is Enabled or unset, and forbidden otherwise"
+// +kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || has(self.subnets)", message="excludeSubnets must be unset when subnets is unset"
+// +kubebuilder:validation:XValidation:rule="!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280", message="MTU should be greater than or equal to 1280 when an IPv6 subnet is used"
+// + ---
+// + TODO: enable the below validation once the following issue is resolved https://github.com/kubernetes/kubernetes/issues/130441
+// + kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || self.excludeSubnets.all(e, self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))",message="excludeSubnets must be subnetworks of the networks specified in the subnets field",fieldPath=".excludeSubnets"
+type LocalnetConfig struct {
+	// role describes the network role in the pod, required.
+	// Controls whether the pod interface will act as primary or secondary.
+	// Localnet topology supports `Secondary` only.
+	// The network will be assigned to pods that have the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+	// to subject.
+	//
+	// +required
+	Role NetworkRole `json:"role"`
+
+	// physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+	// Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+	// In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+	// this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+	//
+	// +kubebuilder:validation:MinLength=1
+	// +kubebuilder:validation:MaxLength=253
+	// +kubebuilder:validation:XValidation:rule="self.matches('^[^,:]+$')", message="physicalNetworkName cannot contain `,` or `:` characters"
+	// +required
+	PhysicalNetworkName string `json:"physicalNetworkName"`
+
+	// subnets is a list of subnets used for pods in this localnet network across the cluster.
+	// The list may be either 1 IPv4 subnet, 1 IPv6 subnet, or 1 of each IP family.
+	// When set, OVN-Kubernetes assigns an IP address from the specified CIDRs to the connected pod,
+	// eliminating the need for manual IP assignment or reliance on an external IPAM service (e.g., a DHCP server).
+	// subnets is optional. When omitted OVN-Kubernetes won't assign IP address automatically.
+	// Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.
+	// The format should match standard CIDR notation (for example, "10.128.0.0/16").
+	// This field must be omitted if `ipam.mode` is `Disabled`.
+	// When physicalNetworkName points to the OVS bridge mapping of a network that provides IPAM services
+	// (e.g., a DHCP server), ipam.mode should be set to Disabled. This turns off OVN-Kubernetes IPAM and avoids
+	// conflicts with the existing IPAM services on this localnet network.
+	//
+	// +optional
+	Subnets DualStackCIDRs `json:"subnets,omitempty"`
+
+	// excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+	// The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+	// excludeSubnets is optional. When omitted no IP address is excluded and all IP addresses specified in `subnets`
+	// are subject to be assignment.
path: go-controller/pkg/crd/userdefinednetwork/v1/cudn.go
user: login: ricky-rav
body: ```suggestion
	// are subject to assignment.
```
created_at: 2025-04-07T17:42:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,10 +223,151 @@ var _ = Describe("NetAttachDefTemplate", func() {
 			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
 				Topology: udnv1.NetworkTopologyLayer2, Layer3: &udnv1.Layer3Config{}}}},
 		),
+		Entry("CUDN, invalid topology: topology layer2 & localnet config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLayer2, Localnet: &udnv1.LocalnetConfig{}}}},
+		),
 		Entry("CUDN, invalid topology: topology layer3 & layer2 config",
 			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
 				Topology: udnv1.NetworkTopologyLayer3, Layer2: &udnv1.Layer2Config{}}}},
 		),
+		Entry("CUDN, invalid topology: topology layer3 & localnet config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLayer3, Localnet: &udnv1.LocalnetConfig{}}}},
+		),
+		Entry("CUDN, invalid topology: topology localnet & layer2 config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Layer2: &udnv1.Layer2Config{}}}},
+		),
+		Entry("CUDN, invalid topology: topology localnet & layer3 config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Layer3: &udnv1.Layer3Config{}}}},
+		),
+		Entry("CUDN, localnet, invalid IPAM config: IPAM lifecycle & disabled ipam mode",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:    udnv1.NetworkRoleSecondary,
+					Subnets: udnv1.DualStackCIDRs{"192.168.100.0/16"},
+					IPAM: &udnv1.IPAMConfig{
+						Lifecycle: udnv1.IPAMLifecyclePersistent,
+						Mode:      udnv1.IPAMDisabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid IPAM config: IPAM enabled & no subnet",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:    udnv1.NetworkRoleSecondary,
+					Subnets: udnv1.DualStackCIDRs{},
+					IPAM: &udnv1.IPAMConfig{
+						Mode: udnv1.IPAMEnabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid IPAM config: IPAM disabled & subnet",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:    udnv1.NetworkRoleSecondary,
+					Subnets: udnv1.DualStackCIDRs{"192.168.100.0/16"},
+					IPAM: &udnv1.IPAMConfig{
+						Mode: udnv1.IPAMDisabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid config: subnets is unset & excludeSubnet is set",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:           udnv1.NetworkRoleSecondary,
+					ExcludeSubnets: []udnv1.CIDR{"192.168.100.0/31", "192.168.100.10/31"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid config: IPAM disabled and excludeSubnet is set",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:           udnv1.NetworkRoleSecondary,
+					ExcludeSubnets: []udnv1.CIDR{"192.168.100.0/31", "192.168.100.10/31"},
+					IPAM: &udnv1.IPAMConfig{
+						Mode: udnv1.IPAMDisabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: invalid role",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRolePrimary,
+					PhysicalNetworkName: "mylocalnet1",
+					Subnets:             udnv1.DualStackCIDRs{"192.168.100.0/16"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: invalid physicalNetworkName: empty",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRoleSecondary,
+					PhysicalNetworkName: "",
+					Subnets:             udnv1.DualStackCIDRs{"192.168.100.0/16"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: invalid physicalNetworkName: too long",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRoleSecondary,
+					PhysicalNetworkName: "ycx7b6dhkhytzva3wrma0cu6mjhqpo2ty20cmpdg9ptvmt1mo9dfnrs56nr0bvg6z6zha5y208js6e2iwk6xb97sp2sojg48lu9d5vxbzzq40rwj1wchae3ju3dpj6qfsjbzjzmc0k489bloe49z4857kds43rqpeca3p5z2dfz562qu59qqb8qa3vo6pmwuaume581dqhlsz57yvbvgu5hmmmzremac7w7l4rmuirkk91767llw0vskanlc339876",
+					Subnets:             udnv1.DualStackCIDRs{"192.168.100.0/16"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: excludeSubnets not in range of subnets",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{Role: udnv1.NetworkRoleSecondary, PhysicalNetworkName: "localnet1",
+					Subnets:        udnv1.DualStackCIDRs{"192.168.0.0/16", "2001:dbb::/64"},
+					ExcludeSubnets: []udnv1.CIDR{"192.200.0.0/30", "2001:aaa::/127", "192.300.0.1/32", "2001:bbb::1/120"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: VLAN: invalid mode",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Localnet: &udnv1.LocalnetConfig{Role: udnv1.NetworkRoleSecondary, PhysicalNetworkName: "localnet1",
+					VLAN: &udnv1.VLANConfig{Mode: "off"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: VLAN: empty vlan access config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Localnet: &udnv1.LocalnetConfig{Role: udnv1.NetworkRoleSecondary, PhysicalNetworkName: "localnet1",
+					VLAN: &udnv1.VLANConfig{Mode: "Access"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: VLAN: invalid access vlan id - 1",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Localnet: &udnv1.LocalnetConfig{Role: udnv1.NetworkRoleSecondary, PhysicalNetworkName: "localnet1",
+					VLAN: &udnv1.VLANConfig{Access: &udnv1.AccessVLANConfig{ID: 1}},
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go
user: login: ormergi
body: "1" doesnt satisfy "<= 0" or ">= 4095"
What do  I miss?
created_at: 2025-04-08T07:28:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -203,6 +257,22 @@ func validateIPAM(ipam *userdefinednetworkv1.IPAMConfig) error {
 	return nil
 }
 
+func validateVLANConfig(vlanConfig *userdefinednetworkv1.VLANConfig) error {
+	if vlanConfig == nil {
+		return nil
+	}
+	if vlanConfig.Mode != userdefinednetworkv1.VLANModeAccess {
+		return fmt.Errorf("vlan.mode support 'Access' mode only")
+	}
+	if vlanConfig.Access == nil {
+		return fmt.Errorf("when vlan.mode is 'Access', vlan.access is required")
+	}
+	if vlanConfig.Access.ID <= 0 || vlanConfig.Access.ID >= 4095 {
+		return fmt.Errorf("vlan.access.id should be higher then 0 and lower then 4095")
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go
user: login: ormergi
body: Fixed
created_at: 2025-04-08T07:31:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,10 +223,151 @@ var _ = Describe("NetAttachDefTemplate", func() {
 			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
 				Topology: udnv1.NetworkTopologyLayer2, Layer3: &udnv1.Layer3Config{}}}},
 		),
+		Entry("CUDN, invalid topology: topology layer2 & localnet config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLayer2, Localnet: &udnv1.LocalnetConfig{}}}},
+		),
 		Entry("CUDN, invalid topology: topology layer3 & layer2 config",
 			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
 				Topology: udnv1.NetworkTopologyLayer3, Layer2: &udnv1.Layer2Config{}}}},
 		),
+		Entry("CUDN, invalid topology: topology layer3 & localnet config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLayer3, Localnet: &udnv1.LocalnetConfig{}}}},
+		),
+		Entry("CUDN, invalid topology: topology localnet & layer2 config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Layer2: &udnv1.Layer2Config{}}}},
+		),
+		Entry("CUDN, invalid topology: topology localnet & layer3 config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Layer3: &udnv1.Layer3Config{}}}},
+		),
+		Entry("CUDN, localnet, invalid IPAM config: IPAM lifecycle & disabled ipam mode",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:    udnv1.NetworkRoleSecondary,
+					Subnets: udnv1.DualStackCIDRs{"192.168.100.0/16"},
+					IPAM: &udnv1.IPAMConfig{
+						Lifecycle: udnv1.IPAMLifecyclePersistent,
+						Mode:      udnv1.IPAMDisabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid IPAM config: IPAM enabled & no subnet",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:    udnv1.NetworkRoleSecondary,
+					Subnets: udnv1.DualStackCIDRs{},
+					IPAM: &udnv1.IPAMConfig{
+						Mode: udnv1.IPAMEnabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid IPAM config: IPAM disabled & subnet",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:    udnv1.NetworkRoleSecondary,
+					Subnets: udnv1.DualStackCIDRs{"192.168.100.0/16"},
+					IPAM: &udnv1.IPAMConfig{
+						Mode: udnv1.IPAMDisabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid config: subnets is unset & excludeSubnet is set",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:           udnv1.NetworkRoleSecondary,
+					ExcludeSubnets: []udnv1.CIDR{"192.168.100.0/31", "192.168.100.10/31"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid config: IPAM disabled and excludeSubnet is set",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:           udnv1.NetworkRoleSecondary,
+					ExcludeSubnets: []udnv1.CIDR{"192.168.100.0/31", "192.168.100.10/31"},
+					IPAM: &udnv1.IPAMConfig{
+						Mode: udnv1.IPAMDisabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: invalid role",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRolePrimary,
+					PhysicalNetworkName: "mylocalnet1",
+					Subnets:             udnv1.DualStackCIDRs{"192.168.100.0/16"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: invalid physicalNetworkName: empty",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRoleSecondary,
+					PhysicalNetworkName: "",
+					Subnets:             udnv1.DualStackCIDRs{"192.168.100.0/16"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: invalid physicalNetworkName: too long",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRoleSecondary,
+					PhysicalNetworkName: "ycx7b6dhkhytzva3wrma0cu6mjhqpo2ty20cmpdg9ptvmt1mo9dfnrs56nr0bvg6z6zha5y208js6e2iwk6xb97sp2sojg48lu9d5vxbzzq40rwj1wchae3ju3dpj6qfsjbzjzmc0k489bloe49z4857kds43rqpeca3p5z2dfz562qu59qqb8qa3vo6pmwuaume581dqhlsz57yvbvgu5hmmmzremac7w7l4rmuirkk91767llw0vskanlc339876",
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go
user: login: ormergi
body: Nice!
Done.
created_at: 2025-04-08T07:31:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,10 +223,151 @@ var _ = Describe("NetAttachDefTemplate", func() {
 			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
 				Topology: udnv1.NetworkTopologyLayer2, Layer3: &udnv1.Layer3Config{}}}},
 		),
+		Entry("CUDN, invalid topology: topology layer2 & localnet config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLayer2, Localnet: &udnv1.LocalnetConfig{}}}},
+		),
 		Entry("CUDN, invalid topology: topology layer3 & layer2 config",
 			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
 				Topology: udnv1.NetworkTopologyLayer3, Layer2: &udnv1.Layer2Config{}}}},
 		),
+		Entry("CUDN, invalid topology: topology layer3 & localnet config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLayer3, Localnet: &udnv1.LocalnetConfig{}}}},
+		),
+		Entry("CUDN, invalid topology: topology localnet & layer2 config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Layer2: &udnv1.Layer2Config{}}}},
+		),
+		Entry("CUDN, invalid topology: topology localnet & layer3 config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Layer3: &udnv1.Layer3Config{}}}},
+		),
+		Entry("CUDN, localnet, invalid IPAM config: IPAM lifecycle & disabled ipam mode",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:    udnv1.NetworkRoleSecondary,
+					Subnets: udnv1.DualStackCIDRs{"192.168.100.0/16"},
+					IPAM: &udnv1.IPAMConfig{
+						Lifecycle: udnv1.IPAMLifecyclePersistent,
+						Mode:      udnv1.IPAMDisabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid IPAM config: IPAM enabled & no subnet",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:    udnv1.NetworkRoleSecondary,
+					Subnets: udnv1.DualStackCIDRs{},
+					IPAM: &udnv1.IPAMConfig{
+						Mode: udnv1.IPAMEnabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid IPAM config: IPAM disabled & subnet",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:    udnv1.NetworkRoleSecondary,
+					Subnets: udnv1.DualStackCIDRs{"192.168.100.0/16"},
+					IPAM: &udnv1.IPAMConfig{
+						Mode: udnv1.IPAMDisabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid config: subnets is unset & excludeSubnet is set",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:           udnv1.NetworkRoleSecondary,
+					ExcludeSubnets: []udnv1.CIDR{"192.168.100.0/31", "192.168.100.10/31"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid config: IPAM disabled and excludeSubnet is set",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:           udnv1.NetworkRoleSecondary,
+					ExcludeSubnets: []udnv1.CIDR{"192.168.100.0/31", "192.168.100.10/31"},
+					IPAM: &udnv1.IPAMConfig{
+						Mode: udnv1.IPAMDisabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: invalid role",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRolePrimary,
+					PhysicalNetworkName: "mylocalnet1",
+					Subnets:             udnv1.DualStackCIDRs{"192.168.100.0/16"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: invalid physicalNetworkName: empty",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRoleSecondary,
+					PhysicalNetworkName: "",
+					Subnets:             udnv1.DualStackCIDRs{"192.168.100.0/16"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: invalid physicalNetworkName: too long",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRoleSecondary,
+					PhysicalNetworkName: "ycx7b6dhkhytzva3wrma0cu6mjhqpo2ty20cmpdg9ptvmt1mo9dfnrs56nr0bvg6z6zha5y208js6e2iwk6xb97sp2sojg48lu9d5vxbzzq40rwj1wchae3ju3dpj6qfsjbzjzmc0k489bloe49z4857kds43rqpeca3p5z2dfz562qu59qqb8qa3vo6pmwuaume581dqhlsz57yvbvgu5hmmmzremac7w7l4rmuirkk91767llw0vskanlc339876",
+					Subnets:             udnv1.DualStackCIDRs{"192.168.100.0/16"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: excludeSubnets not in range of subnets",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{Role: udnv1.NetworkRoleSecondary, PhysicalNetworkName: "localnet1",
+					Subnets:        udnv1.DualStackCIDRs{"192.168.0.0/16", "2001:dbb::/64"},
+					ExcludeSubnets: []udnv1.CIDR{"192.200.0.0/30", "2001:aaa::/127", "192.300.0.1/32", "2001:bbb::1/120"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: VLAN: invalid mode",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Localnet: &udnv1.LocalnetConfig{Role: udnv1.NetworkRoleSecondary, PhysicalNetworkName: "localnet1",
+					VLAN: &udnv1.VLANConfig{Mode: "off"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: VLAN: empty vlan access config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Localnet: &udnv1.LocalnetConfig{Role: udnv1.NetworkRoleSecondary, PhysicalNetworkName: "localnet1",
+					VLAN: &udnv1.VLANConfig{Mode: "Access"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: VLAN: invalid access vlan id - 1",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Localnet: &udnv1.LocalnetConfig{Role: udnv1.NetworkRoleSecondary, PhysicalNetworkName: "localnet1",
+					VLAN: &udnv1.VLANConfig{Access: &udnv1.AccessVLANConfig{ID: 1}},
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go
user: login: ormergi
body: I just realized CI failed due to missing changes in tests following the last CRD change.
I fixed it, CI should pass now :crossed_fingers: 
created_at: 2025-04-08T07:45:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,3 +83,129 @@ type ClusterUserDefinedNetworkList struct {
 	metav1.ListMeta `json:"metadata,omitempty"`
 	Items           []ClusterUserDefinedNetwork `json:"items"`
 }
+
+// +kubebuilder:validation:XValidation:rule="!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode == 'Enabled' ? has(self.subnets) : !has(self.subnets)", message="Subnets is required with ipam.mode is Enabled or unset, and forbidden otherwise"
+// +kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || has(self.subnets)", message="excludeSubnets must be unset when subnets is unset"
+// +kubebuilder:validation:XValidation:rule="!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280", message="MTU should be greater than or equal to 1280 when an IPv6 subnet is used"
+// + ---
+// + TODO: enable the below validation once the following issue is resolved https://github.com/kubernetes/kubernetes/issues/130441
+// + kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || self.excludeSubnets.all(e, self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))",message="excludeSubnets must be subnetworks of the networks specified in the subnets field",fieldPath=".excludeSubnets"
+type LocalnetConfig struct {
+	// role describes the network role in the pod, required.
+	// Controls whether the pod interface will act as primary or secondary.
+	// Localnet topology supports `Secondary` only.
+	// The network will be assigned to pods that have the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+	// to subject.
+	//
+	// +required
+	Role NetworkRole `json:"role"`
+
+	// physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+	// Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+	// In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+	// this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+	//
+	// +kubebuilder:validation:MinLength=1
+	// +kubebuilder:validation:MaxLength=253
+	// +kubebuilder:validation:XValidation:rule="self.matches('^[^,:]+$')", message="physicalNetworkName cannot contain `,` or `:` characters"
+	// +required
+	PhysicalNetworkName string `json:"physicalNetworkName"`
+
+	// subnets is a list of subnets used for pods in this localnet network across the cluster.
+	// The list may be either 1 IPv4 subnet, 1 IPv6 subnet, or 1 of each IP family.
+	// When set, OVN-Kubernetes assigns an IP address from the specified CIDRs to the connected pod,
+	// eliminating the need for manual IP assignment or reliance on an external IPAM service (e.g., a DHCP server).
+	// subnets is optional. When omitted OVN-Kubernetes won't assign IP address automatically.
+	// Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.
+	// The format should match standard CIDR notation (for example, "10.128.0.0/16").
+	// This field must be omitted if `ipam.mode` is `Disabled`.
+	// When physicalNetworkName points to the OVS bridge mapping of a network that provides IPAM services
+	// (e.g., a DHCP server), ipam.mode should be set to Disabled. This turns off OVN-Kubernetes IPAM and avoids
+	// conflicts with the existing IPAM services on this localnet network.
+	//
+	// +optional
+	Subnets DualStackCIDRs `json:"subnets,omitempty"`
+
+	// excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+	// The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+	// excludeSubnets is optional. When omitted no IP address is excluded and all IP addresses specified in `subnets`
+	// are subject to be assignment.
path: go-controller/pkg/crd/userdefinednetwork/v1/cudn.go
user: login: ormergi
body: Done
created_at: 2025-04-08T07:49:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -223,10 +223,151 @@ var _ = Describe("NetAttachDefTemplate", func() {
 			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
 				Topology: udnv1.NetworkTopologyLayer2, Layer3: &udnv1.Layer3Config{}}}},
 		),
+		Entry("CUDN, invalid topology: topology layer2 & localnet config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLayer2, Localnet: &udnv1.LocalnetConfig{}}}},
+		),
 		Entry("CUDN, invalid topology: topology layer3 & layer2 config",
 			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
 				Topology: udnv1.NetworkTopologyLayer3, Layer2: &udnv1.Layer2Config{}}}},
 		),
+		Entry("CUDN, invalid topology: topology layer3 & localnet config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLayer3, Localnet: &udnv1.LocalnetConfig{}}}},
+		),
+		Entry("CUDN, invalid topology: topology localnet & layer2 config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Layer2: &udnv1.Layer2Config{}}}},
+		),
+		Entry("CUDN, invalid topology: topology localnet & layer3 config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Layer3: &udnv1.Layer3Config{}}}},
+		),
+		Entry("CUDN, localnet, invalid IPAM config: IPAM lifecycle & disabled ipam mode",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:    udnv1.NetworkRoleSecondary,
+					Subnets: udnv1.DualStackCIDRs{"192.168.100.0/16"},
+					IPAM: &udnv1.IPAMConfig{
+						Lifecycle: udnv1.IPAMLifecyclePersistent,
+						Mode:      udnv1.IPAMDisabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid IPAM config: IPAM enabled & no subnet",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:    udnv1.NetworkRoleSecondary,
+					Subnets: udnv1.DualStackCIDRs{},
+					IPAM: &udnv1.IPAMConfig{
+						Mode: udnv1.IPAMEnabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid IPAM config: IPAM disabled & subnet",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:    udnv1.NetworkRoleSecondary,
+					Subnets: udnv1.DualStackCIDRs{"192.168.100.0/16"},
+					IPAM: &udnv1.IPAMConfig{
+						Mode: udnv1.IPAMDisabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid config: subnets is unset & excludeSubnet is set",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:           udnv1.NetworkRoleSecondary,
+					ExcludeSubnets: []udnv1.CIDR{"192.168.100.0/31", "192.168.100.10/31"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet, invalid config: IPAM disabled and excludeSubnet is set",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:           udnv1.NetworkRoleSecondary,
+					ExcludeSubnets: []udnv1.CIDR{"192.168.100.0/31", "192.168.100.10/31"},
+					IPAM: &udnv1.IPAMConfig{
+						Mode: udnv1.IPAMDisabled,
+					},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: invalid role",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRolePrimary,
+					PhysicalNetworkName: "mylocalnet1",
+					Subnets:             udnv1.DualStackCIDRs{"192.168.100.0/16"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: invalid physicalNetworkName: empty",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRoleSecondary,
+					PhysicalNetworkName: "",
+					Subnets:             udnv1.DualStackCIDRs{"192.168.100.0/16"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: invalid physicalNetworkName: too long",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{
+					Role:                udnv1.NetworkRoleSecondary,
+					PhysicalNetworkName: "ycx7b6dhkhytzva3wrma0cu6mjhqpo2ty20cmpdg9ptvmt1mo9dfnrs56nr0bvg6z6zha5y208js6e2iwk6xb97sp2sojg48lu9d5vxbzzq40rwj1wchae3ju3dpj6qfsjbzjzmc0k489bloe49z4857kds43rqpeca3p5z2dfz562qu59qqb8qa3vo6pmwuaume581dqhlsz57yvbvgu5hmmmzremac7w7l4rmuirkk91767llw0vskanlc339876",
+					Subnets:             udnv1.DualStackCIDRs{"192.168.100.0/16"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: excludeSubnets not in range of subnets",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet,
+				Localnet: &udnv1.LocalnetConfig{Role: udnv1.NetworkRoleSecondary, PhysicalNetworkName: "localnet1",
+					Subnets:        udnv1.DualStackCIDRs{"192.168.0.0/16", "2001:dbb::/64"},
+					ExcludeSubnets: []udnv1.CIDR{"192.200.0.0/30", "2001:aaa::/127", "192.300.0.1/32", "2001:bbb::1/120"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: VLAN: invalid mode",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Localnet: &udnv1.LocalnetConfig{Role: udnv1.NetworkRoleSecondary, PhysicalNetworkName: "localnet1",
+					VLAN: &udnv1.VLANConfig{Mode: "off"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: VLAN: empty vlan access config",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Localnet: &udnv1.LocalnetConfig{Role: udnv1.NetworkRoleSecondary, PhysicalNetworkName: "localnet1",
+					VLAN: &udnv1.VLANConfig{Mode: "Access"},
+				},
+			}}},
+		),
+		Entry("CUDN, localnet: VLAN: invalid access vlan id - 1",
+			&udnv1.ClusterUserDefinedNetwork{Spec: udnv1.ClusterUserDefinedNetworkSpec{Network: udnv1.NetworkSpec{
+				Topology: udnv1.NetworkTopologyLocalnet, Localnet: &udnv1.LocalnetConfig{Role: udnv1.NetworkRoleSecondary, PhysicalNetworkName: "localnet1",
+					VLAN: &udnv1.VLANConfig{Access: &udnv1.AccessVLANConfig{ID: 1}},
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template_test.go
user: login: ricky-rav
body: Here you're adding unit tests for configurations that are supposed to fail and I see that you want vlanID=1 to fail. 
created_at: 2025-04-08T08:11:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,82 @@
+package cudn
+
+import "github.com/ovn-org/ovn-kubernetes/test/e2e/testdata"
+
+var LocalnetInvalidMTU = []testdata.ValidateCRScenario{
+	{
+		Description: "invalid MTU - higher than 65536",
path: test/e2e/testdata/cudn/invalid-scenarios-localnet-mtu.go
user: login: ricky-rav
body: As I was saying yesterday, I think the correct max is 65535, that is the max value you can express with 2 bytes (2^16-1)
created_at: 2025-04-08T09:55:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,82 @@
+package cudn
+
+import "github.com/ovn-org/ovn-kubernetes/test/e2e/testdata"
+
+var LocalnetInvalidMTU = []testdata.ValidateCRScenario{
+	{
+		Description: "invalid MTU - higher than 65536",
path: test/e2e/testdata/cudn/invalid-scenarios-localnet-mtu.go
user: login: ormergi
body: Good catch, this is my bad, initially I thought kubebuilder markers for minimum & maximum validations are non-inclusive, by they are inclusive so setting the marker to max 65535 is the correct form.
I fixed the markers on the CRD and e2e tests accordingly
created_at: 2025-04-08T13:03:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,3 +84,137 @@ type ClusterUserDefinedNetworkList struct {
 	metav1.ListMeta `json:"metadata,omitempty"`
 	Items           []ClusterUserDefinedNetwork `json:"items"`
 }
+
+const NetworkTopologyLocalnet NetworkTopology = "Localnet"
+
+// +kubebuilder:validation:XValidation:rule="!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode == 'Enabled' ? has(self.subnets) : !has(self.subnets)", message="Subnets is required with ipam.mode is Enabled or unset, and forbidden otherwise"
+// +kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || has(self.subnets)", message="excludeSubnets must be unset when subnets is unset"
+// +kubebuilder:validation:XValidation:rule="!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280", message="MTU should be greater than or equal to 1280 when an IPv6 subnet is used"
+// + ---
+// + TODO: enable the below validation once the following issue is resolved https://github.com/kubernetes/kubernetes/issues/130441
+// + kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || self.excludeSubnets.all(e, self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))",message="excludeSubnets must be subnetworks of the networks specified in the subnets field",fieldPath=".excludeSubnets"
+type LocalnetConfig struct {
+	// role describes the network role in the pod, required.
+	// Controls whether the pod interface will act as primary or secondary.
+	// Localnet topology supports `Secondary` only.
+	// The network will be assigned to pods that have the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+	// to subject.
+	//
+	// +kubebuilder:validation:Enum=Secondary
+	// +required
+	Role NetworkRole `json:"role"`
+
+	// physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+	// Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+	// In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+	// this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+	//
+	// +kubebuilder:validation:MinLength=1
+	// +kubebuilder:validation:MaxLength=253
+	// +kubebuilder:validation:XValidation:rule="self.matches('^[^,:]+$')", message="physicalNetworkName cannot contain `,` or `:` characters"
+	// +required
+	PhysicalNetworkName string `json:"physicalNetworkName"`
+
+	// subnets is a list of subnets used for pods in this localnet network across the cluster.
+	// The list may be either 1 IPv4 subnet, 1 IPv6 subnet, or 1 of each IP family.
+	// When set, OVN-Kubernetes assigns an IP address from the specified CIDRs to the connected pod,
+	// eliminating the need for manual IP assignment or reliance on an external IPAM service (e.g., a DHCP server).
+	// subnets is optional. When omitted OVN-Kubernetes won't assign IP address automatically.
+	// Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.
+	// The format should match standard CIDR notation (for example, "10.128.0.0/16").
+	// This field must be omitted if `ipam.mode` is `Disabled`.
+	// When physicalNetworkName points to the OVS bridge mapping of a network that provides IPAM services
+	// (e.g., a DHCP server), ipam.mode should be set to Disabled. This turns off OVN-Kubernetes IPAM and avoids
+	// conflicts with the existing IPAM services on this localnet network.
+	//
+	// +optional
+	Subnets DualStackCIDRs `json:"subnets,omitempty"`
+
+	// excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+	// The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+	// excludeSubnets is optional. When omitted no IP address is excluded and all IP addresses specified in `subnets`
+	// are subject to assignment.
+	// The format should match standard CIDR notation (for example, "10.128.0.0/16").
+	// This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+	// When `physicalNetworkName` points to OVS bridge mapping of a network with reserved IP addresses
+	// (which shouldn't be assigned by OVN-Kubernetes), the specified CIDRs will not be assigned. For example:
+	// Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+	// to pods: `10.0.0.201`, `10.0.0.202`.
+	//
+	// +optional
+	// +kubebuilder:validation:MinItems=1
+	// +kubebuilder:validation:MaxItems=25
+	ExcludeSubnets []CIDR `json:"excludeSubnets,omitempty"`
+
+	// ipam configurations for the network.
+	// ipam is optional. When omitted, `subnets` must be specified.
+	// When `ipam.mode` is `Disabled`, `subnets` must be omitted.
+	// `ipam.mode` controls how much of the IP configuration will be managed by OVN.
+	//    When `Enabled`, OVN-Kubernetes will apply IP configuration to the SDN infra and assign IPs from the selected
+	//    subnet to the pods.
+	//    When `Disabled`, OVN-Kubernetes only assigns MAC addresses, and provides layer2 communication, and enables users
+	//    to configure IP addresses on the pods.
+	// `ipam.lifecycle` controls IP addresses management lifecycle.
+	//    When set to 'Persistent', the assigned IP addresses will be persisted in `ipamclaims.k8s.cni.cncf.io` object.
+	// 	  Useful for VMs, IP address will be persistent after restarts and migrations. Supported when `ipam.mode` is `Enabled`.
+	//
+	// +optional
+	IPAM *IPAMConfig `json:"ipam,omitempty"`
+
+	// mtu is the maximum transmission unit for a network.
+	// mtu is optional. When omitted, the configured value in OVN-Kubernetes (defaults to 1500 for localnet topology)
+	// is used for the network.
+	// Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+	// Maximum value is 65536.
path: go-controller/pkg/crd/userdefinednetwork/v1/cudn.go
user: login: ricky-rav
body: 65535
created_at: 2025-04-08T17:01:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,3 +84,137 @@ type ClusterUserDefinedNetworkList struct {
 	metav1.ListMeta `json:"metadata,omitempty"`
 	Items           []ClusterUserDefinedNetwork `json:"items"`
 }
+
+const NetworkTopologyLocalnet NetworkTopology = "Localnet"
+
+// +kubebuilder:validation:XValidation:rule="!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode == 'Enabled' ? has(self.subnets) : !has(self.subnets)", message="Subnets is required with ipam.mode is Enabled or unset, and forbidden otherwise"
+// +kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || has(self.subnets)", message="excludeSubnets must be unset when subnets is unset"
+// +kubebuilder:validation:XValidation:rule="!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280", message="MTU should be greater than or equal to 1280 when an IPv6 subnet is used"
+// + ---
+// + TODO: enable the below validation once the following issue is resolved https://github.com/kubernetes/kubernetes/issues/130441
+// + kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || self.excludeSubnets.all(e, self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))",message="excludeSubnets must be subnetworks of the networks specified in the subnets field",fieldPath=".excludeSubnets"
+type LocalnetConfig struct {
+	// role describes the network role in the pod, required.
+	// Controls whether the pod interface will act as primary or secondary.
+	// Localnet topology supports `Secondary` only.
+	// The network will be assigned to pods that have the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+	// to subject.
+	//
+	// +kubebuilder:validation:Enum=Secondary
+	// +required
+	Role NetworkRole `json:"role"`
+
+	// physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+	// Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+	// In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+	// this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+	//
+	// +kubebuilder:validation:MinLength=1
+	// +kubebuilder:validation:MaxLength=253
+	// +kubebuilder:validation:XValidation:rule="self.matches('^[^,:]+$')", message="physicalNetworkName cannot contain `,` or `:` characters"
+	// +required
+	PhysicalNetworkName string `json:"physicalNetworkName"`
+
+	// subnets is a list of subnets used for pods in this localnet network across the cluster.
+	// The list may be either 1 IPv4 subnet, 1 IPv6 subnet, or 1 of each IP family.
+	// When set, OVN-Kubernetes assigns an IP address from the specified CIDRs to the connected pod,
+	// eliminating the need for manual IP assignment or reliance on an external IPAM service (e.g., a DHCP server).
+	// subnets is optional. When omitted OVN-Kubernetes won't assign IP address automatically.
+	// Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.
+	// The format should match standard CIDR notation (for example, "10.128.0.0/16").
+	// This field must be omitted if `ipam.mode` is `Disabled`.
+	// When physicalNetworkName points to the OVS bridge mapping of a network that provides IPAM services
+	// (e.g., a DHCP server), ipam.mode should be set to Disabled. This turns off OVN-Kubernetes IPAM and avoids
+	// conflicts with the existing IPAM services on this localnet network.
+	//
+	// +optional
+	Subnets DualStackCIDRs `json:"subnets,omitempty"`
+
+	// excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+	// The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+	// excludeSubnets is optional. When omitted no IP address is excluded and all IP addresses specified in `subnets`
+	// are subject to assignment.
+	// The format should match standard CIDR notation (for example, "10.128.0.0/16").
+	// This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+	// When `physicalNetworkName` points to OVS bridge mapping of a network with reserved IP addresses
+	// (which shouldn't be assigned by OVN-Kubernetes), the specified CIDRs will not be assigned. For example:
+	// Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+	// to pods: `10.0.0.201`, `10.0.0.202`.
+	//
+	// +optional
+	// +kubebuilder:validation:MinItems=1
+	// +kubebuilder:validation:MaxItems=25
+	ExcludeSubnets []CIDR `json:"excludeSubnets,omitempty"`
+
+	// ipam configurations for the network.
+	// ipam is optional. When omitted, `subnets` must be specified.
+	// When `ipam.mode` is `Disabled`, `subnets` must be omitted.
+	// `ipam.mode` controls how much of the IP configuration will be managed by OVN.
+	//    When `Enabled`, OVN-Kubernetes will apply IP configuration to the SDN infra and assign IPs from the selected
+	//    subnet to the pods.
+	//    When `Disabled`, OVN-Kubernetes only assigns MAC addresses, and provides layer2 communication, and enables users
+	//    to configure IP addresses on the pods.
+	// `ipam.lifecycle` controls IP addresses management lifecycle.
+	//    When set to 'Persistent', the assigned IP addresses will be persisted in `ipamclaims.k8s.cni.cncf.io` object.
+	// 	  Useful for VMs, IP address will be persistent after restarts and migrations. Supported when `ipam.mode` is `Enabled`.
+	//
+	// +optional
+	IPAM *IPAMConfig `json:"ipam,omitempty"`
+
+	// mtu is the maximum transmission unit for a network.
+	// mtu is optional. When omitted, the configured value in OVN-Kubernetes (defaults to 1500 for localnet topology)
+	// is used for the network.
+	// Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+	// Maximum value is 65536.
path: go-controller/pkg/crd/userdefinednetwork/v1/cudn.go
user: login: ormergi
body: Hi, sorry for going back and forward on this, I think the max value validation should be 65536.
This is how the MTU validation are for Layer2 and Layer3 typologies, and also OCP clusterNetwork https://github.com/openshift/api/blob/master/network/v1/types.go#L62-L67
created_at: 2025-04-09T07:46:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -77,3 +84,137 @@ type ClusterUserDefinedNetworkList struct {
 	metav1.ListMeta `json:"metadata,omitempty"`
 	Items           []ClusterUserDefinedNetwork `json:"items"`
 }
+
+const NetworkTopologyLocalnet NetworkTopology = "Localnet"
+
+// +kubebuilder:validation:XValidation:rule="!has(self.ipam) || !has(self.ipam.mode) || self.ipam.mode == 'Enabled' ? has(self.subnets) : !has(self.subnets)", message="Subnets is required with ipam.mode is Enabled or unset, and forbidden otherwise"
+// +kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || has(self.subnets)", message="excludeSubnets must be unset when subnets is unset"
+// +kubebuilder:validation:XValidation:rule="!has(self.subnets) || !has(self.mtu) || !self.subnets.exists_one(i, isCIDR(i) && cidr(i).ip().family() == 6) || self.mtu >= 1280", message="MTU should be greater than or equal to 1280 when an IPv6 subnet is used"
+// + ---
+// + TODO: enable the below validation once the following issue is resolved https://github.com/kubernetes/kubernetes/issues/130441
+// + kubebuilder:validation:XValidation:rule="!has(self.excludeSubnets) || self.excludeSubnets.all(e, self.subnets.exists(s, cidr(s).containsCIDR(cidr(e))))",message="excludeSubnets must be subnetworks of the networks specified in the subnets field",fieldPath=".excludeSubnets"
+type LocalnetConfig struct {
+	// role describes the network role in the pod, required.
+	// Controls whether the pod interface will act as primary or secondary.
+	// Localnet topology supports `Secondary` only.
+	// The network will be assigned to pods that have the `k8s.v1.cni.cncf.io/networks` annotation in place pointing
+	// to subject.
+	//
+	// +kubebuilder:validation:Enum=Secondary
+	// +required
+	Role NetworkRole `json:"role"`
+
+	// physicalNetworkName points to the OVS bridge-mapping's network-name configured in the nodes, required.
+	// Min length is 1, max length is 253, cannot contain `,` or `:` characters.
+	// In case OVS bridge-mapping is defined by Kubernetes-nmstate with `NodeNetworkConfigurationPolicy` (NNCP),
+	// this field should point to the NNCP `spec.desiredState.ovn.bridge-mappings` item's `localnet` value.
+	//
+	// +kubebuilder:validation:MinLength=1
+	// +kubebuilder:validation:MaxLength=253
+	// +kubebuilder:validation:XValidation:rule="self.matches('^[^,:]+$')", message="physicalNetworkName cannot contain `,` or `:` characters"
+	// +required
+	PhysicalNetworkName string `json:"physicalNetworkName"`
+
+	// subnets is a list of subnets used for pods in this localnet network across the cluster.
+	// The list may be either 1 IPv4 subnet, 1 IPv6 subnet, or 1 of each IP family.
+	// When set, OVN-Kubernetes assigns an IP address from the specified CIDRs to the connected pod,
+	// eliminating the need for manual IP assignment or reliance on an external IPAM service (e.g., a DHCP server).
+	// subnets is optional. When omitted OVN-Kubernetes won't assign IP address automatically.
+	// Dual-stack clusters may set 2 subnets (one for each IP family), otherwise only 1 subnet is allowed.
+	// The format should match standard CIDR notation (for example, "10.128.0.0/16").
+	// This field must be omitted if `ipam.mode` is `Disabled`.
+	// When physicalNetworkName points to the OVS bridge mapping of a network that provides IPAM services
+	// (e.g., a DHCP server), ipam.mode should be set to Disabled. This turns off OVN-Kubernetes IPAM and avoids
+	// conflicts with the existing IPAM services on this localnet network.
+	//
+	// +optional
+	Subnets DualStackCIDRs `json:"subnets,omitempty"`
+
+	// excludeSubnets is a list of CIDRs to be removed from the specified CIDRs in `subnets`.
+	// The CIDRs in this list must be in range of at least one subnet specified in `subnets`.
+	// excludeSubnets is optional. When omitted no IP address is excluded and all IP addresses specified in `subnets`
+	// are subject to assignment.
+	// The format should match standard CIDR notation (for example, "10.128.0.0/16").
+	// This field must be omitted if `subnets` is unset or `ipam.mode` is `Disabled`.
+	// When `physicalNetworkName` points to OVS bridge mapping of a network with reserved IP addresses
+	// (which shouldn't be assigned by OVN-Kubernetes), the specified CIDRs will not be assigned. For example:
+	// Given: `subnets: "10.0.0.0/24"`, `excludeSubnets: "10.0.0.200/30", the following addresses will not be assigned
+	// to pods: `10.0.0.201`, `10.0.0.202`.
+	//
+	// +optional
+	// +kubebuilder:validation:MinItems=1
+	// +kubebuilder:validation:MaxItems=25
+	ExcludeSubnets []CIDR `json:"excludeSubnets,omitempty"`
+
+	// ipam configurations for the network.
+	// ipam is optional. When omitted, `subnets` must be specified.
+	// When `ipam.mode` is `Disabled`, `subnets` must be omitted.
+	// `ipam.mode` controls how much of the IP configuration will be managed by OVN.
+	//    When `Enabled`, OVN-Kubernetes will apply IP configuration to the SDN infra and assign IPs from the selected
+	//    subnet to the pods.
+	//    When `Disabled`, OVN-Kubernetes only assigns MAC addresses, and provides layer2 communication, and enables users
+	//    to configure IP addresses on the pods.
+	// `ipam.lifecycle` controls IP addresses management lifecycle.
+	//    When set to 'Persistent', the assigned IP addresses will be persisted in `ipamclaims.k8s.cni.cncf.io` object.
+	// 	  Useful for VMs, IP address will be persistent after restarts and migrations. Supported when `ipam.mode` is `Enabled`.
+	//
+	// +optional
+	IPAM *IPAMConfig `json:"ipam,omitempty"`
+
+	// mtu is the maximum transmission unit for a network.
+	// mtu is optional. When omitted, the configured value in OVN-Kubernetes (defaults to 1500 for localnet topology)
+	// is used for the network.
+	// Minimum value for IPv4 subnet is 576, and for IPv6 subnet is 1280.
+	// Maximum value is 65536.
path: go-controller/pkg/crd/userdefinednetwork/v1/cudn.go
user: login: ricky-rav
body: uhm ok. I'm still very perplexed about 65536 being our max, but let's be coherent with what's in openshift/api then.
created_at: 2025-04-09T08:50:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -185,10 +206,43 @@ func renderCNINetworkConfig(networkName, nadName string, spec SpecGetter) (map[s
 	if netConfSpec.AllowPersistentIPs {
 		cniNetConf["allowPersistentIPs"] = netConfSpec.AllowPersistentIPs
 	}
-
+	if netConfSpec.PhysicalNetworkName != "" {
+		cniNetConf["physicalNetworkName"] = netConfSpec.PhysicalNetworkName
+	}
+	if len(netConfSpec.ExcludeSubnets) > 0 {
+		cniNetConf["excludeSubnets"] = netConfSpec.ExcludeSubnets
+	}
+	if netConfSpec.VLANID != 0 {
+		cniNetConf["vlanID"] = netConfSpec.VLANID
+	}
 	return cniNetConf, nil
 }
 
+func validateLocalnetConfig(cfg userdefinednetworkv1.LocalnetConfig) error {
+	if cfg.Role == userdefinednetworkv1.NetworkRolePrimary {
+		return fmt.Errorf("role can be secondary only")
+	}
+	if cfg.PhysicalNetworkName == "" {
+		return fmt.Errorf("physicalNetworkName cannot be empty")
+	}
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go
user: login: trozet
body: why dont we validate this in CEL instead of here?
created_at: 2025-04-15T21:02:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -185,10 +206,43 @@ func renderCNINetworkConfig(networkName, nadName string, spec SpecGetter) (map[s
 	if netConfSpec.AllowPersistentIPs {
 		cniNetConf["allowPersistentIPs"] = netConfSpec.AllowPersistentIPs
 	}
-
+	if netConfSpec.PhysicalNetworkName != "" {
+		cniNetConf["physicalNetworkName"] = netConfSpec.PhysicalNetworkName
+	}
+	if len(netConfSpec.ExcludeSubnets) > 0 {
+		cniNetConf["excludeSubnets"] = netConfSpec.ExcludeSubnets
+	}
+	if netConfSpec.VLANID != 0 {
+		cniNetConf["vlanID"] = netConfSpec.VLANID
+	}
 	return cniNetConf, nil
 }
 
+func validateLocalnetConfig(cfg userdefinednetworkv1.LocalnetConfig) error {
+	if cfg.Role == userdefinednetworkv1.NetworkRolePrimary {
+		return fmt.Errorf("role can be secondary only")
+	}
+	if cfg.PhysicalNetworkName == "" {
+		return fmt.Errorf("physicalNetworkName cannot be empty")
+	}
+	if len(cfg.PhysicalNetworkName) > 253 {
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go
user: login: trozet
body: same thing for this
created_at: 2025-04-15T21:02:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -203,6 +257,22 @@ func validateIPAM(ipam *userdefinednetworkv1.IPAMConfig) error {
 	return nil
 }
 
+func validateVLANConfig(vlanConfig *userdefinednetworkv1.VLANConfig) error {
+	if vlanConfig == nil {
+		return nil
+	}
+	if vlanConfig.Mode != userdefinednetworkv1.VLANModeAccess {
+		return fmt.Errorf("vlan.mode support 'Access' mode only")
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go
user: login: trozet
body: nit, maybe better error message is: invalid vlan.mode value. Only current acceptable value is 'Access'
created_at: 2025-04-15T21:12:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -203,6 +257,22 @@ func validateIPAM(ipam *userdefinednetworkv1.IPAMConfig) error {
 	return nil
 }
 
+func validateVLANConfig(vlanConfig *userdefinednetworkv1.VLANConfig) error {
+	if vlanConfig == nil {
+		return nil
+	}
+	if vlanConfig.Mode != userdefinednetworkv1.VLANModeAccess {
+		return fmt.Errorf("vlan.mode support 'Access' mode only")
+	}
+	if vlanConfig.Access == nil {
+		return fmt.Errorf("when vlan.mode is 'Access', vlan.access is required")
+	}
+	if vlanConfig.Access.ID <= 0 || vlanConfig.Access.ID >= 4095 {
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go
user: login: trozet
body: CEL validation on this one?
created_at: 2025-04-15T21:12:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -243,6 +243,17 @@ func validateLocalnetConfig(cfg userdefinednetworkv1.LocalnetConfig) error {
 	return nil
 }
 
+func localnetMTU(desiredMTU int32) int {
+	// The MTU for localnet topology should be as the default MTU (1500) because the underlay
+	// is not part of the SDN and compensating for the SDN overhead is not required.
+	mtu := 1500
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go
user: login: trozet
body: I think a better default value would be config.Default.MTU + 100
created_at: 2025-04-15T21:15:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -185,10 +206,43 @@ func renderCNINetworkConfig(networkName, nadName string, spec SpecGetter) (map[s
 	if netConfSpec.AllowPersistentIPs {
 		cniNetConf["allowPersistentIPs"] = netConfSpec.AllowPersistentIPs
 	}
-
+	if netConfSpec.PhysicalNetworkName != "" {
+		cniNetConf["physicalNetworkName"] = netConfSpec.PhysicalNetworkName
+	}
+	if len(netConfSpec.ExcludeSubnets) > 0 {
+		cniNetConf["excludeSubnets"] = netConfSpec.ExcludeSubnets
+	}
+	if netConfSpec.VLANID != 0 {
+		cniNetConf["vlanID"] = netConfSpec.VLANID
+	}
 	return cniNetConf, nil
 }
 
+func validateLocalnetConfig(cfg userdefinednetworkv1.LocalnetConfig) error {
+	if cfg.Role == userdefinednetworkv1.NetworkRolePrimary {
+		return fmt.Errorf("role can be secondary only")
+	}
+	if cfg.PhysicalNetworkName == "" {
+		return fmt.Errorf("physicalNetworkName cannot be empty")
+	}
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go
user: login: ormergi
body: We do have CEL validations for it at the API level https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5010/files#diff-073a0bb9fa1b183caf787b824a1bddd130ec004579ff07082596b7426d4c9b8cR111-R116 
I added similar validations on the controller side for completeness, and to prevent form creating NAD with invalid spec in case kube-apiserver validation service is down, although its not likely I think its possible.
Make sense? 

created_at: 2025-04-16T09:18:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -185,10 +206,43 @@ func renderCNINetworkConfig(networkName, nadName string, spec SpecGetter) (map[s
 	if netConfSpec.AllowPersistentIPs {
 		cniNetConf["allowPersistentIPs"] = netConfSpec.AllowPersistentIPs
 	}
-
+	if netConfSpec.PhysicalNetworkName != "" {
+		cniNetConf["physicalNetworkName"] = netConfSpec.PhysicalNetworkName
+	}
+	if len(netConfSpec.ExcludeSubnets) > 0 {
+		cniNetConf["excludeSubnets"] = netConfSpec.ExcludeSubnets
+	}
+	if netConfSpec.VLANID != 0 {
+		cniNetConf["vlanID"] = netConfSpec.VLANID
+	}
 	return cniNetConf, nil
 }
 
+func validateLocalnetConfig(cfg userdefinednetworkv1.LocalnetConfig) error {
+	if cfg.Role == userdefinednetworkv1.NetworkRolePrimary {
+		return fmt.Errorf("role can be secondary only")
+	}
+	if cfg.PhysicalNetworkName == "" {
+		return fmt.Errorf("physicalNetworkName cannot be empty")
+	}
+	if len(cfg.PhysicalNetworkName) > 253 {
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go
user: login: ormergi
body: Please see https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5010/files/082457cd9d6bf3065da210aff8ea2f643b0b4af2..5162882165cb2379c9f36d9a20cac5f679380e47#r2046484060
created_at: 2025-04-16T09:18:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -203,6 +257,22 @@ func validateIPAM(ipam *userdefinednetworkv1.IPAMConfig) error {
 	return nil
 }
 
+func validateVLANConfig(vlanConfig *userdefinednetworkv1.VLANConfig) error {
+	if vlanConfig == nil {
+		return nil
+	}
+	if vlanConfig.Mode != userdefinednetworkv1.VLANModeAccess {
+		return fmt.Errorf("vlan.mode support 'Access' mode only")
+	}
+	if vlanConfig.Access == nil {
+		return fmt.Errorf("when vlan.mode is 'Access', vlan.access is required")
+	}
+	if vlanConfig.Access.ID <= 0 || vlanConfig.Access.ID >= 4095 {
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go
user: login: ormergi
body: CEL validation for VLAN ID https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5010/files#diff-073a0bb9fa1b183caf787b824a1bddd130ec004579ff07082596b7426d4c9b8cR192-R198

Please see https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5010/files/082457cd9d6bf3065da210aff8ea2f643b0b4af2..5162882165cb2379c9f36d9a20cac5f679380e47#r2046484060
created_at: 2025-04-16T09:20:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -203,6 +257,22 @@ func validateIPAM(ipam *userdefinednetworkv1.IPAMConfig) error {
 	return nil
 }
 
+func validateVLANConfig(vlanConfig *userdefinednetworkv1.VLANConfig) error {
+	if vlanConfig == nil {
+		return nil
+	}
+	if vlanConfig.Mode != userdefinednetworkv1.VLANModeAccess {
+		return fmt.Errorf("vlan.mode support 'Access' mode only")
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go
user: login: ormergi
body: Done
created_at: 2025-04-16T09:25:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -243,6 +243,17 @@ func validateLocalnetConfig(cfg userdefinednetworkv1.LocalnetConfig) error {
 	return nil
 }
 
+func localnetMTU(desiredMTU int32) int {
+	// The MTU for localnet topology should be as the default MTU (1500) because the underlay
+	// is not part of the SDN and compensating for the SDN overhead is not required.
+	mtu := 1500
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go
user: login: ormergi
body: Done
created_at: 2025-04-16T09:47:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -185,10 +206,43 @@ func renderCNINetworkConfig(networkName, nadName string, spec SpecGetter) (map[s
 	if netConfSpec.AllowPersistentIPs {
 		cniNetConf["allowPersistentIPs"] = netConfSpec.AllowPersistentIPs
 	}
-
+	if netConfSpec.PhysicalNetworkName != "" {
+		cniNetConf["physicalNetworkName"] = netConfSpec.PhysicalNetworkName
+	}
+	if len(netConfSpec.ExcludeSubnets) > 0 {
+		cniNetConf["excludeSubnets"] = netConfSpec.ExcludeSubnets
+	}
+	if netConfSpec.VLANID != 0 {
+		cniNetConf["vlanID"] = netConfSpec.VLANID
+	}
 	return cniNetConf, nil
 }
 
+func validateLocalnetConfig(cfg userdefinednetworkv1.LocalnetConfig) error {
+	if cfg.Role == userdefinednetworkv1.NetworkRolePrimary {
+		return fmt.Errorf("role can be secondary only")
+	}
+	if cfg.PhysicalNetworkName == "" {
+		return fmt.Errorf("physicalNetworkName cannot be empty")
+	}
path: go-controller/pkg/clustermanager/userdefinednetwork/template/net-attach-def-template.go
user: login: trozet
body: yeah i dont think it is possible
created_at: 2025-04-16T12:58:41Z
author_association: CONTRIBUTOR

PR comments:
user: login: ormergi
created_at: 2025-02-27T17:35:37Z
body: The failing jobs "control-plane" and "network-segmentation" failed due to known issue tracked at https://github.com/ovn-kubernetes/ovn-kubernetes/issues/4144 
author_association: CONTRIBUTOR
user: login: ormergi
created_at: 2025-03-05T00:09:53Z
body: [Changes](https://github.com/ovn-kubernetes/ovn-kubernetes/compare/16167919d21e63d99191ee1ff696c82a6ae697a1..47204e6bf211bdafc1ce7e9abbc1ff131b64bf08): resolve https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5010#pullrequestreview-2651030547
author_association: CONTRIBUTOR
user: login: ormergi
created_at: 2025-03-17T15:42:44Z
body: [Changes](https://github.com/ovn-kubernetes/ovn-kubernetes/compare/47204e6bf211bdafc1ce7e9abbc1ff131b64bf08..05b41b6517d94fb0dac54a8f499b3d2d45761913): rebase

author_association: CONTRIBUTOR
user: login: ormergi
created_at: 2025-03-20T09:39:11Z
body: [rebase](https://github.com/ovn-kubernetes/ovn-kubernetes/compare/05b41b6517d94fb0dac54a8f499b3d2d45761913..281a5028371f9f7e5e5cc80f305dbc7b702d47ca)
author_association: CONTRIBUTOR
user: login: ormergi
created_at: 2025-03-20T11:53:06Z
body: [Changes](https://github.com/ovn-kubernetes/ovn-kubernetes/compare/281a5028371f9f7e5e5cc80f305dbc7b702d47ca..35852fa4679cade3b5fd8591afefcc6352cac89f): 
- Drop e2e test following discussion at https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5010#discussion_r2003510731
- Update the VLAN part in the API to follow discriminated-union enable the API evolve and support additional modes in the future (e.g.: trunk)
author_association: CONTRIBUTOR
user: login: ormergi
created_at: 2025-03-24T08:36:47Z
body: [force-push1](https://github.com/ovn-kubernetes/ovn-kubernetes/compare/c563997b53a4ee4b25ce7f83e717f255b7503583..48493003feb6ca9c6c5a2bae4b8290f237646e81): add description for API validation e2e tests improve debugging
[force-push2](https://github.com/ovn-kubernetes/ovn-kubernetes/compare/48493003feb6ca9c6c5a2bae4b8290f237646e81..d43470deaefcb39c23a0ce775f0d143c240f5cd2): update crd according to latest design 
author_association: CONTRIBUTOR
user: login: ormergi
created_at: 2025-03-24T08:36:57Z
body: @npinaeva PTAL when you can :pray: 
author_association: CONTRIBUTOR
user: login: ormergi
created_at: 2025-04-07T13:17:39Z
body: [Rebase ](https://github.com/ovn-kubernetes/ovn-kubernetes/compare/d43470deaefcb39c23a0ce775f0d143c240f5cd2..a1a6e8b8960b80589cb27bc2fc53e19a5fba221a)
author_association: CONTRIBUTOR
user: login: ormergi
created_at: 2025-04-07T14:24:20Z
body: [Changes](https://github.com/ovn-kubernetes/ovn-kubernetes/compare/a1a6e8b8960b80589cb27bc2fc53e19a5fba221a..3d1ae099beada94c6f9fb39171e502defe8fd04d): resolve https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5010#pullrequestreview-2743655551
@ricky-rav PTAL

author_association: CONTRIBUTOR
user: login: coveralls
created_at: 2025-04-07T17:23:54Z
body: ## Pull Request Test Coverage Report for [Build 14361305400](https://coveralls.io/builds/73150065)


### Details

* **77** of **77**   **(100.0%)**  changed or added relevant lines in **1** file are covered.
* **8** unchanged lines in **3** files lost coverage.
* Overall coverage increased (+**0.07%**) to **54.998%**

---


|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [go-controller/pkg/clustermanager/zone_cluster_controller.go](https://coveralls.io/builds/73150065/source?filename=go-controller%2Fpkg%2Fclustermanager%2Fzone_cluster_controller.go#L145) | 2 | 70.61% |
| [go-controller/pkg/ovn/base_network_controller_namespace.go](https://coveralls.io/builds/73150065/source?filename=go-controller%2Fpkg%2Fovn%2Fbase_network_controller_namespace.go#L409) | 2 | 74.68% |
| [go-controller/pkg/ovn/hybrid.go](https://coveralls.io/builds/73150065/source?filename=go-controller%2Fpkg%2Fovn%2Fhybrid.go#L216) | 4 | 79.04% |
<!-- | **Total:** | **8** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/73150065/badge)](https://coveralls.io/builds/73150065) |
| :-- | --: |
| Change from base [Build 14346259987](https://coveralls.io/builds/73137152): |  0.07% |
| Covered Lines: | 37723 |
| Relevant Lines: | 68590 |

---
##### 💛  - [Coveralls](https://coveralls.io)

author_association: NONE
user: login: ormergi
created_at: 2025-04-08T07:52:22Z
body: [force-push1](https://github.com/ovn-kubernetes/ovn-kubernetes/compare/3d1ae099beada94c6f9fb39171e502defe8fd04d..388437ad31b3d20d1d099a29cae041918e57b31b): resolve https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5010#discussion_r2031592698 https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5010#discussion_r2031610033 https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5010#discussion_r2032599515
[force-push2](https://github.com/ovn-kubernetes/ovn-kubernetes/compare/388437ad31b3d20d1d099a29cae041918e57b31b..50b07857a23c0b43b1e144fa1e1470fa8525c9a2): resolve https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5010#discussion_r2031712562
@ricky-rav PTAL

author_association: CONTRIBUTOR
user: login: ricky-rav
created_at: 2025-04-08T10:11:08Z
body: One more thing. Here we're introducing the API, unit tests and e2e tests that validate it. Do we need also a simple e2e test showcasing that from a localnet CUDN CR we actually get a functional localnet secondary network?
author_association: CONTRIBUTOR
user: login: ormergi
created_at: 2025-04-08T13:25:52Z
body: [Changes](https://github.com/ovn-kubernetes/ovn-kubernetes/compare/50b07857a23c0b43b1e144fa1e1470fa8525c9a2..56fc2cb7ca342b4f9df5b8c903bce7906661de8b): resolve https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5010#pullrequestreview-2749309879: 
- Change kubebuilder validations to max mtu 65535, and the relevant e2e tests.
- Fix unit test to validate the desired scenario, previously the given spec was missing require fields causing the test to fail for the wrong reason. 
@ricky-rav PTAL
author_association: CONTRIBUTOR
user: login: ormergi
created_at: 2025-04-08T13:30:13Z
body: > One more thing. Here we're introducing the API, unit tests and e2e tests that validate it. Do we need also a simple e2e test showcasing that from a localnet CUDN CR we actually get a functional localnet secondary network?

@ricky-rav initially this PR introduced  such representative test, although following previous review we realized its not really necessary, because generating the NAD for localnet is covered by unit-tests, and the CRD controller functionality is agnostic to the topology settings.
Please see https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5010#discussion_r1978993506 and let me know what you think :pray:  

author_association: CONTRIBUTOR
user: login: ormergi
created_at: 2025-04-09T07:55:55Z
body: [Changes](https://github.com/ovn-kubernetes/ovn-kubernetes/compare/56fc2cb7ca342b4f9df5b8c903bce7906661de8b..efc3569897d05495e1b2c6241ea3321993143328): revert MTU max value validation https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5010#discussion_r2034705231
@ricky-rav PTAL

author_association: CONTRIBUTOR
user: login: ormergi
created_at: 2025-04-09T11:05:38Z
body: @ricky-rav I pushed new commit to resolve https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5010#issuecomment-2785930384, PTAL :pray: 
author_association: CONTRIBUTOR
user: login: ricky-rav
created_at: 2025-04-09T14:15:53Z
body: The e2e test looks ok to me. Given that we don't have much time left, I'm fine with just showcasing basic functionality and leaving more advanced use cases to the existing e2e tests with NADs. 
Should we also test `ipam`, `excludeSubnets`?
author_association: CONTRIBUTOR
user: login: ormergi
created_at: 2025-04-09T15:46:32Z
body: @ricky-rav I pushed new changes [diff](https://github.com/ovn-kubernetes/ovn-kubernetes/compare/9496722574b10715df247ab30209a6a46d6b2f44..b1e9d2470b5728ebd1a177af05d094e4137687bd)  to add coverage for `excludeSubnets`, similar check as in multi-homing localnet tests 
https://github.com/ovn-kubernetes/ovn-kubernetes/blob/master/test/e2e/multihoming.go#L103

Regarding IPAM settings affects IP assignment and similar to other settings and covered by unit tests.
Adding e2e for it as part of network-segmentation lane require deploying additional components (IPAMClaim controller).
Its is covered in kubevirt lane https://github.com/ovn-kubernetes/ovn-kubernetes/commit/c77ee8c38c6a6d9e55131a1272db5fad5b606e44 
WDYT?
author_association: CONTRIBUTOR
user: login: ormergi
created_at: 2025-04-10T08:10:45Z
body: @trozet could you please take a look? 
author_association: CONTRIBUTOR
user: login: ormergi
created_at: 2025-04-16T09:49:26Z
body: [Changes](https://github.com/ovn-kubernetes/ovn-kubernetes/compare/b1e9d2470b5728ebd1a177af05d094e4137687bd..a75e9c3e8436d37a763d04f9d4066d370c67f2b6):  address https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5010#pullrequestreview-2769834719
@trozet PTAL :pray: 
author_association: CONTRIBUTOR
user: login: trozet
created_at: 2025-04-16T13:00:10Z
body: i think just remove the extra validations you are doing in the code and rely on CEL, and then we are good to go. I think having them in the code just adds confusion.
author_association: CONTRIBUTOR
user: login: ormergi
created_at: 2025-04-16T16:20:21Z
body: [Changes](https://github.com/ovn-kubernetes/ovn-kubernetes/compare/a75e9c3e8436d37a763d04f9d4066d370c67f2b6..314f483cf1360a73a380b855e51127eac12ad2dd): resolve https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5010#issuecomment-2809517118 
Remove validation on controller side that already exist on API side (using CEL validations on the CRD).
Add missing e2e API validations tests

With this approach, relaying on API validations only, we can remove more validation logic of layer2 & layer3 typologies  from the controller code later on.
author_association: CONTRIBUTOR
user: login: ormergi
created_at: 2025-04-16T17:44:05Z
body: upgrade lanes fail due to issue tracked by https://github.com/ovn-kubernetes/ovn-kubernetes/issues/5183
shard-conformance ipv6 failed due to issues tracked by https://github.com/ovn-kubernetes/ovn-kubernetes/issues/5170
author_association: CONTRIBUTOR
user: login: npinaeva
created_at: 2025-04-17T12:53:20Z
body: great job @ormergi ! thank you for this effort!
author_association: CONTRIBUTOR