From: "Vladimir V. Kamarzin" <v...@vvk.pp.ru> When working in TAP mode, openvpn at server side maintains mapping table "MAC" -> "client". It needs to know what MAC belongs to what client to be able to forward traffic.
How does openvpn maintains it's MAC address table? Openvpn basically emulates learning switch behavior, but not in 100% the same way. Openvpn analyzes ethernet header of all packets, coming from clients, and extracts MAC address from them. So where is openvpn behavior differs from learning switch ones? Whether learning switch sees a packet for broadcast, multicast or unknown unicast destination, it sends it to all ports. Openvpn does the same thing, except for unicast destinations. Consider following example setup: - S: switch - A: generic network host, 192.168.0.1/24 - B: special device which provides access to C, 192.168.0.2/24 and MAC 00:00:00:11:11:11 - C: special device that we want access, 192.168.0.3/24, MAC 00:00:00:22:22:22 Plug scheme: (A) and (B) are plugged to (S). (C) is plugged to (B). ARP handling for (B) is performed by (C). Here is step-by-step scheme how it works: 1. (A) wants to "ping" (C). So it sends ARP request "who has 192.168.0.3 tell 192.168.0.1" with broadcast destination in ethernet frame. Switch forwards this packet to all ports. 2. (B) receives ARP request and generates reply: "192.168.0.3 is at 00:00:00:22:22:22". Although MAC in ethernet header is 00:00:00:11:11:11! 3. Switch (S) see this reply and learns 00:00:00:11:11:11 to it MAC address table. (A) see this reply and add entry to ARP cache: 192.168.0.3 -> 00:00:00:22:22:22 4. (A) sends unicast packet (ICMP echo request) to 192.168.0.3, with ethernet destination address 00:00:00:22:22:22 5. Switch (S) does not know where is 00:00:00:22:22:22, so it sends this packet to all ports 6. (C) receives ICMP echo request and generates ICMP echo reply. Ethernet header now contains 00:00:00:22:22:22. 7. Switch see frame with src 00:00:00:22:22:22 and learns it. All happy, traffic passes. In openvpn case, there is no step 5. This patch implements option "tap-flood-unknown-unicast", which changes openvpn behavior to switch-like. Bandwidth on vpn links is limited & expensive, so it is disabled by default. Signed-off-by: Vladimir V. Kamarzin <v...@vvk.pp.ru> --- doc/openvpn.8 | 6 ++++++ src/openvpn/multi.c | 34 ++++++++++++++++++++++++++++++++-- src/openvpn/options.c | 9 +++++++++ src/openvpn/options.h | 2 ++ 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/doc/openvpn.8 b/doc/openvpn.8 index 6fc7462..01db9e8 100644 --- a/doc/openvpn.8 +++ b/doc/openvpn.8 @@ -5220,6 +5220,12 @@ the TAP-Win32 adapter time to come up before Windows IP Helper API operations are applied to it. .\"********************************************************* .TP +.B \-\-tap-flood-unknown-unicast +Flood an unicast packet to all clients whether destination address +is not bound to any client. Useful when you have sort of an +arp-proxy at the client side. +.\"********************************************************* +.TP .B \-\-show-net-up Output OpenVPN's view of the system routing table and network adapter list to the syslog or log file after the TUN/TAP adapter diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index ab3f10c..e7040be 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -991,6 +991,33 @@ multi_learn_addr (struct multi_context *m, } /* + * Do we have client instance associated with address? + */ +static bool +multi_check_known_addr (struct multi_context *m, + const struct mroute_addr *addr) +{ + struct multi_route *route; + + /* check for local address */ + if (mroute_addr_equal (addr, &m->local)) + /* we don't need flooding of all clients when address is local */ + return true; + + route = (struct multi_route *) hash_lookup (m->vhash, addr); + + /* does host route (possible cached) exist? */ + if (route && multi_route_defined (m, route)) + { + return true; + } + else + { + return false; + } +} + +/* * Get client instance based on virtual address. */ static struct multi_instance * @@ -2341,8 +2368,11 @@ multi_process_incoming_tun (struct multi_context *m, const unsigned int mpp_flag { struct context *c; - /* broadcast or multicast dest addr? */ - if (mroute_flags & (MROUTE_EXTRACT_BCAST|MROUTE_EXTRACT_MCAST)) + /* Broadcast or multicast dest addr? Or unknown unicast dest addr? */ + if ((mroute_flags & (MROUTE_EXTRACT_BCAST|MROUTE_EXTRACT_MCAST)) + || (m->top.options.tap_flood_unknown_unicast == true + && dev_type == DEV_TYPE_TAP + && !multi_check_known_addr (m, &dest))) { /* for now, treat multicast as broadcast */ #ifdef ENABLE_PF diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 0ae2c61..2d9736c 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -473,6 +473,8 @@ static const char usage_message[] = "--tcp-nodelay : Macro that sets TCP_NODELAY socket flag on the server\n" " as well as pushes it to connecting clients.\n" "--learn-address cmd : Run command cmd to validate client virtual addresses.\n" + "--tap-flood-unknown-unicast : Flood an unicast packet to all clients whether destination\n" + " address is not bound to any client.\n" "--connect-freq n s : Allow a maximum of n new connections per s seconds.\n" "--max-clients n : Allow a maximum of n simultaneously connected clients.\n" "--max-routes-per-client n : Allow a maximum of n internal routes per client.\n" @@ -1194,6 +1196,7 @@ show_p2mp_parms (const struct options *o) SHOW_INT (virtual_hash_size); SHOW_STR (client_connect_script); SHOW_STR (learn_address_script); + SHOW_BOOL (tap_flood_unknown_unicast); SHOW_STR (client_disconnect_script); SHOW_STR (client_config_dir); SHOW_BOOL (ccd_exclusive); @@ -2102,6 +2105,8 @@ options_postprocess_verify_ce (const struct options *options, const struct conne msg (M_USAGE, "--hash-size requires --mode server"); if (options->learn_address_script) msg (M_USAGE, "--learn-address requires --mode server"); + if (options->tap_flood_unknown_unicast) + msg (M_USAGE, "--tap-flood-unknown-unicast requires --mode server"); if (options->client_connect_script) msg (M_USAGE, "--client-connect requires --mode server"); if (options->client_disconnect_script) @@ -6119,6 +6124,10 @@ add_option (struct options *options, { VERIFY_PERMISSION (OPT_P_ROUTE_EXTRAS); } + else if (streq (p[0], "tap-flood-unknown-unicast")) + { + options->tap_flood_unknown_unicast = true; + } else if (streq (p[0], "pkcs11-id-type") || streq (p[0], "pkcs11-sign-mode") || streq (p[0], "pkcs11-slot") || diff --git a/src/openvpn/options.h b/src/openvpn/options.h index f80532c..cba1049 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -591,6 +591,8 @@ struct options bool show_net_up; int route_method; #endif + + bool tap_flood_unknown_unicast; }; #define streq(x, y) (!strcmp((x), (y))) -- 1.8.3.2