#
 # Tests currently implemented:
 #
+# - pmtu_ipv4
+#      Set up two namespaces, A and B, with two paths between them over routers
+#      R1 and R2 (also implemented with namespaces), with different MTUs:
+#
+#        segment a_r1    segment b_r1          a_r1: 2000
+#      .--------------R1--------------.        a_r2: 1500
+#      A                               B       a_r3: 2000
+#      '--------------R2--------------'        a_r4: 1400
+#        segment a_r2    segment b_r2
+#
+#      Check that PMTU exceptions with the correct PMTU are created. Then
+#      decrease and increase the MTU of the local link for one of the paths,
+#      A to R1, checking that route exception PMTU changes accordingly over
+#      this path. Also check that locked exceptions are created when an ICMP
+#      message advertising a PMTU smaller than net.ipv4.route.min_pmtu is
+#      received
+#
+# - pmtu_ipv6
+#      Same as pmtu_ipv4, except for locked PMTU tests, using IPv6
+#
 # - pmtu_vti4_exception
 #      Set up vti tunnel on top of veth, with xfrm states and policies, in two
 #      namespaces with matching endpoints. Check that route exception is not
 which ping6 > /dev/null 2>&1 && ping6=$(which ping6) || ping6=$(which ping)
 
 tests="
+       pmtu_ipv4_exception             ipv4: PMTU exceptions
+       pmtu_ipv6_exception             ipv6: PMTU exceptions
        pmtu_vti6_exception             vti6: PMTU exceptions
        pmtu_vti4_exception             vti4: PMTU exceptions
        pmtu_vti4_default_mtu           vti4: default MTU assignment
 
 NS_A="ns-$(mktemp -u XXXXXX)"
 NS_B="ns-$(mktemp -u XXXXXX)"
+NS_R1="ns-$(mktemp -u XXXXXX)"
+NS_R2="ns-$(mktemp -u XXXXXX)"
 ns_a="ip netns exec ${NS_A}"
 ns_b="ip netns exec ${NS_B}"
+ns_r1="ip netns exec ${NS_R1}"
+ns_r2="ip netns exec ${NS_R2}"
+
+# Addressing and routing for tests with routers: four network segments, with
+# index SEGMENT between 1 and 4, a common prefix (PREFIX4 or PREFIX6) and an
+# identifier ID, which is 1 for hosts (A and B), 2 for routers (R1 and R2).
+# Addresses are:
+# - IPv4: PREFIX4.SEGMENT.ID (/24)
+# - IPv6: PREFIX6:SEGMENT::ID (/64)
+prefix4="192.168"
+prefix6="fd00"
+a_r1=1
+a_r2=2
+b_r1=3
+b_r2=4
+#      ns      peer    segment
+routing_addrs="
+       A       R1      ${a_r1}
+       A       R2      ${a_r2}
+       B       R1      ${b_r1}
+       B       R2      ${b_r2}
+"
+# Traffic from A to B goes through R1 by default, and through R2, if destined to
+# B's address on the b_r2 segment.
+# Traffic from B to A goes through R1.
+#      ns      destination             gateway
+routes="
+       A       default                 ${prefix4}.${a_r1}.2
+       A       ${prefix4}.${b_r2}.1    ${prefix4}.${a_r2}.2
+       B       default                 ${prefix4}.${b_r1}.2
+
+       A       default                 ${prefix6}:${a_r1}::2
+       A       ${prefix6}:${b_r2}::1   ${prefix6}:${a_r2}::2
+       B       default                 ${prefix6}:${b_r1}::2
+"
 
 veth4_a_addr="192.168.1.1"
 veth4_b_addr="192.168.1.2"
        err_buf=
 }
 
+# Find the auto-generated name for this namespace
+nsname() {
+       eval echo \$NS_$1
+}
+
 setup_namespaces() {
-       ip netns add ${NS_A} || return 1
-       ip netns add ${NS_B}
+       for n in ${NS_A} ${NS_B} ${NS_R1} ${NS_R2}; do
+               ip netns add ${n} || return 1
+       done
 }
 
 setup_veth() {
        setup_xfrm 6 ${veth6_a_addr} ${veth6_b_addr}
 }
 
