Here is corrected patch for master.
From 23f8bbf88f22f649022405473d542a7779783556 Mon Sep 17 00:00:00 2001
From: "Vladimir V. Kamarzin" <v...@vvk.pp.ru>
Date: Fri, 7 Mar 2014 18:36:03 +0600
Subject: [PATCH] Flood unicast packet with unknown destination to all clients
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 | 7 +++++++
src/openvpn/multi.c | 34 ++++++++++++++++++++++++++++++++--
src/openvpn/options.c | 9 +++++++++
src/openvpn/options.h | 1 +
4 files changed, 49 insertions(+), 2 deletions(-)
diff --git a/doc/openvpn.8 b/doc/openvpn.8
index d01c935..ae35424 100644
--- a/doc/openvpn.8
+++ b/doc/openvpn.8
@@ -3615,6 +3615,13 @@ connection is torn down.
Not implemented on Windows.
.\"*********************************************************
+.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 and using TAP. This enables full
+emulation of a learning switch behavior.
+.\"*********************************************************
.SS Client Mode
Use client mode when connecting to an OpenVPN server
which has
diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c
index 2839b30..e008c1a 100644
--- a/src/openvpn/multi.c
+++ b/src/openvpn/multi.c
@@ -1002,6 +1002,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 *
@@ -2348,8 +2375,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 ef6170c..6018a94 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -492,6 +492,8 @@ static const char usage_message[] =
" sessions to a web server at host:port. dir specifies an\n"
" optional directory to write origin IP:port data.\n"
#endif
+ "--tap-flood-unknown-unicast : Flood an unicast packet to all clients whether destination\n"
+ " address is not bound to any client.\n"
#endif
"\n"
"Client options (when connecting to a multi-client server):\n"
@@ -1230,6 +1232,7 @@ show_p2mp_parms (const struct options *o)
SHOW_STR (port_share_host);
SHOW_STR (port_share_port);
#endif
+ SHOW_BOOL (tap_flood_unknown_unicast);
#endif /* P2MP_SERVER */
SHOW_BOOL (client);
@@ -2143,6 +2146,8 @@ options_postprocess_verify_ce (const struct options *options, const struct conne
msg (M_USAGE, "--stale-routes-check requires --mode server");
if (compat_flag (COMPAT_FLAG_QUERY | COMPAT_NO_NAME_REMAPPING))
msg (M_USAGE, "--compat-x509-names no-remapping requires --mode server");
+ if (options->tap_flood_unknown_unicast)
+ msg (M_USAGE, "--tap-flood-unknown-unicast requires --mode server");
}
#endif /* P2MP_SERVER */
@@ -5724,6 +5729,10 @@ add_option (struct options *options,
options->port_share_journal_dir = p[3];
}
#endif
+ else if (streq (p[0], "tap-flood-unknown-unicast"))
+ {
+ options->tap_flood_unknown_unicast = true;
+ }
else if (streq (p[0], "client-to-client"))
{
VERIFY_PERMISSION (OPT_P_GENERAL);
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index 27bbc14..47289f9 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -458,6 +458,7 @@ struct options
char *port_share_port;
const char *port_share_journal_dir;
#endif
+ bool tap_flood_unknown_unicast;
#endif
bool client;
--
1.8.3.2