---
northd/northd.c | 271 +++++++++++++++++++++++++++++++++++++++-
northd/northd.h | 41 +++---
northd/ovn-northd.8.xml | 211 +++++++++++++++++++++++++++----
ovn-nb.ovsschema | 21 +++-
ovn-nb.xml | 39 ++++++
tests/atlocal.in | 3 +
tests/ovn-northd.at | 38 ++++++
tests/ovn.at | 224 ++++++++++++++++++++++++++++++++-
tests/system-ovn.at | 148 ++++++++++++++++++++++
9 files changed, 948 insertions(+), 48 deletions(-)
diff --git a/northd/northd.c b/northd/northd.c
index 331d9c267..045c3576b 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -91,7 +91,6 @@ static bool use_ct_inv_match = true;
static bool default_acl_drop;
#define MAX_OVN_TAGS 4096
-
/* Due to various hard-coded priorities need to implement ACLs, the
* northbound database supports a smaller range of ACL priorities than
@@ -153,6 +152,8 @@ static bool default_acl_drop;
#define REGBIT_LOOKUP_NEIGHBOR_IP_RESULT "reg9[3]"
#define REGBIT_DST_NAT_IP_LOCAL "reg9[4]"
#define REGBIT_KNOWN_LB_SESSION "reg9[6]"
+#define REGBIT_DHCP_RELAY_REQ_CHK "reg9[7]"
+#define REGBIT_DHCP_RELAY_RESP_CHK "reg9[8]"
/* Register to store the eth address associated to a router port for packets
* received in S_ROUTER_IN_ADMISSION.
@@ -168,6 +169,7 @@ static bool default_acl_drop;
#define REG_NEXT_HOP_IPV6 "xxreg0"
#define REG_SRC_IPV4 "reg1"
#define REG_SRC_IPV6 "xxreg1"
+#define REG_DHCP_RELAY_DIP_IPV4 "reg2"
#define REG_ROUTE_TABLE_ID "reg7"
/* Register used to store backend ipv6 address
@@ -232,7 +234,7 @@ static bool default_acl_drop;
* | R1 | SRC_IPV4 for ARP-REQ | 0 | | R | |
* | | (>= IP_INPUT) | | | E | NEXT_HOP_IPV6 (>= DEFRAG ) |
* +-----+---------------------------+---+-----------------+ G | |
- * | R2 | UNUSED | X | | 0 | |
+ * | R2 REG_DHCP_RELAY_DIP_IPV4 | X | | 0 | |
* | | | R | | | |
* +-----+---------------------------+ E | UNUSED | | |
* | R3 | UNUSED | G | | | |
@@ -259,7 +261,9 @@ static bool default_acl_drop;
* | | EGRESS_LOOPBACK/ | G | UNUSED |
* | R9 | PKT_LARGER/ | 4 | |
* | | LOOKUP_NEIGHBOR_RESULT/ | | |
- * | | SKIP_LOOKUP_NEIGHBOR} | | |
+ * | | SKIP_LOOKUP_NEIGHBOR/ | | |
+ * | |REGBIT_DHCP_RELAY_REQ_CHK/ | | |
+ * | |REGBIT_DHCP_RELAY_RESP_CHK}| | |
* | | | | |
* | | REG_ORIG_TP_DPORT_ROUTER | | |
* | | | | |
@@ -8543,6 +8547,90 @@ build_dhcpv6_options_flows(struct ovn_port *op,
ds_destroy(&match);
}
+static const char *
+ls_dhcp_relay_port(const struct ovn_datapath *od)
+{
+ return smap_get(&od->nbs->other_config, "dhcp_relay_port");
+}
+
+static void
+build_lswitch_dhcp_relay_flows(struct ovn_port *op,
+ const struct hmap *ls_ports,
+ struct lflow_table *lflows,
+ struct ds *match,
+ struct ds *actions)
+{
+ if (op->nbrp || !op->nbsp) {
+ return;
+ }
+
+ /* consider only ports attached to VMs */
+ if (strcmp(op->nbsp->type, "")) {
+ return;
+ }
+
+ if (!op->od || !op->od->n_router_ports ||
+ !op->od->nbs) {
+ return;
+ }
+
+ /* configure dhcp relay flows only when peer router has
+ * relay config enabled */
+ const char *dhcp_relay_port = ls_dhcp_relay_port(op->od);
+ if (!dhcp_relay_port) {
+ return;
+ }
+
+ struct ovn_port *sp = ovn_port_find(ls_ports, dhcp_relay_port);
+
+ if (!sp || !sp->nbsp || !sp->peer) {
+ return;
+ }
+
+ struct ovn_port *rp = sp->peer;
+ if (!rp || !rp->nbrp || !rp->nbrp->dhcp_relay || rp->peer != sp) {
+ return;
+ }
+
+ char *server_ip_str = NULL;
+ uint16_t port;
+ int addr_family;
+ struct in6_addr server_ip;
+ struct nbrec_dhcp_relay *dhcp_relay = rp->nbrp->dhcp_relay;
+
+ if (!ip_address_and_port_from_lb_key(dhcp_relay->servers, &server_ip_str,
+ &server_ip, &port, &addr_family)) {
+ return;
+ }
+
+ if (server_ip_str == NULL) {
+ return;
+ }
+
+ ds_clear(match);
+ ds_clear(actions);
+
+ ds_put_format(
+ match, "inport == %s && eth.src == %s && "
+ "ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && "
+ "udp.src == 68 && udp.dst == 67",
+ op->json_key, op->lsp_addrs[0].ea_s);
+ ds_put_format(actions,
+ "eth.dst=%s;outport=%s;next;/* DHCP_RELAY_REQ */",
+ rp->lrp_networks.ea_s,sp->json_key);
+ ovn_lflow_add_with_hint__(lflows, op->od,
+ S_SWITCH_IN_L2_LKUP, 100,
+ ds_cstr(match),
+ ds_cstr(actions),
+ op->key,
+ NULL,
+ &op->nbsp->header_,
+ op->lflow_ref);
+ ds_clear(match);
+ ds_clear(actions);
+ free(server_ip_str);
+}
+
static void
build_drop_arp_nd_flows_for_unbound_router_ports(struct ovn_port *op,
const struct ovn_port *port,
@@ -9144,6 +9232,13 @@ build_lswitch_dhcp_options_and_response(struct ovn_port *op,
return;
}
+ if (op->od && op->od->nbs
+ && ls_dhcp_relay_port(op->od)) {
+ /* Don't add the DHCP server flows if DHCP Relay is enabled on the
+ * logical switch. */
+ return;
+ }
+
bool is_external = lsp_is_external(op->nbsp);
if (is_external && (!op->od->n_localnet_ports ||
!op->nbsp->ha_chassis_group)) {
@@ -13623,6 +13718,166 @@ build_dhcpv6_reply_flows_for_lrouter_port(
}
}
+static void
+build_dhcp_relay_flows_for_lrouter_port(
+ struct ovn_port *op, struct lflow_table *lflows,
+ struct ds *match, struct ds *actions,
+ struct lflow_ref *lflow_ref)
+{
+ if (!op->nbrp || !op->nbrp->dhcp_relay) {
+ return;
+
+ }
+
+ /* configure dhcp relay flows only when peer switch has
+ * relay config enabled */
+ struct ovn_port *sp = op->peer;
+ if (!sp || !sp->nbsp || sp->peer != op ||
+ !sp->od || !ls_dhcp_relay_port(sp->od)) {
+ return;
+ }
+
+ struct nbrec_dhcp_relay *dhcp_relay = op->nbrp->dhcp_relay;
+ if (!dhcp_relay->servers) {
+ return;
+ }
+
+ int addr_family;
+ /* currently not supporting custom port,
+ * dhcp server port is always set to 67 when installing flows */
+ uint16_t port;
+ char *server_ip_str = NULL;
+ struct in6_addr server_ip;
+
+ if (!ip_address_and_port_from_lb_key(dhcp_relay->servers, &server_ip_str,
+ &server_ip, &port, &addr_family)) {
+ return;
+ }
+
+ if (server_ip_str == NULL) {
+ return;
+ }
+
+ ds_clear(match);
+ ds_clear(actions);
+
+ ds_put_format(
+ match, "inport == %s && "
+ "ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && "
+ "ip.frag == 0 && udp.src == 68 && udp.dst == 67",
+ op->json_key);
+ ds_put_format(actions,
+ REGBIT_DHCP_RELAY_REQ_CHK
+ " = dhcp_relay_req_chk(%s, %s);"
+ "next; /* DHCP_RELAY_REQ */",
+ op->lrp_networks.ipv4_addrs[0].addr_s, server_ip_str);
+
+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110,
+ ds_cstr(match), ds_cstr(actions),
+ &op->nbrp->header_, lflow_ref);
+
+ ds_clear(match);
+ ds_clear(actions);
+
+ ds_put_format(
+ match, "inport == %s && "
+ "ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && "
+ "udp.src == 68 && udp.dst == 67 && "
+ REGBIT_DHCP_RELAY_REQ_CHK,
+ op->json_key);
+ ds_put_format(actions,
+ "ip4.src=%s;ip4.dst=%s;udp.src=67;next; /* DHCP_RELAY_REQ */",
+ op->lrp_networks.ipv4_addrs[0].addr_s, server_ip_str);
+
+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_DHCP_RELAY_REQ, 100,
+ ds_cstr(match), ds_cstr(actions),
+ &op->nbrp->header_, lflow_ref);
+
+ ds_clear(match);
+ ds_clear(actions);
+
+ ds_put_format(
+ match, "inport == %s && "
+ "ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && "
+ "udp.src == 68 && udp.dst == 67 && "
+ REGBIT_DHCP_RELAY_REQ_CHK" == 0",
+ op->json_key);
+ ds_put_format(actions,
+ "drop; /* DHCP_RELAY_REQ */");
+
+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_DHCP_RELAY_REQ, 1,
+ ds_cstr(match), ds_cstr(actions),
+ &op->nbrp->header_, lflow_ref);
+
+ ds_clear(match);
+ ds_clear(actions);
+
+ ds_put_format(
+ match, "ip4.src == %s && ip4.dst == %s && "
+ "ip.frag == 0 && udp.src == 67 && udp.dst == 67",
+ server_ip_str, op->lrp_networks.ipv4_addrs[0].addr_s);
+ ds_put_format(actions, "next;/* DHCP_RELAY_RESP */");
+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110,
+ ds_cstr(match), ds_cstr(actions),
+ &op->nbrp->header_, lflow_ref);
+
+ ds_clear(match);
+ ds_clear(actions);
+
+ ds_put_format(
+ match, "ip4.src == %s && ip4.dst == %s && "
+ "udp.src == 67 && udp.dst == 67",
+ server_ip_str, op->lrp_networks.ipv4_addrs[0].addr_s);
+ ds_put_format(actions,
+ REG_DHCP_RELAY_DIP_IPV4" = ip4.dst;"
+ REGBIT_DHCP_RELAY_RESP_CHK
+ " = dhcp_relay_resp_chk(%s, %s);next;/* DHCP_RELAY_RESP */",
+ op->lrp_networks.ipv4_addrs[0].addr_s, server_ip_str);
+
+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_DHCP_RELAY_RESP_CHK,
+ 100,
+ ds_cstr(match), ds_cstr(actions),
+ &op->nbrp->header_, lflow_ref);
+
+
+ ds_clear(match);
+ ds_clear(actions);
+
+ ds_put_format(
+ match, "ip4.src == %s && "
+ REG_DHCP_RELAY_DIP_IPV4" == %s && "
+ "udp.src == 67 && udp.dst == 67 && "
+ REGBIT_DHCP_RELAY_RESP_CHK,
+ server_ip_str, op->lrp_networks.ipv4_addrs[0].addr_s);
+ ds_put_format(actions,
+ "ip4.src=%s;udp.dst=68;"
+ "outport=%s;output; /* DHCP_RELAY_RESP */",
+ op->lrp_networks.ipv4_addrs[0].addr_s, op->json_key);
+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_DHCP_RELAY_RESP,
+ 100,
+ ds_cstr(match), ds_cstr(actions),
+ &op->nbrp->header_, lflow_ref);
+
+ ds_clear(match);
+ ds_clear(actions);
+
+ ds_put_format(
+ match, "ip4.src == %s && "
+ REG_DHCP_RELAY_DIP_IPV4" == %s && "
+ "udp.src == 67 && udp.dst == 67 && "
+ REGBIT_DHCP_RELAY_RESP_CHK" == 0",
+ server_ip_str, op->lrp_networks.ipv4_addrs[0].addr_s);
+ ds_put_format(actions,
+ "drop; /* DHCP_RELAY_RESP */");
+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_DHCP_RELAY_RESP,
+ 1,
+ ds_cstr(match), ds_cstr(actions),
+ &op->nbrp->header_, lflow_ref);
+ ds_clear(match);
+ ds_clear(actions);
+ free(server_ip_str);
+}
+
static void
build_ipv6_input_flows_for_lrouter_port(
struct ovn_port *op, struct lflow_table *lflows,
@@ -14893,6 +15148,13 @@ static void build_lr_nat_defrag_and_lb_default_flows(
lflow_ref);
ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;",
lflow_ref);
+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DHCP_RELAY_REQ, 0, "1",
+ "next;", lflow_ref);
+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DHCP_RELAY_RESP_CHK, 0, "1",
+ "next;", lflow_ref);
+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DHCP_RELAY_RESP, 0, "1",
+ "next;", lflow_ref);
+
/* Send the IPv6 NS packets to next table. When ovn-controller
* generates IPv6 NS (for the action - nd_ns{}), the injected
@@ -15652,6 +15914,7 @@ build_lswitch_and_lrouter_iterate_by_lsp(struct ovn_port *op,
build_lswitch_icmp_packet_toobig_admin_flows(op, lflows, match, actions);
build_lswitch_ip_unicast_lookup(op, lflows, actions,
match);
+ build_lswitch_dhcp_relay_flows(op, ls_ports, lflows, match, actions);
/* Build Logical Router Flows. */
build_arp_resolve_flows_for_lsp(op, lflows, lr_ports, match, actions);
@@ -15681,6 +15944,8 @@ build_lswitch_and_lrouter_iterate_by_lrp(struct ovn_port *op,
op->lflow_ref);
build_dhcpv6_reply_flows_for_lrouter_port(op, lsi->lflows, &lsi->match,
op->lflow_ref);
+ build_dhcp_relay_flows_for_lrouter_port(op, lsi->lflows, &lsi->match,
+ &lsi->actions, op->lflow_ref);
build_ipv6_input_flows_for_lrouter_port(op, lsi->lflows,
&lsi->match, &lsi->actions,
lsi->meter_groups,
diff --git a/northd/northd.h b/northd/northd.h
index 18cad5234..940926945 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -441,24 +441,29 @@ enum ovn_stage {
PIPELINE_STAGE(ROUTER, IN, LOOKUP_NEIGHBOR, 1, "lr_in_lookup_neighbor") \
PIPELINE_STAGE(ROUTER, IN, LEARN_NEIGHBOR, 2, "lr_in_learn_neighbor") \
PIPELINE_STAGE(ROUTER, IN, IP_INPUT, 3, "lr_in_ip_input") \
- PIPELINE_STAGE(ROUTER, IN, UNSNAT, 4, "lr_in_unsnat") \
- PIPELINE_STAGE(ROUTER, IN, DEFRAG, 5, "lr_in_defrag") \
- PIPELINE_STAGE(ROUTER, IN, LB_AFF_CHECK, 6, "lr_in_lb_aff_check") \
- PIPELINE_STAGE(ROUTER, IN, DNAT, 7, "lr_in_dnat") \
- PIPELINE_STAGE(ROUTER, IN, LB_AFF_LEARN, 8, "lr_in_lb_aff_learn") \
- PIPELINE_STAGE(ROUTER, IN, ECMP_STATEFUL, 9, "lr_in_ecmp_stateful") \
- PIPELINE_STAGE(ROUTER, IN, ND_RA_OPTIONS, 10, "lr_in_nd_ra_options") \
- PIPELINE_STAGE(ROUTER, IN, ND_RA_RESPONSE, 11, "lr_in_nd_ra_response") \
- PIPELINE_STAGE(ROUTER, IN, IP_ROUTING_PRE, 12, "lr_in_ip_routing_pre") \
- PIPELINE_STAGE(ROUTER, IN, IP_ROUTING, 13, "lr_in_ip_routing") \
- PIPELINE_STAGE(ROUTER, IN, IP_ROUTING_ECMP, 14, "lr_in_ip_routing_ecmp") \
- PIPELINE_STAGE(ROUTER, IN, POLICY, 15, "lr_in_policy") \
- PIPELINE_STAGE(ROUTER, IN, POLICY_ECMP, 16, "lr_in_policy_ecmp") \
- PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 17, "lr_in_arp_resolve") \
- PIPELINE_STAGE(ROUTER, IN, CHK_PKT_LEN, 18, "lr_in_chk_pkt_len") \
- PIPELINE_STAGE(ROUTER, IN, LARGER_PKTS, 19, "lr_in_larger_pkts") \
- PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 20, "lr_in_gw_redirect") \
- PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 21, "lr_in_arp_request") \
+ PIPELINE_STAGE(ROUTER, IN, DHCP_RELAY_REQ, 4, "lr_in_dhcp_relay_req") \
+ PIPELINE_STAGE(ROUTER, IN, UNSNAT, 5, "lr_in_unsnat") \
+ PIPELINE_STAGE(ROUTER, IN, DEFRAG, 6, "lr_in_defrag") \
+ PIPELINE_STAGE(ROUTER, IN, LB_AFF_CHECK, 7, "lr_in_lb_aff_check") \
+ PIPELINE_STAGE(ROUTER, IN, DNAT, 8, "lr_in_dnat") \
+ PIPELINE_STAGE(ROUTER, IN, LB_AFF_LEARN, 9, "lr_in_lb_aff_learn") \
+ PIPELINE_STAGE(ROUTER, IN, ECMP_STATEFUL, 10, "lr_in_ecmp_stateful") \
+ PIPELINE_STAGE(ROUTER, IN, ND_RA_OPTIONS, 11, "lr_in_nd_ra_options") \
+ PIPELINE_STAGE(ROUTER, IN, ND_RA_RESPONSE, 12, "lr_in_nd_ra_response") \
+ PIPELINE_STAGE(ROUTER, IN, IP_ROUTING_PRE, 13, "lr_in_ip_routing_pre") \
+ PIPELINE_STAGE(ROUTER, IN, IP_ROUTING, 14, "lr_in_ip_routing") \
+ PIPELINE_STAGE(ROUTER, IN, IP_ROUTING_ECMP, 15, "lr_in_ip_routing_ecmp") \
+ PIPELINE_STAGE(ROUTER, IN, POLICY, 16, "lr_in_policy") \
+ PIPELINE_STAGE(ROUTER, IN, POLICY_ECMP, 17, "lr_in_policy_ecmp") \
+ PIPELINE_STAGE(ROUTER, IN, DHCP_RELAY_RESP_CHK, 18, \
+ "lr_in_dhcp_relay_resp_chk") \
+ PIPELINE_STAGE(ROUTER, IN, DHCP_RELAY_RESP, 19, \
+ "lr_in_dhcp_relay_resp") \
+ PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 20, "lr_in_arp_resolve") \
+ PIPELINE_STAGE(ROUTER, IN, CHK_PKT_LEN, 21, "lr_in_chk_pkt_len") \
+ PIPELINE_STAGE(ROUTER, IN, LARGER_PKTS, 22, "lr_in_larger_pkts") \
+ PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 23, "lr_in_gw_redirect") \
+ PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 24, "lr_in_arp_request") \
\
/* Logical router egress stages. */ \
PIPELINE_STAGE(ROUTER, OUT, CHECK_DNAT_LOCAL, 0, \
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index b14a30285..1e7fb3888 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -1934,6 +1934,12 @@ output;
logical switch.
+
+ A priority-100 flow that forwards all DHCP broadcast packets coming
+ from VIFs to the logical router port's MAC when DHCP relay is enabled
+ on the logical switch.
+
+
Priority-90 flows for transit switches that forward registered
IP multicast traffic to their corresponding multicast group , which
@@ -2864,6 +2870,44 @@ icmp6_error {
+
+
+ For each logical router port configured with DHCP relay the
+ following priority-110 flows are added to manage the DHCP relay
+ traffic:
+
+
+ -
+
+ if inport
is lrp and ip4.src == 0.0.0.0
+
and ip4.dst == 255.255.255.255
and
+ ip4.frag == 0
and udp.src == 68
+ and udp.dst == 67
, the dhcp_relay_req_chk
+
action is executed.
+
+
+
+ reg9[7] = dhcp_relay_req_chk(lrp_ip,
+ dhcp_server_ip);next
+
+
+
+ if action is successful then, GIADDR in the dhcp header is
+ updated with lrp ip and stores 1 into reg9[7] else stores 0
+ into reg9[7].
+
+
+
+ -
+ if
ip4.src
is DHCP server ip and ip4.dst
+
is lrp IP and udp.src == 67
and
+ udp.dst == 67
, the packet is advanced to the next
+ pipeline stage.
+
+
+
+
+
L3 admission control: Priority-120 flows allows IGMP and MLD packets
@@ -3328,8 +3372,50 @@ icmp6 {
+
Ingress Table 4: DHCP Relay Request
+
+ This stage process the DHCP request packets on which
+ dhcp_relay_req_chk
action is applied in the IP input stage.
+
+
+ -
+
+ A priority-100 logical flow is added for each logical router port
+ configured with DHCP relay that matches inport
is lrp
+ and ip4.src == 0.0.0.0
and
+ ip4.dst == 255.255.255.255
and udp.src == 68
+
and udp.dst == 67
and reg9[7] == 1
+ and applies following actions. If reg9[7]
is set to 1
+ then, dhcp_relay_req_chk
action was successful.
+
+
+
+ip4.src=lrp ip;
+ip4.dst=dhcp server ip;
+udp.src = 67;
+next;
+
+
+
+ -
+
+ A priority-1 logical flow is added for each logical router port
+ configured with DHCP relay that matches inport
is lrp
+ and ip4.src == 0.0.0.0
and
+ ip4.dst == 255.255.255.255
and udp.src == 68
+
and udp.dst == 67
and reg9[7] == 0
+ and drops the packet. If reg9[7]
is set to 0 then,
+ dhcp_relay_req_chk
action was unsuccessful.
+
+
+
+ -
+ A priority-0 flow that matches all packets to advance to the next
+ table.
+
+
- Ingress Table 4: UNSNAT
+ Ingress Table 5: UNSNAT
This is for already established connections' reverse traffic.
@@ -3338,7 +3424,7 @@ icmp6 {
unSNATted here.
- Ingress Table 4: UNSNAT on Gateway and Distributed Routers
+ Ingress Table 5: UNSNAT on Gateway and Distributed Routers
- Ingress Table 4: UNSNAT on Gateway Routers
+ Ingress Table 5: UNSNAT on Gateway Routers
-
@@ -3414,7 +3500,7 @@ icmp6 {
- Ingress Table 4: UNSNAT on Distributed Routers
+ Ingress Table 5: UNSNAT on Distributed Routers
-
@@ -3461,7 +3547,7 @@ icmp6 {
- Ingress Table 5: DEFRAG
+ Ingress Table 6: DEFRAG
This is to send packets to connection tracker for tracking and
@@ -3504,7 +3590,7 @@ icmp6 {
this allows potentially related ICMP traffic to pass through CT.
- Ingress Table 6: Load balancing affinity check
+ Ingress Table 7: Load balancing affinity check
Load balancing affinity check table contains the following
@@ -3531,7 +3617,7 @@ icmp6 {
- Ingress Table 7: DNAT
+ Ingress Table 8: DNAT
Packets enter the pipeline with destination IP address that needs to
@@ -3539,7 +3625,7 @@ icmp6 {
in the reverse direction needs to be unDNATed.
- Ingress Table 7: Load balancing DNAT rules
+ Ingress Table 8: Load balancing DNAT rules
Following load balancing DNAT flows are added for Gateway router or
@@ -3660,7 +3746,7 @@ icmp6 {
-
Ingress Table 7: DNAT on Gateway Routers
+ Ingress Table 8: DNAT on Gateway Routers
-
@@ -3702,7 +3788,7 @@ icmp6 {
- Ingress Table 7: DNAT on Distributed Routers
+ Ingress Table 8: DNAT on Distributed Routers
On distributed routers, the DNAT table only handles packets
@@ -3757,7 +3843,7 @@ icmp6 {
-
Ingress Table 8: Load balancing affinity learn
+ Ingress Table 9: Load balancing affinity learn
Load balancing affinity learn table contains the following
@@ -3785,7 +3871,7 @@ icmp6 {
-
Ingress Table 9: ECMP symmetric reply processing
+ Ingress Table 10: ECMP symmetric reply processing
-
If ECMP routes with symmetric reply are configured in the
@@ -3804,7 +3890,7 @@ icmp6 {
- Ingress Table 10: IPv6 ND RA option processing
+ Ingress Table 11: IPv6 ND RA option processing
-
@@ -3834,7 +3920,7 @@ reg0[5] = put_nd_ra_opts(options);next;
- Ingress Table 11: IPv6 ND RA responder
+ Ingress Table 12: IPv6 ND RA responder
This table implements IPv6 ND RA responder for the IPv6 ND RA replies
@@ -3879,7 +3965,7 @@ output;
-
Ingress Table 12: IP Routing Pre
+ Ingress Table 13: IP Routing Pre
If a packet arrived at this table from Logical Router Port P
@@ -3909,7 +3995,7 @@ output;
-
Ingress Table 13: IP Routing
+ Ingress Table 14: IP Routing
A packet that arrives at this table is an IP packet that should be
@@ -4115,7 +4201,7 @@ select(reg8[16..31], MID1, MID2, ...);
-
Ingress Table 14: IP_ROUTING_ECMP
+ Ingress Table 15: IP_ROUTING_ECMP
This table implements the second part of IP routing for ECMP routes
@@ -4172,7 +4258,7 @@ outport = P;
-
Ingress Table 15: Router policies
+ Ingress Table 16: Router policies
This table adds flows for the logical router policies configured
on the logical router. Please see the
@@ -4244,7 +4330,7 @@ next;
-
Ingress Table 16: ECMP handling for router policies
+ Ingress Table 17: ECMP handling for router policies
This table handles the ECMP for the router policies configured
with multiple nexthops.
@@ -4293,7 +4379,84 @@ outport = P
-
Ingress Table 17: ARP/ND Resolution
+ Ingress Table 18: DHCP Relay Response Check
+
+ This stage process the DHCP response packets coming from the DHCP server.
+
+
+
+ -
+
+ A priority 100 logical flow is added for each logical router port
+ configured with DHCP relay that matches ip4.src
is
+ DHCP server ip and ip4.dst
is lrp IP and
+ ip4.frag == 0
and udp.src == 67
and
+ udp.dst == 67
and applies dhcp_relay_resp_chk
+
action. Original destination ip is stored in reg2.
+
+
+
+ reg9[8] = dhcp_relay_resp_chk(lrp_ip,
+ dhcp_server_ip);next
+
+
+
+ if action is successful then, dest mac and dest IP addresses are
+ updated in the packet and stores 1 into reg9[8] else stores 0 into
+ reg9[8].
+
+
+
+ -
+ A priority-0 flow that matches all packets to advance to the next
+ table.
+
+
+
+ Ingress Table 19: DHCP Relay Response
+
+ This stage process the DHCP response packets on which
+ dhcp_relay_resp_chk
action is applied in the previous stage.
+
+
+ -
+
+ A priority 100 logical flow is added for each logical router port
+ configured with DHCP relay that matches ip4.src
is
+ DHCP server ip and reg2
is lrp IP and
+ udp.src == 67
and udp.dst == 67
+ and reg9[8] == 1
and applies following actions. If
+ reg9[8]
is set to 1 then,
+ dhcp_relay_resp_chk
was successful.
+
+
+
+ip4.src = lrp ip;
+udp.dst = 68;
+outport = lrp port;
+output;
+
+
+
+ -
+
+ A priority 1 logical flow is added for the logical router port
+ on which DHCP relay is enabled that matches ip4.src
+ is DHCP server ip and reg2
is lrp IP and
+ udp.src == 67
and udp.dst == 67
+ and reg9[8] == 0
and drops the packet. If
+ reg9[8]
is set to 0 then,
+ dhcp_relay_resp_chk
was unsuccessful.
+
+
+
+ -
+ A priority-0 flow that matches all packets to advance to the next
+ table.
+
+
+
+ Ingress Table 20: ARP/ND Resolution
Any packet that reaches this table is an IP packet whose next-hop
@@ -4507,7 +4670,7 @@ outport = P
-
Ingress Table 18: Check packet length
+ Ingress Table 21: Check packet length
For distributed logical routers or gateway routers with gateway
@@ -4544,7 +4707,7 @@ REGBIT_PKT_LARGER = check_pkt_larger(L); next;
and advances to the next table.
- Ingress Table 19: Handle larger packets
+ Ingress Table 22: Handle larger packets
For distributed logical routers or gateway routers with gateway port
@@ -4607,7 +4770,7 @@ icmp6 {
and advances to the next table.
- Ingress Table 20: Gateway Redirect
+ Ingress Table 23: Gateway Redirect
For distributed logical routers where one or more of the logical router
@@ -4691,7 +4854,7 @@ icmp6 {
-
Ingress Table 21: ARP Request
+ Ingress Table 24: ARP Request
In the common case where the Ethernet destination has been resolved, this
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
index a9c5b7af5..10ce50b25 100644
--- a/ovn-nb.ovsschema
+++ b/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
{
"name": "OVN_Northbound",
- "version": "7.3.0",
- "cksum": "3546526738 34483",
+ "version": "7.3.1",
+ "cksum": "3899022625 35372",
"tables": {
"NB_Global": {
"columns": {
@@ -436,6 +436,11 @@
"ipv6_prefix": {"type": {"key": "string",
"min": 0,
"max": "unlimited"}},
+ "dhcp_relay": {"type": {"key": {"type": "uuid",
+ "refTable": "DHCP_Relay",
+ "refType": "strong"},
+ "min": 0,
+ "max": 1}},
"external_ids": {
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}},
@@ -534,6 +539,18 @@
"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}}},
"isRoot": true},
+ "DHCP_Relay": {
+ "columns": {
+ "name": {"type": "string"},
+ "servers": {"type": {"key": "string",
+ "min": 0,
+ "max": 1}},
+ "options": {"type": {"key": "string", "value": "string",
+ "min": 0, "max": "unlimited"}},
+ "external_ids": {
+ "type": {"key": "string", "value": "string",
+ "min": 0, "max": "unlimited"}}},
+ "isRoot": true},
"Connection": {
"columns": {
"target": {"type": "string"},
diff --git a/ovn-nb.xml b/ovn-nb.xml
index b652046a7..5cb6ba640 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -703,6 +703,13 @@
+
+ If set to the name of logical switch port of type router
+ then, DHCP Relay is enabled for this logical switch provided the
+ corresponding has DHCP Relay
+ configured.
+
+
Value used to request to assign L2 address only if neither subnet
nor ipv6_prefix are specified
@@ -3066,6 +3073,11 @@ or
port has all ingress and egress traffic dropped.
+
+ This column is used to enabled DHCP Relay. Please refer
+ to table.
+
+
Gateways, as documented under Gateways
in the OVN
@@ -4379,6 +4391,33 @@ or
+
+
+ OVN implements native DHCPv4 relay support which caters to the common
+ use case of relaying the DHCP requests to external DHCP server.
+
+
+
+ A name for the DHCP Relay.
+
+
+
+
+ The DHCPv4 server IP address.
+
+
+
+
+ Future purpose.
+
+
+
+
+ See External IDs at the beginning of this document.
+
+
+
+
Configuration for a database connection to an Open vSwitch database
diff --git a/tests/atlocal.in b/tests/atlocal.in
index 63d891b89..32d1c374e 100644
--- a/tests/atlocal.in
+++ b/tests/atlocal.in
@@ -187,6 +187,9 @@ fi
# Set HAVE_DHCPD
find_command dhcpd
+# Set HAVE_DHCLIENT
+find_command dhclient
+
# Set HAVE_BFDD_BEACON
find_command bfdd-beacon
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index dcc29ffa8..7267c7017 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -12191,6 +12191,44 @@ check_row_count nb:QoS 0
AT_CLEANUP
])
+OVN_FOR_EACH_NORTHD_NO_HV([
+AT_SETUP([check DHCP RELAY])
+ovn_start NORTHD_TYPE
+
+check ovn-nbctl ls-add ls0
+check ovn-nbctl lsp-add ls0 ls0-port1
+check ovn-nbctl lsp-set-addresses ls0-port1 02:00:00:00:00:10
+check ovn-nbctl lr-add lr0
+check ovn-nbctl lrp-add lr0 lrp1 02:00:00:00:00:01 192.168.1.1/24
+check ovn-nbctl lsp-add ls0 lrp1-attachment
+check ovn-nbctl lsp-set-type lrp1-attachment router
+check ovn-nbctl lsp-set-addresses lrp1-attachment 00:00:00:00:ff:02
+check ovn-nbctl lsp-set-options lrp1-attachment router-port=lrp1
+check ovn-nbctl lrp-add lr0 lrp-ext 02:00:00:00:00:02 192.168.2.1/24
+
+dhcp_relay=$(ovn-nbctl create DHCP_Relay servers=172.16.1.1)
+check ovn-nbctl set Logical_Router_port lrp1 dhcp_relay=$dhcp_relay
+check ovn-nbctl set Logical_Switch ls0 other_config:dhcp_relay_port=lrp1-attachment
+
+check ovn-nbctl --wait=sb sync
+
+ovn-sbctl lflow-list > lflows
+AT_CAPTURE_FILE([lflows])
+
+AT_CHECK([grep -e "DHCP_RELAY_" lflows | sed 's/table=../table=??/'], [0], [dnl
+ table=??(lr_in_ip_input ), priority=110 , match=(inport == "lrp1" && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && ip.frag == 0 && udp.src == 68 && udp.dst == 67), action=(reg9[[7]] = dhcp_relay_req_chk(192.168.1.1, 172.16.1.1);next; /* DHCP_RELAY_REQ */)
+ table=??(lr_in_ip_input ), priority=110 , match=(ip4.src == 172.16.1.1 && ip4.dst == 192.168.1.1 && ip.frag == 0 && udp.src == 67 && udp.dst == 67), action=(next;/* DHCP_RELAY_RESP */)
+ table=??(lr_in_dhcp_relay_req), priority=100 , match=(inport == "lrp1" && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67 && reg9[[7]]), action=(ip4.src=192.168.1.1;ip4.dst=172.16.1.1;udp.src=67;next; /* DHCP_RELAY_REQ */)
+ table=??(lr_in_dhcp_relay_req), priority=1 , match=(inport == "lrp1" && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67 && reg9[[7]] == 0), action=(drop; /* DHCP_RELAY_REQ */)
+ table=??(lr_in_dhcp_relay_resp_chk), priority=100 , match=(ip4.src == 172.16.1.1 && ip4.dst == 192.168.1.1 && udp.src == 67 && udp.dst == 67), action=(reg2 = ip4.dst;reg9[[8]] = dhcp_relay_resp_chk(192.168.1.1, 172.16.1.1);next;/* DHCP_RELAY_RESP */)
+ table=??(lr_in_dhcp_relay_resp), priority=100 , match=(ip4.src == 172.16.1.1 && reg2 == 192.168.1.1 && udp.src == 67 && udp.dst == 67 && reg9[[8]]), action=(ip4.src=192.168.1.1;udp.dst=68;outport="lrp1";output; /* DHCP_RELAY_RESP */)
+ table=??(lr_in_dhcp_relay_resp), priority=1 , match=(ip4.src == 172.16.1.1 && reg2 == 192.168.1.1 && udp.src == 67 && udp.dst == 67 && reg9[[8]] == 0), action=(drop; /* DHCP_RELAY_RESP */)
+ table=??(ls_in_l2_lkup ), priority=100 , match=(inport == "ls0-port1" && eth.src == 02:00:00:00:00:10 && ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255 && udp.src == 68 && udp.dst == 67), action=(eth.dst=02:00:00:00:00:01;outport="lrp1-attachment";next;/* DHCP_RELAY_REQ */)
+])
+
+AT_CLEANUP
+])
+
AT_SETUP([NB_Global and SB_Global incremental processing])
ovn_start
diff --git a/tests/ovn.at b/tests/ovn.at
index 1ad4159cf..395bcf142 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -34273,7 +34273,7 @@ check ovn-nbctl set nb_global . options:use_common_zone="true"
check ovn-nbctl --wait=hv sync
# Use constants so that if tables or registers change, this test can
# be updated easily.
-DNAT_TABLE=15
+DNAT_TABLE=16
SNAT_TABLE=45
DNAT_ZONE_REG="NXM_NX_REG11[[0..15]]"
SNAT_ZONE_REG="NXM_NX_REG12[[0..15]]"
@@ -37806,3 +37806,225 @@ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=0 |grep priority=1
OVN_CLEANUP([hv1])
AT_CLEANUP
])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([DHCP RELAY])
+ovn_start
+net_add n1
+
+AT_CHECK([ovn-nbctl ls-add ls0])
+AT_CHECK([ovn-nbctl lsp-add ls0 vif0])
+AT_CHECK([ovn-nbctl lsp-set-addresses vif0 "50:54:00:00:00:10"])
+AT_CHECK([ovn-nbctl lsp-add ls0 lrp1-attachment])
+AT_CHECK([ovn-nbctl lsp-set-type lrp1-attachment router])
+AT_CHECK([ovn-nbctl lsp-set-addresses lrp1-attachment 50:54:00:00:00:01])
+AT_CHECK([ovn-nbctl lsp-set-options lrp1-attachment router-port=lrp1])
+
+AT_CHECK([ovn-nbctl lr-add lr0])
+AT_CHECK([ovn-nbctl lrp-add lr0 lrp1 50:54:00:00:00:01 192.168.1.1/24])
+AT_CHECK([ovn-nbctl lrp-add lr0 lrp2 50:54:00:00:00:02 172.16.1.254/24])
+
+AT_CHECK([ovn-nbctl ls-add ls-ext])
+AT_CHECK([ovn-nbctl lsp-add ls-ext lrp2-attachment])
+AT_CHECK([ovn-nbctl lsp-set-type lrp2-attachment router])
+AT_CHECK([ovn-nbctl lsp-set-addresses lrp2-attachment 50:54:00:00:00:02])
+AT_CHECK([ovn-nbctl lsp-set-options lrp2-attachment router-port=lrp2])
+AT_CHECK([ovn-nbctl lsp-add ls-ext ln_port])
+AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown])
+AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet])
+AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1])
+
+dhcp_relay=$(ovn-nbctl create DHCP_Relay servers=172.16.1.1)
+AT_CHECK([ovn-nbctl set Logical_Router_port lrp1 dhcp_relay=$dhcp_relay])
+AT_CHECK([ovn-nbctl set Logical_Switch ls0 other_config:dhcp_relay_port=lrp1-attachment])
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys
+ovn_attach n1 br-phys 192.168.0.1
+ovs-vsctl -- add-port br-int vif0 -- \
+ set interface vif0 external-ids:iface-id=vif0 \
+ options:tx_pcap=hv1/vif0-tx.pcap \
+ options:rxq_pcap=hv1/vif0-rx.pcap \
+ ofport-request=1
+ovs-vsctl -- add-port br-phys ext0 -- \
+ set interface ext0 \
+ options:tx_pcap=hv1/ext0-tx.pcap \
+ options:rxq_pcap=hv1/ext0-rx.pcap \
+ ofport-request=2
+
+ovs-vsctl set open . external_ids:ovn-bridge-mappings=physnet1:br-phys
+
+wait_for_ports_up
+AT_CHECK([ovn-nbctl --wait=hv sync])
+
+send_dhcp_packet() {
+ src_mac=${1}
+ src_ip=${2}
+ dst_mac=${3}
+ dst_ip=${4}
+ op_code=${5}
+ msg_type=${6}
+ yiaddr=$7
+ giaddr=${8}
+ sid=${9}
+ iface=${10}
+ #echo "ARGS: ${1} ${2} ${3} ${4} ${5} ${6} ${7} ${8} ${9} ${10}"
+ echo "ARGS: $@"
+ if [[ $op_code == "01" ]]; then
+ ip_len=0111
+ udp_len=00fd
+ src_port=0044
+ else
+ ip_len=011d
+ udp_len=0109
+ src_port=0043
+ fi
+ flags=0000
+
+ local pkt=${dst_mac}${src_mac}08004510${ip_len}0000000080110000${src_ip}${dst_ip}
+ # udp header and dhcp header
+ pkt=${pkt}${src_port}0043${udp_len}0000
+ pkt=${pkt}${op_code}0106006359aa760000${flags}00000000${yiaddr}00000000${giaddr}${src_mac}
+ # client hardware padding
+ pkt=${pkt}00000000000000000000
+ # server hostname
+ pkt=${pkt}0000000000000000000000000000000000000000000000000000000000000000
+ pkt=${pkt}0000000000000000000000000000000000000000000000000000000000000000
+ # boot file name
+ pkt=${pkt}0000000000000000000000000000000000000000000000000000000000000000
+ pkt=${pkt}0000000000000000000000000000000000000000000000000000000000000000
+ pkt=${pkt}0000000000000000000000000000000000000000000000000000000000000000
+ pkt=${pkt}0000000000000000000000000000000000000000000000000000000000000000
+ # dhcp magic cookie
+ pkt=${pkt}63825363
+ # dhcp message type
+ pkt=${pkt}3501${msg_type}
+ # dhcp server identifier and subnet mask options
+ if [[ $op_code == "02" ]]; then
+ pkt=${pkt}3604${sid}
+ pkt=${pkt}0104ffffff00
+ fi
+ # dhcp pad option
+ pkt=${pkt}00
+ # dhcp end option
+ pkt=${pkt}ff
+
+ tcpdump_hex "-- sending DHCP pkt on hv1-$iface" $pkt
+
+ ovs-appctl netdev-dummy/receive $iface $pkt
+}
+
+ovn-sbctl dump-flows > lflows
+AT_CAPTURE_FILE([lflows])
+
+# Get the OF table numbers
+dhcp_relay_req_table=$(ovn-debug lflow-stage-to-oftable lr_in_dhcp_relay_req)
+dhcp_relay_resp_table=$(ovn-debug lflow-stage-to-oftable lr_in_dhcp_relay_resp)
+
+# ====================================================
+# Send DHCP valid discovery
+src_mac="505400000010"
+src_ip=`ip_to_hex 0.0.0.0`
+dst_mac="ffffffffffff"
+dst_ip=`ip_to_hex 255.255.255.255`
+yiaddr=`ip_to_hex 0.0.0.0`
+giaddr=`ip_to_hex 0.0.0.0`
+sid=$src_ip
+# send packet
+send_dhcp_packet $src_mac $src_ip $dst_mac $dst_ip 01 01 $yiaddr $giaddr $sid vif0
+
+ovs-ofctl dump-flows br-int table=$dhcp_relay_req_table > pflows1_dhcp_relay_req
+AT_CAPTURE_FILE([pflows1_dhcp_relay_req])
+
+AT_CHECK([cat pflows1_dhcp_relay_req | grep -v NXST | grep 255.255.255.255 | grep resubmit |
+cut -d ' ' -f5-5 | sed "s/,//"], [0], [dnl
+n_packets=1
+])
+
+# ====================================================
+# Send DHCP discovery with giaddr set
+giaddr=`ip_to_hex 192.168.1.1`
+# send packet
+send_dhcp_packet $src_mac $src_ip $dst_mac $dst_ip 01 01 $yiaddr $giaddr $sid vif0
+
+ovs-ofctl dump-flows br-int table=$dhcp_relay_req_table > pflows2_dhcp_relay_req
+AT_CAPTURE_FILE([pflows2_dhcp_relay_req])
+
+AT_CHECK([cat pflows2_dhcp_relay_req | grep -v NXST | grep 255.255.255.255 | grep drop |
+cut -d ' ' -f5-5 | sed "s/,//"], [0], [dnl
+n_packets=1
+])
+
+# ====================================================
+# Send DHCP valid offer
+src_mac="50540000001f"
+src_ip=`ip_to_hex 172.16.1.1`
+dst_mac="505400000002"
+dst_ip=`ip_to_hex 192.168.1.1`
+yiaddr=`ip_to_hex 192.168.1.10`
+giaddr=`ip_to_hex 192.168.1.1`
+sid=$src_ip
+# send packet
+send_dhcp_packet $src_mac $src_ip $dst_mac $dst_ip 02 02 $yiaddr $giaddr $sid ext0
+
+ovs-ofctl dump-flows br-int table=$dhcp_relay_resp_table > pflows1_dhcp_relay_resp
+AT_CAPTURE_FILE([pflows1_dhcp_relay_resp])
+
+AT_CHECK([cat pflows1_dhcp_relay_resp | grep -v NXST | grep 172.16.1.1 | grep resubmit |
+cut -d ' ' -f5-5 | sed "s/,//"], [0], [dnl
+n_packets=1
+])
+# ====================================================
+# Send DHCP offer with incorrect giaddr
+giaddr=`ip_to_hex 192.168.1.10`
+# send packet
+send_dhcp_packet $src_mac $src_ip $dst_mac $dst_ip 02 02 $yiaddr $giaddr $sid ext0
+
+ovs-ofctl dump-flows br-int table=$dhcp_relay_resp_table > pflows2_dhcp_relay_resp
+AT_CAPTURE_FILE([pflows2_dhcp_relay_resp])
+
+AT_CHECK([cat pflows2_dhcp_relay_resp | grep -v NXST | grep 172.16.1.1 | grep drop |
+cut -d ' ' -f5-5 | sed "s/,//"], [0], [dnl
+n_packets=1
+])
+
+giaddr=`ip_to_hex 192.168.1.1`
+
+# ====================================================
+# Send DHCP offer with yiaddr outside of the subnet
+yiaddr=`ip_to_hex 192.168.2.10`
+# send packet
+send_dhcp_packet $src_mac $src_ip $dst_mac $dst_ip 02 02 $yiaddr $giaddr $sid ext0
+
+ovs-ofctl dump-flows br-int table=$dhcp_relay_resp_table > pflows2_dhcp_relay_resp
+AT_CAPTURE_FILE([pflows2_dhcp_relay_resp])
+
+AT_CHECK([cat pflows2_dhcp_relay_resp | grep -v NXST | grep 172.16.1.1 | grep drop |
+cut -d ' ' -f5-5 | sed "s/,//"], [0], [dnl
+n_packets=2
+])
+
+yiaddr=`ip_to_hex 192.168.1.10`
+
+# ====================================================
+# Send DHCP offer with differnt server identifier
+sid=`ip_to_hex 172.16.1.100`
+# send packet
+send_dhcp_packet $src_mac $src_ip $dst_mac $dst_ip 02 02 $yiaddr $giaddr $sid ext0
+
+ovs-ofctl dump-flows br-int table=$dhcp_relay_resp_table > pflows2_dhcp_relay_resp
+AT_CAPTURE_FILE([pflows2_dhcp_relay_resp])
+
+AT_CHECK([cat pflows2_dhcp_relay_resp | grep -v NXST | grep 172.16.1.1 | grep drop |
+cut -d ' ' -f5-5 | sed "s/,//"], [0], [dnl
+n_packets=3
+])
+
+sid=`ip_to_hex 172.16.1.1`
+
+OVN_CLEANUP([hv1
+/WARN\|DHCP_RELAY/d
+])
+AT_CLEANUP
+])
diff --git a/tests/system-ovn.at b/tests/system-ovn.at
index 5848f3901..b9f731396 100644
--- a/tests/system-ovn.at
+++ b/tests/system-ovn.at
@@ -12512,3 +12512,151 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d
/connection dropped.*/d"])
AT_CLEANUP
])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([DHCP RELAY])
+AT_SKIP_IF([test $HAVE_DHCPD = no])
+AT_SKIP_IF([test $HAVE_DHCLIENT = no])
+AT_SKIP_IF([test $HAVE_TCPDUMP = no])
+ovn_start
+OVS_TRAFFIC_VSWITCHD_START()
+
+ADD_BR([br-int])
+ADD_BR([br-ext])
+
+ovs-ofctl add-flow br-ext action=normal
+# Set external-ids in br-int needed for ovn-controller
+ovs-vsctl \
+ -- set Open_vSwitch . external-ids:system-id=hv1 \
+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \
+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \
+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \
+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true
+
+# Start ovn-controller
+start_daemon ovn-controller
+
+ADD_NAMESPACES(sw01)
+ADD_VETH(sw01, sw01, br-int, "0", "f0:00:00:01:02:03")
+ADD_NAMESPACES(sw11)
+ADD_VETH(sw11, sw11, br-int, "0", "f0:00:00:02:02:03")
+ADD_NAMESPACES(server)
+ADD_VETH(s1, server, br-ext, "172.16.1.1/24", "f0:00:00:01:02:05", \
+ "172.16.1.254")
+
+check ovn-nbctl lr-add R1
+
+check ovn-nbctl ls-add sw0
+check ovn-nbctl ls-add sw1
+check ovn-nbctl ls-add sw-ext
+
+check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24
+check ovn-nbctl lrp-add R1 rp-sw1 00:00:03:01:02:03 192.168.2.1/24
+check ovn-nbctl lrp-add R1 rp-ext 00:00:02:01:02:03 172.16.1.254/24
+
+dhcp_relay=$(ovn-nbctl create DHCP_Relay servers=172.16.1.1)
+check ovn-nbctl set Logical_Router_port rp-sw0 dhcp_relay=$dhcp_relay
+check ovn-nbctl set Logical_Router_port rp-sw1 dhcp_relay=$dhcp_relay
+check ovn-nbctl lrp-set-gateway-chassis rp-ext hv1
+
+check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \
+ type=router options:router-port=rp-sw0 \
+ -- lsp-set-addresses sw0-rp router
+check ovn-nbctl lsp-add sw1 sw1-rp -- set Logical_Switch_Port sw1-rp \
+ type=router options:router-port=rp-sw1 \
+ -- lsp-set-addresses sw1-rp router
+
+check ovn-nbctl set Logical_Switch sw0 other_config:dhcp_relay_port=sw0-rp
+check ovn-nbctl set Logical_Switch sw1 other_config:dhcp_relay_port=sw1-rp
+
+check ovn-nbctl lsp-add sw-ext ext-rp -- set Logical_Switch_Port ext-rp \
+ type=router options:router-port=rp-ext \
+ -- lsp-set-addresses ext-rp router
+check ovn-nbctl lsp-add sw-ext lnet \
+ -- lsp-set-addresses lnet unknown \
+ -- lsp-set-type lnet localnet \
+ -- lsp-set-options lnet network_name=phynet
+
+check ovn-nbctl lsp-add sw0 sw01 \
+ -- lsp-set-addresses sw01 "f0:00:00:01:02:03"
+
+check ovn-nbctl lsp-add sw1 sw11 \
+ -- lsp-set-addresses sw11 "f0:00:00:02:02:03"
+
+AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext])
+
+OVN_POPULATE_ARP
+
+check ovn-nbctl --wait=hv sync
+
+DHCP_TEST_DIR="/tmp/dhcp-test"
+rm -rf $DHCP_TEST_DIR
+mkdir $DHCP_TEST_DIR
+cat > $DHCP_TEST_DIR/dhcpd.conf < $DHCP_TEST_DIR/dhclien.conf < dhcpd.log 2>&1], [dhcpd.pid])
+
+NS_CHECK_EXEC([server], [tcpdump -l -nvv -i s1 udp > pkt.pcap 2>tcpdump_err &])
+OVS_WAIT_UNTIL([grep "listening" tcpdump_err])
+on_exit 'kill $(pidof tcpdump)'
+
+NS_CHECK_EXEC([sw01], [dhclient -1 -q -lf $DHCP_TEST_DIR/dhclient-sw01.lease -pf $DHCP_TEST_DIR/dhclient-sw01.pid -cf $DHCP_TEST_DIR/dhclien.conf sw01])
+NS_CHECK_EXEC([sw11], [dhclient -1 -q -lf $DHCP_TEST_DIR/dhclient-sw11.lease -pf $DHCP_TEST_DIR/dhclient-sw11.pid -cf $DHCP_TEST_DIR/dhclien.conf sw11])
+
+OVS_WAIT_UNTIL([
+ total_pkts=$(cat pkt.pcap | wc -l)
+ test ${total_pkts} -ge 8
+])
+
+on_exit 'kill `cat $DHCP_TEST_DIR/dhclient-sw01.pid` &&
+kill `cat $DHCP_TEST_DIR/dhclient-sw11.pid` && rm -rf $DHCP_TEST_DIR'
+
+NS_CHECK_EXEC([sw01], [ip addr show sw01 | grep -oP '(?<=inet\s)\d+(\.\d+){3}'], [0], [dnl
+192.168.1.10
+])
+NS_CHECK_EXEC([sw11], [ip addr show sw11 | grep -oP '(?<=inet\s)\d+(\.\d+){3}'], [0], [dnl
+192.168.2.10
+])
+OVS_APP_EXIT_AND_WAIT([ovn-controller])
+
+as ovn-sb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as ovn-nb
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+as northd
+OVS_APP_EXIT_AND_WAIT([ovn-northd])
+
+as
+OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d
+/failed to query port patch-.*/d
+/.*terminating with signal 15.*/d"])
+AT_CLEANUP
+])