+setup_routing() {
+       for i in ${NS_R1} ${NS_R2}; do
+               ip netns exec ${i} sysctl -q net/ipv4/ip_forward=1
+               ip netns exec ${i} sysctl -q net/ipv6/conf/all/forwarding=1
+       done
+
+       for i in ${routing_addrs}; do
+               [ "${ns}" = "" ]        && ns="${i}"            && continue
+               [ "${peer}" = "" ]      && peer="${i}"          && continue
+               [ "${segment}" = "" ]   && segment="${i}"
+
+               ns_name="$(nsname ${ns})"
+               peer_name="$(nsname ${peer})"
+               if="veth_${ns}-${peer}"
+               ifpeer="veth_${peer}-${ns}"
+
+               # Create veth links
+               ip link add ${if} up netns ${ns_name} type veth peer name ${ifpeer} netns ${peer_name} || return 1
+               ip -n ${peer_name} link set dev ${ifpeer} up
+
+               # Add addresses
+               ip -n ${ns_name}   addr add ${prefix4}.${segment}.1/24  dev ${if}
+               ip -n ${ns_name}   addr add ${prefix6}:${segment}::1/64 dev ${if}
+
+               ip -n ${peer_name} addr add ${prefix4}.${segment}.2/24  dev ${ifpeer}
+               ip -n ${peer_name} addr add ${prefix6}:${segment}::2/64 dev ${ifpeer}
+
+               ns=""; peer=""; segment=""
+       done
+
+       for i in ${routes}; do
+               [ "${ns}" = "" ]        && ns="${i}"            && continue
+               [ "${addr}" = "" ]      && addr="${i}"          && continue
+               [ "${gw}" = "" ]        && gw="${i}"
+
+               ns_name="$(nsname ${ns})"
+
+               ip -n ${ns_name} route add ${addr} via ${gw}
+
+               ns=""; addr=""; gw=""
+       done
+}
+
 setup() {
        [ "$(id -u)" -ne 0 ] && echo "  need to run as root" && return $ksft_skip
 
 
 cleanup() {
        [ ${cleanup_done} -eq 1 ] && return
-       ip netns del ${NS_A} 2> /dev/null
-       ip netns del ${NS_B} 2> /dev/null
+       for n in ${NS_A} ${NS_B} ${NS_R1} ${NS_R2}; do
+               ip netns del ${n} 2> /dev/null
+       done
        cleanup_done=1
 }
 
        return 1
 }
 
