diff mbox series

[ovs-dev,v5] route-table: Add support for v4 via v6 route.

Message ID 20240529232759.380537-1-witu@nvidia.com
State Changes Requested
Headers show
Series [ovs-dev,v5] route-table: Add support for v4 via v6 route. | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test fail github build: failed
ovsrobot/intel-ovs-compilation success test: success

Commit Message

William Tu May 29, 2024, 11:27 p.m. UTC
Add route-table support for ipv4 dst via ipv6. One use case is BGP
unnumbered, a mechanism that establishes peering sessions without the
need to explicitly configure IPv4 addresses on the interfaces involved
in the peering. Without using IPv4 address assignments, it uses
link-local IPv6 addresses of the directly connected neighbors for
peering purposes. For example, BGP might install the following route:
$ ip route get 100.87.18.3
    100.87.18.3 via inet6 fe80::920a:84ff:fe9e:9570 \
    dev br-phy src 100.87.18.6

Note that the v6 addr fe80::920a:84ff:fe9e:9570 is not being used in
the packet header, but only used for lookup the out dev br-phy.
Currently OVS can only support either all-ipv4 or all-ipv6, the patch
adds support for such use case.

Reported-at: https://mail.openvswitch.org/pipermail/ovs-discuss/2024-January/052908.html
Acked-by: Simon Horman <horms@ovn.org>
Signed-off-by: William Tu <witu@nvidia.com>
---
v5: fix minor CI failure
v4: feedback from Ilya
- add route del test case, wrap around test width
- not set neighbor cache manually
- on br-phy, use /32 on address in steead of /24
compare v3 and v4
https://github.com/williamtu/ovs/compare/router..router-v4
v3: add vxlan test, remove rfc
v2: fix CI error
---
 lib/ovs-router.c             | 39 +++++++++++-----------
 lib/route-table.c            | 21 ++++++++++++
 ofproto/ofproto-dpif-xlate.c |  3 +-
 tests/ovs-router.at          | 51 +++++++++++++++++++++++++++++
 tests/system-route.at        | 39 ++++++++++++++++++++++
 tests/tunnel-push-pop.at     | 63 ++++++++++++++++++++++++++++++++++++
 6 files changed, 197 insertions(+), 19 deletions(-)

Comments

Ilya Maximets May 30, 2024, 6:17 p.m. UTC | #1
On 5/30/24 01:27, William Tu wrote:
> Add route-table support for ipv4 dst via ipv6. One use case is BGP
> unnumbered, a mechanism that establishes peering sessions without the
> need to explicitly configure IPv4 addresses on the interfaces involved
> in the peering. Without using IPv4 address assignments, it uses
> link-local IPv6 addresses of the directly connected neighbors for
> peering purposes. For example, BGP might install the following route:
> $ ip route get 100.87.18.3
>     100.87.18.3 via inet6 fe80::920a:84ff:fe9e:9570 \
>     dev br-phy src 100.87.18.6
> 
> Note that the v6 addr fe80::920a:84ff:fe9e:9570 is not being used in
> the packet header, but only used for lookup the out dev br-phy.
> Currently OVS can only support either all-ipv4 or all-ipv6, the patch
> adds support for such use case.
> 
> Reported-at: https://mail.openvswitch.org/pipermail/ovs-discuss/2024-January/052908.html
> Acked-by: Simon Horman <horms@ovn.org>
> Signed-off-by: William Tu <witu@nvidia.com>
> ---
> v5: fix minor CI failure
> v4: feedback from Ilya
> - add route del test case, wrap around test width
> - not set neighbor cache manually
> - on br-phy, use /32 on address in steead of /24
> compare v3 and v4
> https://github.com/williamtu/ovs/compare/router..router-v4
> v3: add vxlan test, remove rfc
> v2: fix CI error
> ---

<snip>

> diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
> index 508737c53ec6..7266f0990570 100644
> --- a/tests/tunnel-push-pop.at
> +++ b/tests/tunnel-push-pop.at
> @@ -196,6 +196,69 @@ OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep 100022eb0000000120000237 | wc -l`
>  OVS_VSWITCHD_STOP
>  AT_CLEANUP
>  
> +AT_SETUP([tunnel_push_pop - v4 via v6 route])
> +
> +OVS_VSWITCHD_START([add-port br0 p0 -- set Interface p0 type=dummy ofport_request=1 other-config:hwaddr=aa:55:aa:55:00:00])
> +AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy], [0])
> +AT_CHECK([ovs-vsctl add-port int-br t1 -- set Interface t1 type=vxlan \
> +                       options:remote_ip=1.1.2.92 options:key=123 ofport_request=1\
> +                       ], [0])
> +
> +AT_CHECK([ovs-appctl dpif/show], [0], [dnl
> +dummy@ovs-dummy: hit:0 missed:0
> +  br0:
> +    br0 65534/100: (dummy-internal)
> +    p0 1/1: (dummy)
> +  int-br:
> +    int-br 65534/2: (dummy-internal)
> +    t1 1/4789: (vxlan: key=123, remote_ip=1.1.2.92)
> +])
> +
> +AT_CHECK([ovs-ofctl add-flow br0 action=normal])
> +
> +dnl Setup dummy interface IP addresses.
> +AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/32], [0], [OK
> +])
> +AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/64], [0], [OK
> +])
> +dnl Add a static v4 via v6 route
> +AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/32 br0 2001:cafe::10 src=1.1.2.89], [0], [OK
> +])
> +
> +AT_CHECK([ovs-appctl ovs/route/show | grep br0 | sort], [0], [dnl
> +Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
> +Cached: 2001:cafe::/64 dev br0 SRC 2001:cafe::88 local
> +User: 1.1.2.92/32 dev br0 GW 2001:cafe::10 SRC 1.1.2.89
> +])
> +
> +dnl Check ARP Snoop
> +AT_CHECK([ovs-appctl netdev-dummy/receive p0 'recirc_id(0),in_port(100),dnl
> +eth(src=f8:bc:12:44:34:b6,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),dnl
> +arp(sip=1.1.2.92,tip=1.1.2.88,op=2,sha=f8:bc:12:44:34:b6,tha=00:00:00:00:00:00)'])

This is still not a correct test, we would never receive an ARP
from an IPv6-only network.  This must be an IPv6 NA packet instead.

All in all, I'd expect the following test to work without modifications
(unless I mistyped something):

---
AT_SETUP([tunnel_push_pop - v4 via v6 route])

OVS_VSWITCHD_START(
    [add-port br0 p0 \
     -- set Interface p0 type=dummy ofport_request=1 \
                         other-config:hwaddr=aa:55:aa:55:00:00])
AT_CHECK([ovs-appctl vlog/set dpif_netdev:dbg])
AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy])
AT_CHECK([ovs-vsctl add-port int-br t2 \
          -- set Interface t2 type=geneve \
                              options:remote_ip=1.1.2.92 \
                              options:key=123 ofport_request=2])

dnl Setup IP addresses.
AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/32], [0], [OK
])
AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/64], [0], [OK
])
dnl Adding a static v4 via v6 route.
AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/32 br0 2001:cafe::10 src=1.1.2.88], [0], [OK
])

dnl Checking that a local route for added IP was successfully installed.
AT_CHECK([ovs-appctl ovs/route/show | grep br0 | sort], [0], [dnl
Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
Cached: 2001:cafe::/64 dev br0 SRC 2001:cafe::88 local
User: 1.1.2.92/32 dev br0 GW 2001:cafe::10 SRC 1.1.2.88
])

AT_CHECK([ovs-ofctl add-flow br0 action=normal])
AT_CHECK([ovs-ofctl add-flow int-br action=normal])

AT_CHECK([ovs-vsctl -- set Interface p0 options:tx_pcap=p0.pcap])

dnl Check that v4-over-v6 route is used in the trace and that a tunnel neighbor
dnl lookup miss generates ND and not an ARP.
AT_CHECK([ovs-appctl ofproto/trace int-br in_port=LOCAL \
                | grep -E 'tunnel|neighbor|actions'], [0], [dnl
     -> output to native tunnel
     -> tunneling to 2001:cafe::10 via br0
     -> neighbor cache miss for 2001:cafe::10 on bridge br0, sending ND request
Datapath actions: drop
])

dnl Check that the correct Neighbor Solicitation was sent out via p0.
m4_define([ND_NS_PACKET], [m4_joinall([,],
  [eth_src=aa:55:aa:55:00:00,eth_dst=33:33:ff:00:00:10,eth_type=0x86dd],
  [ipv6_src=2001:cafe::88,ipv6_dst=ff02::1:ff00:10],
  [nw_proto=58,nw_ttl=255,nw_frag=no],
  [icmpv6_type=135,icmpv6_code=0],
  [nd_target=2001:cafe::10,nd_options_type=1,nd_sll=aa:55:aa:55:00:00])])

OVS_WAIT_UNTIL([test $(ovs-pcap p0.pcap \
    | grep -c "$(ovs-ofctl compose-packet --bare 'ND_NS_PACKET')") -eq 1])

dnl Now send a Neighbor Advertisement from p0 which has two effects:
dnl 1. The neighbor cache will learn that 2001:cafe::10 is at f8:bc:12:44:34:b6.
dnl 2. The br0 mac learning will learn that f8:bc:12:44:34:b6 is on p0.
AT_CHECK([ovs-appctl netdev-dummy/receive p0 dnl
 'recirc_id(0),in_port(1),dnl
  eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x86dd),dnl
  ipv6(src=2001:cafe::10,dst=2001:cafe::88,label=0,proto=58,tclass=0,hlimit=255,frag=no),dnl
  icmpv6(type=136,code=0),dnl
  nd(target=2001:cafe::10,sll=00:00:00:00:00:00,tll=f8:bc:12:44:34:b6)'
])

dnl Check that v4-over-v6 route is used in the trace and the tunnel is working.
AT_CHECK([ovs-appctl ofproto/trace int-br in_port=LOCAL \
                | grep -E 'tunnel|neighbor|actions'], [0], [dnl
     -> output to native tunnel
     -> tunneling to 2001:cafe::10 via br0
     -> tunneling from aa:55:aa:55:00:00 1.1.2.88 to f8:bc:12:44:34:b6 2001:cafe::10
Datapath actions: tnl_push(tnl_port(6081),header(size=50,type=5,dnl
eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),dnl
ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x4000),dnl
udp(src=0,dst=6081,csum=0x0),geneve(vni=0x7b)),out_port(100)),1
])

dnl Now check that the packet is actually encapsulated and delivered.
packet=50540000000a5054000000091234
eth=f8bc124434b6aa55aa5500000800
ip4=450000320000400040113406010102580101025c
dnl Source port is based on a packet hash, so it may differ depending on the
dnl compiler flags and CPU type.  Masked with '....'.
udp=....17c1001e0000
geneve=0000655800007b00
encap=${eth}${ip4}${udp}${geneve}
dnl Output to the tunnel from the int-br internal port.
dnl Checking that the packet arrived and it was correctly encapsulated.
AT_CHECK([ovs-appctl netdev-dummy/receive int-br "${packet}"])
OVS_WAIT_UNTIL([test $(ovs-pcap p0.pcap | grep -c "${encap}${packet}") -eq 1])

dnl Sending again to exercise the non-miss upcall path.
AT_CHECK([ovs-appctl netdev-dummy/receive int-br "${packet}"])
OVS_WAIT_UNTIL([test $(ovs-pcap p0.pcap | grep -c "${encap}${packet}") -eq 2])

dnl Finally, checking that the datapath flow is also correct.
AT_CHECK([ovs-appctl dpctl/dump-flows | grep tnl_push \
            | strip_ufid | strip_used], [0], [dnl
recirc_id(0),in_port(2),packet_type(ns=0,id=0),dnl
eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x1234), dnl
packets:1, bytes:14, used:0.0s, dnl
actions:tnl_push(tnl_port(6081),header(size=50,type=5,dnl
eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),dnl
ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x4000),dnl
udp(src=0,dst=6081,csum=0x0),geneve(vni=0x7b)),out_port(100)),1
])

OVS_VSWITCHD_STOP
AT_CLEANUP
---

With the current version of a patch it fails on the tunnel neighbor
lookup because it looks for IPv4 neighbor and there are no IPv4
neighbors in this setup, all the neighbors are IPv6-only.

Best regards, Ilya Maximets.
Ilya Maximets May 30, 2024, 6:29 p.m. UTC | #2
On 5/30/24 20:17, Ilya Maximets wrote:
> On 5/30/24 01:27, William Tu wrote:
>> Add route-table support for ipv4 dst via ipv6. One use case is BGP
>> unnumbered, a mechanism that establishes peering sessions without the
>> need to explicitly configure IPv4 addresses on the interfaces involved
>> in the peering. Without using IPv4 address assignments, it uses
>> link-local IPv6 addresses of the directly connected neighbors for
>> peering purposes. For example, BGP might install the following route:
>> $ ip route get 100.87.18.3
>>     100.87.18.3 via inet6 fe80::920a:84ff:fe9e:9570 \
>>     dev br-phy src 100.87.18.6
>>
>> Note that the v6 addr fe80::920a:84ff:fe9e:9570 is not being used in
>> the packet header, but only used for lookup the out dev br-phy.
>> Currently OVS can only support either all-ipv4 or all-ipv6, the patch
>> adds support for such use case.
>>
>> Reported-at: https://mail.openvswitch.org/pipermail/ovs-discuss/2024-January/052908.html
>> Acked-by: Simon Horman <horms@ovn.org>
>> Signed-off-by: William Tu <witu@nvidia.com>
>> ---
>> v5: fix minor CI failure
>> v4: feedback from Ilya
>> - add route del test case, wrap around test width
>> - not set neighbor cache manually
>> - on br-phy, use /32 on address in steead of /24
>> compare v3 and v4
>> https://github.com/williamtu/ovs/compare/router..router-v4
>> v3: add vxlan test, remove rfc
>> v2: fix CI error
>> ---
> 
> <snip>
> 
>> diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
>> index 508737c53ec6..7266f0990570 100644
>> --- a/tests/tunnel-push-pop.at
>> +++ b/tests/tunnel-push-pop.at
>> @@ -196,6 +196,69 @@ OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep 100022eb0000000120000237 | wc -l`
>>  OVS_VSWITCHD_STOP
>>  AT_CLEANUP
>>  
>> +AT_SETUP([tunnel_push_pop - v4 via v6 route])
>> +
>> +OVS_VSWITCHD_START([add-port br0 p0 -- set Interface p0 type=dummy ofport_request=1 other-config:hwaddr=aa:55:aa:55:00:00])
>> +AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy], [0])
>> +AT_CHECK([ovs-vsctl add-port int-br t1 -- set Interface t1 type=vxlan \
>> +                       options:remote_ip=1.1.2.92 options:key=123 ofport_request=1\
>> +                       ], [0])
>> +
>> +AT_CHECK([ovs-appctl dpif/show], [0], [dnl
>> +dummy@ovs-dummy: hit:0 missed:0
>> +  br0:
>> +    br0 65534/100: (dummy-internal)
>> +    p0 1/1: (dummy)
>> +  int-br:
>> +    int-br 65534/2: (dummy-internal)
>> +    t1 1/4789: (vxlan: key=123, remote_ip=1.1.2.92)
>> +])
>> +
>> +AT_CHECK([ovs-ofctl add-flow br0 action=normal])
>> +
>> +dnl Setup dummy interface IP addresses.
>> +AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/32], [0], [OK
>> +])
>> +AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/64], [0], [OK
>> +])
>> +dnl Add a static v4 via v6 route
>> +AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/32 br0 2001:cafe::10 src=1.1.2.89], [0], [OK
>> +])
>> +
>> +AT_CHECK([ovs-appctl ovs/route/show | grep br0 | sort], [0], [dnl
>> +Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
>> +Cached: 2001:cafe::/64 dev br0 SRC 2001:cafe::88 local
>> +User: 1.1.2.92/32 dev br0 GW 2001:cafe::10 SRC 1.1.2.89
>> +])
>> +
>> +dnl Check ARP Snoop
>> +AT_CHECK([ovs-appctl netdev-dummy/receive p0 'recirc_id(0),in_port(100),dnl
>> +eth(src=f8:bc:12:44:34:b6,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),dnl
>> +arp(sip=1.1.2.92,tip=1.1.2.88,op=2,sha=f8:bc:12:44:34:b6,tha=00:00:00:00:00:00)'])
> 
> This is still not a correct test, we would never receive an ARP
> from an IPv6-only network.  This must be an IPv6 NA packet instead.
> 
> All in all, I'd expect the following test to work without modifications
> (unless I mistyped something):
> 
> ---
> AT_SETUP([tunnel_push_pop - v4 via v6 route])
> 
> OVS_VSWITCHD_START(
>     [add-port br0 p0 \
>      -- set Interface p0 type=dummy ofport_request=1 \
>                          other-config:hwaddr=aa:55:aa:55:00:00])
> AT_CHECK([ovs-appctl vlog/set dpif_netdev:dbg])
> AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy])
> AT_CHECK([ovs-vsctl add-port int-br t2 \
>           -- set Interface t2 type=geneve \
>                               options:remote_ip=1.1.2.92 \
>                               options:key=123 ofport_request=2])
> 
> dnl Setup IP addresses.
> AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/32], [0], [OK
> ])
> AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/64], [0], [OK
> ])
> dnl Adding a static v4 via v6 route.
> AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/32 br0 2001:cafe::10 src=1.1.2.88], [0], [OK
> ])
> 
> dnl Checking that a local route for added IP was successfully installed.
> AT_CHECK([ovs-appctl ovs/route/show | grep br0 | sort], [0], [dnl
> Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
> Cached: 2001:cafe::/64 dev br0 SRC 2001:cafe::88 local
> User: 1.1.2.92/32 dev br0 GW 2001:cafe::10 SRC 1.1.2.88
> ])
> 
> AT_CHECK([ovs-ofctl add-flow br0 action=normal])
> AT_CHECK([ovs-ofctl add-flow int-br action=normal])
> 
> AT_CHECK([ovs-vsctl -- set Interface p0 options:tx_pcap=p0.pcap])
> 
> dnl Check that v4-over-v6 route is used in the trace and that a tunnel neighbor
> dnl lookup miss generates ND and not an ARP.
> AT_CHECK([ovs-appctl ofproto/trace int-br in_port=LOCAL \
>                 | grep -E 'tunnel|neighbor|actions'], [0], [dnl
>      -> output to native tunnel
>      -> tunneling to 2001:cafe::10 via br0
>      -> neighbor cache miss for 2001:cafe::10 on bridge br0, sending ND request
> Datapath actions: drop
> ])
> 
> dnl Check that the correct Neighbor Solicitation was sent out via p0.
> m4_define([ND_NS_PACKET], [m4_joinall([,],
>   [eth_src=aa:55:aa:55:00:00,eth_dst=33:33:ff:00:00:10,eth_type=0x86dd],
>   [ipv6_src=2001:cafe::88,ipv6_dst=ff02::1:ff00:10],
>   [nw_proto=58,nw_ttl=255,nw_frag=no],
>   [icmpv6_type=135,icmpv6_code=0],
>   [nd_target=2001:cafe::10,nd_options_type=1,nd_sll=aa:55:aa:55:00:00])])
> 
> OVS_WAIT_UNTIL([test $(ovs-pcap p0.pcap \
>     | grep -c "$(ovs-ofctl compose-packet --bare 'ND_NS_PACKET')") -eq 1])
> 
> dnl Now send a Neighbor Advertisement from p0 which has two effects:
> dnl 1. The neighbor cache will learn that 2001:cafe::10 is at f8:bc:12:44:34:b6.
> dnl 2. The br0 mac learning will learn that f8:bc:12:44:34:b6 is on p0.
> AT_CHECK([ovs-appctl netdev-dummy/receive p0 dnl
>  'recirc_id(0),in_port(1),dnl
>   eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x86dd),dnl
>   ipv6(src=2001:cafe::10,dst=2001:cafe::88,label=0,proto=58,tclass=0,hlimit=255,frag=no),dnl
>   icmpv6(type=136,code=0),dnl
>   nd(target=2001:cafe::10,sll=00:00:00:00:00:00,tll=f8:bc:12:44:34:b6)'
> ])
> 
> dnl Check that v4-over-v6 route is used in the trace and the tunnel is working.
> AT_CHECK([ovs-appctl ofproto/trace int-br in_port=LOCAL \
>                 | grep -E 'tunnel|neighbor|actions'], [0], [dnl
>      -> output to native tunnel
>      -> tunneling to 2001:cafe::10 via br0
>      -> tunneling from aa:55:aa:55:00:00 1.1.2.88 to f8:bc:12:44:34:b6 2001:cafe::10

Though, I think, we should make this output less confusing, e.g.
    -> tunneling from aa:55:aa:55:00:00 1.1.2.88 to 1.1.2.92 via f8:bc:12:44:34:b6 2001:cafe::10
Or something like that.  Otherwise it's lacking the actual destination
IP information and may be hard to understand.

> Datapath actions: tnl_push(tnl_port(6081),header(size=50,type=5,dnl
> eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),dnl
> ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x4000),dnl
> udp(src=0,dst=6081,csum=0x0),geneve(vni=0x7b)),out_port(100)),1
> ])
> 
> dnl Now check that the packet is actually encapsulated and delivered.
> packet=50540000000a5054000000091234
> eth=f8bc124434b6aa55aa5500000800
> ip4=450000320000400040113406010102580101025c
> dnl Source port is based on a packet hash, so it may differ depending on the
> dnl compiler flags and CPU type.  Masked with '....'.
> udp=....17c1001e0000
> geneve=0000655800007b00
> encap=${eth}${ip4}${udp}${geneve}
> dnl Output to the tunnel from the int-br internal port.
> dnl Checking that the packet arrived and it was correctly encapsulated.
> AT_CHECK([ovs-appctl netdev-dummy/receive int-br "${packet}"])
> OVS_WAIT_UNTIL([test $(ovs-pcap p0.pcap | grep -c "${encap}${packet}") -eq 1])
> 
> dnl Sending again to exercise the non-miss upcall path.
> AT_CHECK([ovs-appctl netdev-dummy/receive int-br "${packet}"])
> OVS_WAIT_UNTIL([test $(ovs-pcap p0.pcap | grep -c "${encap}${packet}") -eq 2])
> 
> dnl Finally, checking that the datapath flow is also correct.
> AT_CHECK([ovs-appctl dpctl/dump-flows | grep tnl_push \
>             | strip_ufid | strip_used], [0], [dnl
> recirc_id(0),in_port(2),packet_type(ns=0,id=0),dnl
> eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x1234), dnl
> packets:1, bytes:14, used:0.0s, dnl
> actions:tnl_push(tnl_port(6081),header(size=50,type=5,dnl
> eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),dnl
> ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x4000),dnl
> udp(src=0,dst=6081,csum=0x0),geneve(vni=0x7b)),out_port(100)),1
> ])
> 
> OVS_VSWITCHD_STOP
> AT_CLEANUP
> ---
> 
> With the current version of a patch it fails on the tunnel neighbor
> lookup because it looks for IPv4 neighbor and there are no IPv4
> neighbors in this setup, all the neighbors are IPv6-only.
> 
> Best regards, Ilya Maximets.
William Tu June 1, 2024, 7:44 p.m. UTC | #3
Hi Ilya,

