The IP assigned to the TAP interface belongs to the host, so is a local IP. Example:
# ip -4 address show tap0
20: tap0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000
inet 10.0.4.1/24 scope global tap0
valid_lft forever preferred_lft forever
# ip route get 10.0.4.1
local 10.0.4.1 dev lo src 10.0.4.1 uid 0
What does matter is the route to the TAP interface. That is, because of the implicit route added when the interface is brought up, any IP in 10.0.4.0/24 except 10.0.4.1.
# ip route get 10.0.4.2
10.0.4.2 dev tap0 src 10.0.4.1 uid 0
So if you want your program to actually see any traffic, try replacing in your rules 10.0.4.1 with for example 10.0.4.2. It's up to the attached program to actually reply from this IP if that's the goal (and if using tap rather than tun, reply to ARP as well).
- you made a typo in the last rule it's a destination address so
-d rather than
-o. It's up to you to choose to MASQUERADE the source at all, it's not needed to route the packet to the program and have a (correct) reply sent back to the initial source.
- it's not clear if you're actually involving an other peer to do the test. Doing the test from the host itself would not require nat/PREROUTING and filter/FORWARD but nat/OUTPUT and filter/OUTPUT.
- If an other peer was indeed involved, as it was, your test would have been using filter/INPUT rather than filter/FORWARD and not using nat/POSTROUTING, since the destination is local. Anyway once corrected it would be using filter/FORWARD and nat/POSTROUTING.
- if using tap (layer 2: ethernet) rather than tun (layer3: ip), when looking for this icmp (with tcpdump or in the attached program) and not seing it, look also for unanswered ARP requests.
Further reference: https://backreference.org/2010/03/26/tuntap-interface-tutorial/