+test_pmtu_ipvX() {
+       family=${1}
+
+       setup namespaces routing || return 2
+
+       if [ ${family} -eq 4 ]; then
+               ping=ping
+               dst1="${prefix4}.${b_r1}.1"
+               dst2="${prefix4}.${b_r2}.1"
+       else
+               ping=${ping6}
+               dst1="${prefix6}:${b_r1}::1"
+               dst2="${prefix6}:${b_r2}::1"
+       fi
+
+       # Set up initial MTU values
+       mtu "${ns_a}"  veth_A-R1 2000
+       mtu "${ns_r1}" veth_R1-A 2000
+       mtu "${ns_r1}" veth_R1-B 1400
+       mtu "${ns_b}"  veth_B-R1 1400
+
+       mtu "${ns_a}"  veth_A-R2 2000
+       mtu "${ns_r2}" veth_R2-A 2000
+       mtu "${ns_r2}" veth_R2-B 1500
+       mtu "${ns_b}"  veth_B-R2 1500
+
+       # Create route exceptions
+       ${ns_a} ${ping} -q -M want -i 0.1 -w 2 -s 1800 ${dst1} > /dev/null
+       ${ns_a} ${ping} -q -M want -i 0.1 -w 2 -s 1800 ${dst2} > /dev/null
+
+       # Check that exceptions have been created with the correct PMTU
+       pmtu_1="$(route_get_dst_pmtu_from_exception "${ns_a}" ${dst1})"
+       check_pmtu_value "1400" "${pmtu_1}" "exceeding MTU" || return 1
+       pmtu_2="$(route_get_dst_pmtu_from_exception "${ns_a}" ${dst2})"
+       check_pmtu_value "1500" "${pmtu_2}" "exceeding MTU" || return 1
+
+       # Decrease local MTU below PMTU, check for PMTU decrease in route exception
+       mtu "${ns_a}"  veth_A-R1 1300
+       mtu "${ns_r1}" veth_R1-A 1300
+       pmtu_1="$(route_get_dst_pmtu_from_exception "${ns_a}" ${dst1})"
+       check_pmtu_value "1300" "${pmtu_1}" "decreasing local MTU" || return 1
+       # Second exception shouldn't be modified
+       pmtu_2="$(route_get_dst_pmtu_from_exception "${ns_a}" ${dst2})"
+       check_pmtu_value "1500" "${pmtu_2}" "changing local MTU on a link not on this path" || return 1
+
+       # Increase MTU, check for PMTU increase in route exception
+       mtu "${ns_a}"  veth_A-R1 1700
+       mtu "${ns_r1}" veth_R1-A 1700
+       pmtu_1="$(route_get_dst_pmtu_from_exception "${ns_a}" ${dst1})"
+       check_pmtu_value "1700" "${pmtu_1}" "increasing local MTU" || return 1
+       # Second exception shouldn't be modified
+       pmtu_2="$(route_get_dst_pmtu_from_exception "${ns_a}" ${dst2})"
+       check_pmtu_value "1500" "${pmtu_2}" "changing local MTU on a link not on this path" || return 1
+
+       # Skip PMTU locking tests for IPv6
+       [ $family -eq 6 ] && return 0
+
+       # Decrease remote MTU on path via R2, get new exception
+       mtu "${ns_r2}" veth_R2-B 400
+       mtu "${ns_b}"  veth_B-R2 400
+       ${ns_a} ${ping} -q -M want -i 0.1 -w 2 -s 1400 ${dst2} > /dev/null
+       pmtu_2="$(route_get_dst_pmtu_from_exception "${ns_a}" ${dst2})"
+       check_pmtu_value "lock 552" "${pmtu_2}" "exceeding MTU, with MTU < min_pmtu" || return 1
+
+       # Decrease local MTU below PMTU
+       mtu "${ns_a}"  veth_A-R2 500
+       mtu "${ns_r2}" veth_R2-A 500
+       pmtu_2="$(route_get_dst_pmtu_from_exception "${ns_a}" ${dst2})"
+       check_pmtu_value "500" "${pmtu_2}" "decreasing local MTU" || return 1
+
+       # Increase local MTU
+       mtu "${ns_a}"  veth_A-R2 1500
+       mtu "${ns_r2}" veth_R2-A 1500
+       pmtu_2="$(route_get_dst_pmtu_from_exception "${ns_a}" ${dst2})"
+       check_pmtu_value "1500" "${pmtu_2}" "increasing local MTU" || return 1
+
+       # Get new exception
+       ${ns_a} ${ping} -q -M want -i 0.1 -w 2 -s 1400 ${dst2} > /dev/null
+       pmtu_2="$(route_get_dst_pmtu_from_exception "${ns_a}" ${dst2})"
+       check_pmtu_value "lock 552" "${pmtu_2}" "exceeding MTU, with MTU < min_pmtu" || return 1
+}
+
+test_pmtu_ipv4_exception() {
+       test_pmtu_ipvX 4
+}
+
+test_pmtu_ipv6_exception() {
+       test_pmtu_ipvX 6
+}
+
 test_pmtu_vti4_exception() {
        setup namespaces veth vti4 xfrm4 || return 2