From ea04d051ddd4087bc6d18624680fe3809447541a Mon Sep 17 00:00:00 2001 From: Vayam Agarwal Date: Sun, 7 Jun 2026 15:03:12 -0700 Subject: [PATCH 1/2] supporting a DNS server IP while intercepting UDP port 53 packets --- northd/northd.c | 54 ++++++++++++++++++++++++++++++++++++++----------- ovn-nb.xml | 25 +++++++++++++++++++++++ 2 files changed, 67 insertions(+), 12 deletions(-) diff --git a/northd/northd.c b/northd/northd.c index 345a902dca..8cbf041e61 100644 --- a/northd/northd.c +++ b/northd/northd.c @@ -10932,23 +10932,53 @@ build_lswitch_dns_lookup_and_response(struct ovn_datapath *od, if (!ls_has_dns_records(od->nbs)) { return; } + + const char *dns_server_ip = + smap_get(&od->nbs->other_config, "dns_server_ip"); + const char *copp_meter = copp_meter_get(COPP_DNS, od->nbs->copp, + meter_groups); + + struct ds match = DS_EMPTY_INITIALIZER; + + /* IPv4 DNS lookup. */ + if (dns_server_ip) { + ds_put_format(&match, "udp.dst == 53 && ip4.dst == %s", dns_server_ip); + } else { + ds_put_cstr(&match, "udp.dst == 53 && ip4"); + } + ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 100, ds_cstr(&match), + REGBIT_DNS_LOOKUP_RESULT" = dns_lookup(); next;", + lflow_ref, WITH_CTRL_METER(copp_meter)); + ds_clear(&match); + + /* IPv6 DNS lookup. */ ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 100, - "udp.dst == 53", + "udp.dst == 53 && ip6", REGBIT_DNS_LOOKUP_RESULT" = dns_lookup(); next;", - lflow_ref, WITH_CTRL_METER(copp_meter_get(COPP_DNS, - od->nbs->copp, - meter_groups))); - const char *dns_action = "eth.dst <-> eth.src; ip4.src <-> ip4.dst; " + lflow_ref, WITH_CTRL_METER(copp_meter)); + + /* IPv4 DNS response. */ + if (dns_server_ip) { + ds_put_format(&match, "udp.dst == 53 && ip4.dst == %s && " + REGBIT_DNS_LOOKUP_RESULT, dns_server_ip); + } else { + ds_put_cstr(&match, "udp.dst == 53 && ip4 && " + REGBIT_DNS_LOOKUP_RESULT); + } + ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100, ds_cstr(&match), + "eth.dst <-> eth.src; ip4.src <-> ip4.dst; " "udp.dst = udp.src; udp.src = 53; outport = inport; " - "flags.loopback = 1; output;"; - const char *dns_match = "udp.dst == 53 && "REGBIT_DNS_LOOKUP_RESULT; + "flags.loopback = 1; output;", lflow_ref); + ds_clear(&match); + + /* IPv6 DNS response. */ ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100, - dns_match, dns_action, lflow_ref); - dns_action = "eth.dst <-> eth.src; ip6.src <-> ip6.dst; " + "udp.dst == 53 && ip6 && " REGBIT_DNS_LOOKUP_RESULT, + "eth.dst <-> eth.src; ip6.src <-> ip6.dst; " "udp.dst = udp.src; udp.src = 53; outport = inport; " - "flags.loopback = 1; output;"; - ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100, - dns_match, dns_action, lflow_ref); + "flags.loopback = 1; output;", lflow_ref); + + ds_destroy(&match); } /* Table 24: External port. Drop ARP request for router ips from diff --git a/ovn-nb.xml b/ovn-nb.xml index ed16da2772..e393bc21f3 100644 --- a/ovn-nb.xml +++ b/ovn-nb.xml @@ -1157,6 +1157,31 @@ forwarded to all routers and therefor the mac bindings of the routers are no longer updated. + + +

+ IPv4 address of OVN's virtual DNS server for this logical switch. + When set, ovn-northd generates DNS interception flows + that match only packets whose destination IP equals this address. + This prevents OVN from sending a packet-in to + ovn-controller for DNS queries directed at external + resolvers (e.g. 8.8.8.8), reducing unnecessary CPU + overhead. +

+ +

+ The value must be a valid IPv4 address. VMs must be configured + (e.g. via the DHCP option) to use this address as their + DNS resolver so that OVN can intercept and answer their queries. +

+ +

+ When this option is not set, OVN falls back to the default behavior + of intercepting all UDP port-53 traffic on the logical switch. +

+
+ From b88c0f4694786b6893f075eee80e4eb52ca6e872 Mon Sep 17 00:00:00 2001 From: Vayam Agarwal Date: Sun, 7 Jun 2026 15:37:36 -0700 Subject: [PATCH 2/2] test validating the packet is intercepted --- tests/ovn.at | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/tests/ovn.at b/tests/ovn.at index dba5f5ffcb..3c7f1dbe67 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -12532,6 +12532,118 @@ OVN_CLEANUP([hv1]) AT_CLEANUP ]) +OVN_FOR_EACH_NORTHD([ +AT_SETUP([dns lookup : dns_server_ip]) +ovn_start + +check ovn-nbctl ls-add ls1 + +check ovn-nbctl lsp-add ls1 ls1-lp1 \ +-- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4" +check ovn-nbctl lsp-set-port-security ls1-lp1 "f0:00:00:00:00:01 10.0.0.4" + +DNS1=`ovn-nbctl create DNS records={}` +check ovn-nbctl set DNS $DNS1 records:vm1.ovn.org="10.0.0.4" +check ovn-nbctl set Logical_switch ls1 dns_records="$DNS1" + +# Only intercept DNS queries sent to this specific IP, not all UDP port-53 traffic. +check ovn-nbctl set Logical_switch ls1 other_config:dns_server_ip=10.0.0.53 + +net_add n1 +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 hv1-vif1 -- \ + set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \ + options:tx_pcap=hv1/vif1-tx.pcap \ + options:rxq_pcap=hv1/vif1-rx.pcap \ + ofport-request=1 + +OVN_POPULATE_ARP +wait_for_ports_up +check ovn-nbctl --wait=hv sync + +ovn-sbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) + +# DNS lookup/response flows must match only the configured dns_server_ip. +AT_CHECK([grep "ls_in_dns_lookup" sbflows | grep "ip4.dst == 10.0.0.53"], [0], [ignore]) +AT_CHECK([grep "ls_in_dns_response" sbflows | grep "ip4.dst == 10.0.0.53"], [0], [ignore]) +# There must be no catch-all IPv4 DNS flow. +AT_CHECK([grep "ls_in_dns_lookup" sbflows | grep -v "ip4.dst" | grep " ip4"], [1], [ignore]) + +# vm1.ovn.org A-record query/response data (DNS label encoding). +query_name=03766d31036f766e036f726700 +dns_req_data=010201200001000000000000${query_name}00010001 +dns_resp_data=010281200001000100000000${query_name}00010001${query_name}0001000100000e1000040a000004 + +# Build and inject a DNS/UDP/IPv4 packet. When expect_reply=1 the expected +# response is appended to $inport.expected for later OVN_CHECK_PACKETS. +test_dns_server_ip() { + local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 expect_reply=$6 + local query_data=$7 + shift 7 + + local ip_len udp_len + ip_len=`expr 28 + ${#query_data} / 2` + udp_len=`expr $ip_len - 20` + ip_len=$(printf "%x" $ip_len) + udp_len=$(printf "%x" $udp_len) + + local request=${dst_mac}${src_mac}0800450000${ip_len}0000000080110000 + request=${request}${src_ip}${dst_ip}9234003500${udp_len}0000${query_data} + + if test "$expect_reply" = "1"; then + local resp_data=$1 + ip_len=`expr 28 + ${#resp_data} / 2` + udp_len=`expr $ip_len - 20` + ip_len=$(printf "%x" $ip_len) + udp_len=$(printf "%x" $udp_len) + local reply=${src_mac}${dst_mac}0800450000${ip_len}0000000080110000 + reply=${reply}${dst_ip}${src_ip}0035923400${udp_len}0000${resp_data} + echo $reply >> ${inport}.expected + fi + as hv1 ovs-appctl netdev-dummy/receive hv1-vif${inport} $request +} + +AT_CAPTURE_FILE([ofctl_monitor0.log]) +as hv1 ovs-ofctl -t 300 monitor br-int resume --detach --no-chdir \ +--pidfile=ovs-ofctl0.pid 2> ofctl_monitor0.log + +AS_BOX([Query to dns_server_ip should be intercepted and answered by OVN]) +src_ip=`ip_to_hex 10 0 0 4` +dst_ip=`ip_to_hex 10 0 0 53` +test_dns_server_ip 1 f00000000001 f000000000d0 $src_ip $dst_ip 1 \ + $dns_req_data $dns_resp_data + +# NXT_RESUMEs should be 1. +OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c -48"]) +# Skip the IPv4 checksum. +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected], ["cut -c 53-"]) + +reset_pcap_file hv1-vif1 hv1/vif1 +rm -f 1.expected + +AS_BOX([Query to a different IP should not be intercepted]) +src_ip=`ip_to_hex 10 0 0 4` +dst_ip=`ip_to_hex 8 8 8 8` +test_dns_server_ip 1 f00000000001 f000000000d0 $src_ip $dst_ip 0 $dns_req_data + +# NXT_RESUME count must remain 1 (packet was not intercepted by OVN DNS). +OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor*.log | grep -c NXT_RESUME`]) + +$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > 1.packets +AT_CHECK([cat 1.packets], [0], []) + +OVN_CLEANUP([hv1]) + +AT_CLEANUP +]) + OVN_FOR_EACH_NORTHD([ AT_SETUP([dns lookup : EDNS]) OVN_CHECK_SCAPY_EDNS_CLIENT_SUBNET_SUPPORT()