thanks for your review!
The patch passes my githug-ci
https://github.com/williamtu/ovs/actions/runs/9293675528/job/25577358954

I don't know why it fails/skipped at
https://github.com/ovsrobot/ovs/actions/runs/9294537471

On 5/30/24 11:17 AM, Ilya Maximets wrote:
> <snip>
>
>> diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
>> index 508737c53ec6..7266f0990570 100644
>> --- a/tests/tunnel-push-pop.at
>> +++ b/tests/tunnel-push-pop.at
>> @@ -196,6 +196,69 @@ OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep 100022eb0000000120000237 | wc -l`
>>   OVS_VSWITCHD_STOP
>>   AT_CLEANUP
>>
>> +AT_SETUP([tunnel_push_pop - v4 via v6 route])
>> +
>> +OVS_VSWITCHD_START([add-port br0 p0 -- set Interface p0 type=dummy ofport_request=1 other-config:hwaddr=aa:55:aa:55:00:00])
>> +AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy], [0])
>> +AT_CHECK([ovs-vsctl add-port int-br t1 -- set Interface t1 type=vxlan \
>> +                       options:remote_ip=1.1.2.92 options:key=123 ofport_request=1\
>> +                       ], [0])
>> +
>> +AT_CHECK([ovs-appctl dpif/show], [0], [dnl
>> +dummy@ovs-dummy: hit:0 missed:0
>> +  br0:
>> +    br0 65534/100: (dummy-internal)
>> +    p0 1/1: (dummy)
>> +  int-br:
>> +    int-br 65534/2: (dummy-internal)
>> +    t1 1/4789: (vxlan: key=123, remote_ip=1.1.2.92)
>> +])
>> +
>> +AT_CHECK([ovs-ofctl add-flow br0 action=normal])
>> +
>> +dnl Setup dummy interface IP addresses.
>> +AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/32], [0], [OK
>> +])
>> +AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/64], [0], [OK
>> +])
>> +dnl Add a static v4 via v6 route
>> +AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/32 br0 2001:cafe::10 src=1.1.2.89], [0], [OK
>> +])
>> +
>> +AT_CHECK([ovs-appctl ovs/route/show | grep br0 | sort], [0], [dnl
>> +Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
>> +Cached: 2001:cafe::/64 dev br0 SRC 2001:cafe::88 local
>> +User: 1.1.2.92/32 dev br0 GW 2001:cafe::10 SRC 1.1.2.89
>> +])
>> +
>> +dnl Check ARP Snoop
>> +AT_CHECK([ovs-appctl netdev-dummy/receive p0 'recirc_id(0),in_port(100),dnl
>> +eth(src=f8:bc:12:44:34:b6,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),dnl
>> +arp(sip=1.1.2.92,tip=1.1.2.88,op=2,sha=f8:bc:12:44:34:b6,tha=00:00:00:00:00:00)'])
> This is still not a correct test, we would never receive an ARP
> from an IPv6-only network.  This must be an IPv6 NA packet instead.
>
> All in all, I'd expect the following test to work without modifications
> (unless I mistyped something):
thanks a lot, this is very clear!
I applied your test and hit issue below
> ---
> AT_SETUP([tunnel_push_pop - v4 via v6 route])
>
> OVS_VSWITCHD_START(
>      [add-port br0 p0 \
>       -- set Interface p0 type=dummy ofport_request=1 \
>                           other-config:hwaddr=aa:55:aa:55:00:00])
> AT_CHECK([ovs-appctl vlog/set dpif_netdev:dbg])
> AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy])
> AT_CHECK([ovs-vsctl add-port int-br t2 \
>            -- set Interface t2 type=geneve \
>                                options:remote_ip=1.1.2.92 \
>                                options:key=123 ofport_request=2])
>
> dnl Setup IP addresses.
> AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/32], [0], [OK
> ])
> AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/64], [0], [OK
> ])
> dnl Adding a static v4 via v6 route.
> AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/32 br0 2001:cafe::10 src=1.1.2.88], [0], [OK
> ])
>
> dnl Checking that a local route for added IP was successfully installed.
> AT_CHECK([ovs-appctl ovs/route/show | grep br0 | sort], [0], [dnl
> Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
> Cached: 2001:cafe::/64 dev br0 SRC 2001:cafe::88 local
> User: 1.1.2.92/32 dev br0 GW 2001:cafe::10 SRC 1.1.2.88
> ])
>
> AT_CHECK([ovs-ofctl add-flow br0 action=normal])
> AT_CHECK([ovs-ofctl add-flow int-br action=normal])
>
> AT_CHECK([ovs-vsctl -- set Interface p0 options:tx_pcap=p0.pcap])
>
> dnl Check that v4-over-v6 route is used in the trace and that a tunnel neighbor
> dnl lookup miss generates ND and not an ARP.
> AT_CHECK([ovs-appctl ofproto/trace int-br in_port=LOCAL \
>                  | grep -E 'tunnel|neighbor|actions'], [0], [dnl
>       -> output to native tunnel
>       -> tunneling to 2001:cafe::10 via br0
>       -> neighbor cache miss for 2001:cafe::10 on bridge br0, sending ND request
> Datapath actions: drop
> ])
hitting an error here:
so the native tunnel is looking for 1.1.2.92, not the ipv6 address

+++ /root/ovs/tests/testsuite.dir/at-groups/815/stdout    2024-06-01 
17:12:56.836000000 +0300
@@ -1,5 +1,5 @@
       -> output to native tunnel
-     -> tunneling to 2001:cafe::10 via br0
-     -> neighbor cache miss for 2001:cafe::10 on bridge br0, sending ND 
request
+     -> tunneling to 1.1.2.92 via br0
+     -> neighbor cache miss for 1.1.2.92 on bridge br0, sending ARP request
  Datapath actions: drop

I think I still need to change some code in native tunnel...
here although "remote_ip" is 1.1.2.92, but we want to change it to 
2001:cafe::10?

>
> dnl Check that the correct Neighbor Solicitation was sent out via p0.
> m4_define([ND_NS_PACKET], [m4_joinall([,],
>    [eth_src=aa:55:aa:55:00:00,eth_dst=33:33:ff:00:00:10,eth_type=0x86dd],
>    [ipv6_src=2001:cafe::88,ipv6_dst=ff02::1:ff00:10],
>    [nw_proto=58,nw_ttl=255,nw_frag=no],
>    [icmpv6_type=135,icmpv6_code=0],
>    [nd_target=2001:cafe::10,nd_options_type=1,nd_sll=aa:55:aa:55:00:00])])
>
> OVS_WAIT_UNTIL([test $(ovs-pcap p0.pcap \
>      | grep -c "$(ovs-ofctl compose-packet --bare 'ND_NS_PACKET')") -eq 1])
>
> dnl Now send a Neighbor Advertisement from p0 which has two effects:
> dnl 1. The neighbor cache will learn that 2001:cafe::10 is at f8:bc:12:44:34:b6.
but in the end, it's ipv4 1.1.2.92

> dnl 2. The br0 mac learning will learn that f8:bc:12:44:34:b6 is on p0.
> AT_CHECK([ovs-appctl netdev-dummy/receive p0 dnl
>   'recirc_id(0),in_port(1),dnl
>    eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x86dd),dnl
>    ipv6(src=2001:cafe::10,dst=2001:cafe::88,label=0,proto=58,tclass=0,hlimit=255,frag=no),dnl
>    icmpv6(type=136,code=0),dnl
>    nd(target=2001:cafe::10,sll=00:00:00:00:00:00,tll=f8:bc:12:44:34:b6)'
> ])
>
> dnl Check that v4-over-v6 route is used in the trace and the tunnel is working.
> AT_CHECK([ovs-appctl ofproto/trace int-br in_port=LOCAL \
>                  | grep -E 'tunnel|neighbor|actions'], [0], [dnl
>       -> output to native tunnel
>       -> tunneling to 2001:cafe::10 via br0
>       -> tunneling from aa:55:aa:55:00:00 1.1.2.88 to f8:bc:12:44:34:b6 2001:cafe::10
> Datapath actions: tnl_push(tnl_port(6081),header(size=50,type=5,dnl
> eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),dnl
> ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x4000),dnl
> udp(src=0,dst=6081,csum=0x0),geneve(vni=0x7b)),out_port(100)),1
> ])
So here we want to tunneling to 2001:cafe::10, but when push the header, 
we want to push ipv4.
Let me think through again...

Thanks
William
>
> dnl Now check that the packet is actually encapsulated and delivered.
> packet=50540000000a5054000000091234
> eth=f8bc124434b6aa55aa5500000800
> ip4=450000320000400040113406010102580101025c
> dnl Source port is based on a packet hash, so it may differ depending on the
> dnl compiler flags and CPU type.  Masked with '....'.
> udp=....17c1001e0000
> geneve=0000655800007b00
> encap=${eth}${ip4}${udp}${geneve}
> dnl Output to the tunnel from the int-br internal port.
> dnl Checking that the packet arrived and it was correctly encapsulated.
> AT_CHECK([ovs-appctl netdev-dummy/receive int-br "${packet}"])
> OVS_WAIT_UNTIL([test $(ovs-pcap p0.pcap | grep -c "${encap}${packet}") -eq 1])
>
> dnl Sending again to exercise the non-miss upcall path.
> AT_CHECK([ovs-appctl netdev-dummy/receive int-br "${packet}"])
> OVS_WAIT_UNTIL([test $(ovs-pcap p0.pcap | grep -c "${encap}${packet}") -eq 2])
>
> dnl Finally, checking that the datapath flow is also correct.
> AT_CHECK([ovs-appctl dpctl/dump-flows | grep tnl_push \
>              | strip_ufid | strip_used], [0], [dnl
> recirc_id(0),in_port(2),packet_type(ns=0,id=0),dnl
> eth(src=50:54:00:00:00:09,dst=50:54:00:00:00:0a),eth_type(0x1234), dnl
> packets:1, bytes:14, used:0.0s, dnl
> actions:tnl_push(tnl_port(6081),header(size=50,type=5,dnl
> eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),dnl
> ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x4000),dnl
> udp(src=0,dst=6081,csum=0x0),geneve(vni=0x7b)),out_port(100)),1
> ])
>
> OVS_VSWITCHD_STOP
> AT_CLEANUP
> ---
>
> With the current version of a patch it fails on the tunnel neighbor
> lookup because it looks for IPv4 neighbor and there are no IPv4
> neighbors in this setup, all the neighbors are IPv6-only.
>
> Best regards, Ilya Maximets.
diff mbox series

Patch

diff --git a/lib/ovs-router.c b/lib/ovs-router.c
index 3d84c9a30a8f..fbf67b666993 100644
--- a/lib/ovs-router.c
+++ b/lib/ovs-router.c
@@ -184,6 +184,11 @@  verify_prefsrc(const struct in6_addr *ip6_dst,
         goto out;
     }
 
+    /* Skip the check if not the same address family */
+    if (!IN6_IS_ADDR_V4MAPPED(ip6_dst) && IN6_IS_ADDR_V4MAPPED(prefsrc)) {
+        goto out;
+    }
+
     for (i = 0; i < n_in6; i++) {
         struct in6_addr a1, a2;
         a1 = ipv6_addr_bitand(ip6_dst, &mask[i]);
@@ -415,7 +420,6 @@  ovs_router_add(struct unixctl_conn *conn, int argc,
     unsigned int plen;
     ovs_be32 src = 0;
     ovs_be32 gw = 0;
-    bool is_ipv6;
     ovs_be32 ip;
     int err;
     int i;
@@ -423,9 +427,8 @@  ovs_router_add(struct unixctl_conn *conn, int argc,
     if (scan_ipv4_route(argv[1], &ip, &plen)) {
         in6_addr_set_mapped_ipv4(&ip6, ip);
         plen += 96;
-        is_ipv6 = false;
     } else if (scan_ipv6_route(argv[1], &ip6, &plen)) {
-        is_ipv6 = true;
+        ;
     } else {
         unixctl_command_reply_error(conn,
                                     "Invalid 'ip/plen' parameter");
@@ -438,21 +441,21 @@  ovs_router_add(struct unixctl_conn *conn, int argc,
             continue;
         }
 
-        if (is_ipv6) {
-            if (ovs_scan(argv[i], "src="IPV6_SCAN_FMT, src6_s) &&
-                ipv6_parse(src6_s, &src6)) {
-                continue;
-            }
-            if (ipv6_parse(argv[i], &gw6)) {
-                continue;
-            }
-        } else {
-            if (ovs_scan(argv[i], "src="IP_SCAN_FMT, IP_SCAN_ARGS(&src))) {
-                continue;
-            }
-            if (ip_parse(argv[i], &gw)) {
-                continue;
-            }
+        if (ovs_scan(argv[i], "src="IPV6_SCAN_FMT, src6_s) &&
+            ipv6_parse(src6_s, &src6)) {
+            continue;
+        }
+
+        if (ipv6_parse(argv[i], &gw6)) {
+            continue;
+        }
+
+        if (ovs_scan(argv[i], "src="IP_SCAN_FMT, IP_SCAN_ARGS(&src))) {
+            continue;
+        }
+
+        if (ip_parse(argv[i], &gw)) {
+            continue;
         }
 
         unixctl_command_reply_error(conn,
diff --git a/lib/route-table.c b/lib/route-table.c
index f1fe32714e8d..58412711888f 100644
--- a/lib/route-table.c
+++ b/lib/route-table.c
@@ -232,6 +232,7 @@  route_table_parse(struct ofpbuf *buf, struct route_table_msg *change)
         [RTA_OIF] = { .type = NL_A_U32, .optional = true },
         [RTA_GATEWAY] = { .type = NL_A_U32, .optional = true },
         [RTA_MARK] = { .type = NL_A_U32, .optional = true },
+        [RTA_VIA] = { .type = NL_A_UNSPEC, .optional = true },
         [RTA_PREFSRC] = { .type = NL_A_U32, .optional = true },
         [RTA_TABLE] = { .type = NL_A_U32, .optional = true },
     };
@@ -241,6 +242,7 @@  route_table_parse(struct ofpbuf *buf, struct route_table_msg *change)
         [RTA_OIF] = { .type = NL_A_U32, .optional = true },
         [RTA_MARK] = { .type = NL_A_U32, .optional = true },
         [RTA_GATEWAY] = { .type = NL_A_IPV6, .optional = true },
+        [RTA_VIA] = { .type = NL_A_UNSPEC, .optional = true },
         [RTA_PREFSRC] = { .type = NL_A_IPV6, .optional = true },
         [RTA_TABLE] = { .type = NL_A_U32, .optional = true },
     };
@@ -333,6 +335,25 @@  route_table_parse(struct ofpbuf *buf, struct route_table_msg *change)
                     nl_attr_get_in6_addr(attrs[RTA_PREFSRC]);
             }
         }
+        if (attrs[RTA_VIA]) {
+            const struct rtvia *via;
+            ovs_be32 gw;
+
+            via = nl_attr_get(attrs[RTA_VIA]);
+            switch (via->rtvia_family) {
+            case AF_INET:
+                gw = *(ALIGNED_CAST(ovs_be32 *, via->rtvia_addr));
+                in6_addr_set_mapped_ipv4(&change->rd.rta_gw, gw);
+                break;
+            case AF_INET6:
+                change->rd.rta_gw = *(ALIGNED_CAST(struct in6_addr *,
+                                                    via->rtvia_addr));
+                break;
+            default:
+                VLOG_WARN("Unknown address family %d\n", via->rtvia_family);
+                return 0;
+            }
+        }
         if (attrs[RTA_GATEWAY]) {
             if (ipv4) {
                 ovs_be32 gw;
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 7c495089509f..3694a8430382 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -3612,7 +3612,8 @@  tnl_route_lookup_flow(const struct xlate_ctx *ctx,
     }
 
     if (ipv6_addr_is_set(&gw) &&
-        (!IN6_IS_ADDR_V4MAPPED(&gw) || in6_addr_get_mapped_ipv4(&gw))) {
+        (!IN6_IS_ADDR_V4MAPPED(&gw) || in6_addr_get_mapped_ipv4(&gw)) &&
+        (!IN6_IS_ADDR_V4MAPPED(&dst) || IN6_IS_ADDR_V4MAPPED(&gw))) {
         *ip = gw;
     } else {
         *ip = dst;
diff --git a/tests/ovs-router.at b/tests/ovs-router.at
index b3314b3dff0d..5e5e1d013464 100644
--- a/tests/ovs-router.at
+++ b/tests/ovs-router.at
@@ -109,6 +109,57 @@  User: 2001:db8:beef::14/128 MARK 14 dev br0 GW 2001:db8:cafe::1 SRC 2001:db8:caf
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([appctl - route/add and del with ipv4 via ipv6])
+AT_KEYWORDS([ovs_router])
+OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=dummy])
+AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 192.168.9.2/24], [0], [OK
+])
+AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 192.168.9.3/24], [0], [OK
+])
+
+dnl defualt pick the first IP, 192.168.9.2, as src
+AT_CHECK([ovs-appctl ovs/route/add 192.168.9.10/32 br0 dnl
+fe80::920a:84ff:beef:9510], [0], [OK
+])
+AT_CHECK([ovs-appctl ovs/route/add 192.168.9.11/32 br0 dnl
+fe80::920a:84ff:beef:9511 src=192.168.9.2], [0], [OK
+])
+AT_CHECK([ovs-appctl ovs/route/add 192.168.9.12/32 br0 dnl
+fe80::920a:84ff:beef:9512 src=192.168.9.3], [0], [OK
+])
+AT_CHECK([ovs-appctl ovs/route/add 192.168.9.13/32 br0 dnl
+fe80::920a:84ff:beef:9513 pkt_mark=13 src=192.168.9.3], [0], [OK
+])
+
+AT_CHECK([ovs-appctl ovs/route/show | grep User | grep beef | sort], [0], [dnl
+User: 192.168.9.10/32 dev br0 GW fe80::920a:84ff:beef:9510 SRC 192.168.9.2
+User: 192.168.9.11/32 dev br0 GW fe80::920a:84ff:beef:9511 SRC 192.168.9.2
+User: 192.168.9.12/32 dev br0 GW fe80::920a:84ff:beef:9512 SRC 192.168.9.3
+User: 192.168.9.13/32 MARK 13 dev br0 GW fe80::920a:84ff:beef:9513 SRC 192.168.9.3
+])
+
+dnl DST and SRC can be in different domain
+AT_CHECK([ovs-appctl ovs/route/add 192.168.10.12/32 br0 dnl
+fe80::920a:84ff:face:9512 src=192.168.9.3], [0], [OK
+])
+AT_CHECK([ovs-appctl ovs/route/show | grep User | grep face | sort], [0], [dnl
+User: 192.168.10.12/32 dev br0 GW fe80::920a:84ff:face:9512 SRC 192.168.9.3
+])
+
+dnl Delete route
+AT_CHECK([ovs-appctl ovs/route/del 192.168.9.10/32], [0], [OK
+])
+AT_CHECK([ovs-appctl ovs/route/del 192.168.9.11/32], [0], [OK
+])
+AT_CHECK([ovs-appctl ovs/route/del 192.168.9.12/32], [0], [OK
+])
+AT_CHECK([ovs-appctl ovs/route/del 192.168.9.13/32 pkt_mark=13], [0], [OK
+])
+OVS_WAIT_UNTIL([test $(ovs-appctl ovs/route/show | grep -c '192.168.9.1') -eq 0 ])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_SETUP([appctl - route/lookup])
 AT_KEYWORDS([ovs_router])
 OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1 type=dummy])
diff --git a/tests/system-route.at b/tests/system-route.at
index c0ecad6cfb49..dbb2afa5a8fe 100644
--- a/tests/system-route.at
+++ b/tests/system-route.at
@@ -65,6 +65,45 @@  Cached: fc00:db8:beef::13/128 dev br0 GW fc00:db8:cafe::1 SRC fc00:db8:cafe::2])
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([ovs-route - add and del system route ipv4 via ipv6])
+AT_KEYWORDS([route])
+OVS_TRAFFIC_VSWITCHD_START()
+AT_CHECK([ip link set br0 up])
+
+AT_CHECK([ip addr add 192.168.9.2/24 dev br0], [0], [stdout])
+AT_CHECK([ip addr add 192.168.9.3/24 dev br0], [0], [stdout])
+
+AT_CHECK([ip -6 addr add fc00:db8:cafe::2/64 dev br0], [0], [stdout])
+AT_CHECK([ip -6 addr add fc00:db8:cafe::3/64 dev br0], [0], [stdout])
+
+AT_CHECK([ip route add 192.168.9.12/32 dev br0 dnl
+via inet6 fe80::920a:84ff:fe9e:9512 src 192.168.9.2], [0], [stdout])
+AT_CHECK([ip route add 192.168.9.13/32 dev br0 dnl
+via inet6 fe80::920a:84ff:fe9e:9513 src 192.168.9.3], [0], [stdout])
+
+OVS_WAIT_UNTIL_EQUAL([ovs-appctl ovs/route/show | grep -E '192.168.9.1[[23]]/32' | sort], [dnl
+Cached: 192.168.9.12/32 dev br0 GW fe80::920a:84ff:fe9e:9512 SRC 192.168.9.2
+Cached: 192.168.9.13/32 dev br0 GW fe80::920a:84ff:fe9e:9513 SRC 192.168.9.3])
+
+AT_CHECK([ovs-appctl ovs/route/add 192.168.9.14/32 br0 dnl
+fe80::920a:84ff:fe9e:9514 src=192.168.9.2], [0], [OK
+])
+
+OVS_WAIT_UNTIL_EQUAL([ovs-appctl ovs/route/show | grep -E '192.168.9.14/32' | sort], [dnl
+User: 192.168.9.14/32 dev br0 GW fe80::920a:84ff:fe9e:9514 SRC 192.168.9.2])
+
+dnl Delete system cached route
+AT_CHECK([ip route del 192.168.9.12/32 dev br0 dnl
+via inet6 fe80::920a:84ff:fe9e:9512 src 192.168.9.2], [0], [stdout])
+AT_CHECK([ip route del 192.168.9.13/32 dev br0 dnl
+via inet6 fe80::920a:84ff:fe9e:9513 src 192.168.9.3], [0], [stdout])
+
+OVS_WAIT_UNTIL([test $(ovs-appctl ovs/route/show | grep -c '192.168.9.12/32') -eq 0 ])
+OVS_WAIT_UNTIL([test $(ovs-appctl ovs/route/show | grep -c '192.168.9.13/32') -eq 0 ])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
 dnl Checks that OVS doesn't use routes from non-standard tables.
 AT_SETUP([ovs-route - route tables])
 AT_KEYWORDS([route])
diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
index 508737c53ec6..7266f0990570 100644
--- a/tests/tunnel-push-pop.at
+++ b/tests/tunnel-push-pop.at
@@ -196,6 +196,69 @@  OVS_WAIT_UNTIL([test `ovs-pcap p0.pcap | grep 100022eb0000000120000237 | wc -l`
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([tunnel_push_pop - v4 via v6 route])
+
+OVS_VSWITCHD_START([add-port br0 p0 -- set Interface p0 type=dummy ofport_request=1 other-config:hwaddr=aa:55:aa:55:00:00])
+AT_CHECK([ovs-vsctl add-br int-br -- set bridge int-br datapath_type=dummy], [0])
+AT_CHECK([ovs-vsctl add-port int-br t1 -- set Interface t1 type=vxlan \
+                       options:remote_ip=1.1.2.92 options:key=123 ofport_request=1\
+                       ], [0])
+
+AT_CHECK([ovs-appctl dpif/show], [0], [dnl
+dummy@ovs-dummy: hit:0 missed:0
+  br0:
+    br0 65534/100: (dummy-internal)
+    p0 1/1: (dummy)
+  int-br:
+    int-br 65534/2: (dummy-internal)
+    t1 1/4789: (vxlan: key=123, remote_ip=1.1.2.92)
+])
+
+AT_CHECK([ovs-ofctl add-flow br0 action=normal])
+
+dnl Setup dummy interface IP addresses.
+AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 1.1.2.88/32], [0], [OK
+])
+AT_CHECK([ovs-appctl netdev-dummy/ip6addr br0 2001:cafe::88/64], [0], [OK
+])
+dnl Add a static v4 via v6 route
+AT_CHECK([ovs-appctl ovs/route/add 1.1.2.92/32 br0 2001:cafe::10 src=1.1.2.89], [0], [OK
+])
+
+AT_CHECK([ovs-appctl ovs/route/show | grep br0 | sort], [0], [dnl
+Cached: 1.1.2.88/32 dev br0 SRC 1.1.2.88 local
+Cached: 2001:cafe::/64 dev br0 SRC 2001:cafe::88 local
+User: 1.1.2.92/32 dev br0 GW 2001:cafe::10 SRC 1.1.2.89
+])
+
+dnl Check ARP Snoop
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'recirc_id(0),in_port(100),dnl
+eth(src=f8:bc:12:44:34:b6,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),dnl
+arp(sip=1.1.2.92,tip=1.1.2.88,op=2,sha=f8:bc:12:44:34:b6,tha=00:00:00:00:00:00)'])
+
+ovs-appctl time/warp 1000
+ovs-appctl time/warp 1000
+
+AT_CHECK([ovs-appctl tnl/neigh/show | tail -n+3 | sort], [0], [dnl
+1.1.2.92                                      f8:bc:12:44:34:b6   br0
+])
+
+dnl Check VXLAN tunnel push
+AT_CHECK([ovs-ofctl add-flow int-br action=1])
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),dnl
+eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x0800),dnl
+ipv4(src=1.1.3.88,dst=1.1.3.112,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
+
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: tnl_push(tnl_port(4789),header(size=50,type=4,dnl
+eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),dnl
+ipv4(src=1.1.2.89,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x4000),dnl
+udp(src=0,dst=4789,csum=0x0),vxlan(flags=0x8000000,vni=0x7b)),out_port(100)),1
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_SETUP([tunnel_push_pop - action])
 
 OVS_VSWITCHD_START([add-port br0 p0 -- set Interface p0 type=dummy ofport_request=1 other-config:hwaddr=aa:55:aa:55:00:00])