Skip to content

Commit

Permalink
correctly set up route to vpn gateway (#285)
Browse files Browse the repository at this point in the history
* support vpn gateway being on a different interface than the default route (addresses #272)
* this improvement of ipv4_get_route also fixes issues when the gateway is on the same interface but reachable over a subnet route with a specific gateway
* more elaborated comments in ipv4_get_route
  • Loading branch information
mrbaseman authored Apr 19, 2018
1 parent 25df535 commit e30ea4c
Showing 1 changed file with 125 additions and 26 deletions.
151 changes: 125 additions & 26 deletions src/ipv4.c
Original file line number Diff line number Diff line change
Expand Up @@ -107,18 +107,35 @@ static inline void route_destroy(struct rtentry *route)
/*
* Finds system IP route to a destination.
*
* The passed route must have dest and mask set. If the route is found, the
* function fills the gtw and iface properties.
* The passed route must have dest and mask set. If the route is found,
* the function searches for a match in the routing table and returns
* that one. Note that dest and mask contain the network address and
* the mask of the corresponding routing table entry after calling.
* After calling ipv4_get_route it might be necessary to set dest
* and mask again to the desired values for further processing.
*/
static int ipv4_get_route(struct rtentry *route)
{
size_t size;
char buffer[0x1000];
char *start, *line;
char *saveptr1 = NULL, *saveptr2 = NULL;
uint32_t rtdest, rtmask, rtgtw;
int rtfound = 0;

log_debug("ip route show %s\n", ipv4_show_route(route));

// store what we are looking for
rtdest = route_dest(route).s_addr;
rtmask = route_mask(route).s_addr;
rtgtw = route_gtw(route).s_addr;


// initialize the output record
route_dest(route).s_addr = inet_addr("0.0.0.0");
route_mask(route).s_addr = inet_addr("0.0.0.0");
route_gtw(route).s_addr = inet_addr("0.0.0.0");

#ifdef __APPLE__
FILE *fp;
int len = sizeof(buffer) - 1;
Expand All @@ -144,8 +161,20 @@ static int ipv4_get_route(struct rtentry *route)

unsigned short flag_table[256] = { 0 };

// fill the table now (I'm still looking for a more elagant way to do this),
// also, not all flags might be allowed in the context of ipv4
/*
* Fill the flag_table now. Unfortunately it is not easy
* to do this in a more elegant way. The problem here
* is that these are already preprocessor macros and
* we can't use them as arguments for another macro which
* would include the #ifdef statements.
*
* Also, not all flags might be allowed in the context
* of ipv4, and the code depends on which ones are
* actually implemented on the target platform, which
* might also be varying between OSX versions.
*
*/

#ifdef RTF_PROTO1 // Protocol specific routing flag #1
flag_table['1'] = RTF_PROTO1 & USHRT_MAX;
#endif
Expand Down Expand Up @@ -243,7 +272,7 @@ static int ipv4_get_route(struct rtentry *route)
start++;

#ifdef __APPLE__
// Skip 3 more line
// Skip 3 more lines on Mac OSX
start = index(start, '\n');
start = index(++start, '\n');
start = index(++start, '\n');
Expand Down Expand Up @@ -273,6 +302,10 @@ static int ipv4_get_route(struct rtentry *route)
mask = UINT32_MAX;
// "Destination"
tmpstr = strtok_r(line, " ", &saveptr2);
if (strncmp(tmpstr, "Internet6", 9) == 0) {
// we have arrived at the end of ipv4 output
goto end;
}
log_debug("- Destination: %s\n", tmpstr);
// replace literal "default" route by IPV4 numbers-and-dots notation
if (strncmp(tmpstr, "default", 7) == 0) {
Expand Down Expand Up @@ -359,29 +392,89 @@ static int ipv4_get_route(struct rtentry *route)
window = strtol(strtok_r(NULL, "\t", &saveptr2), NULL, 16);
irtt = strtol(strtok_r(NULL, "\t", &saveptr2), NULL, 16);
#endif
/*
* Now that we have parsed a routing entry, check if it
* matches the current argument to the function call.
* In rtentry we have integer representation, i.e.
* the most significant byte corresponds to the last
* number of dotted-number representation and vice versa.
* In this representation ( address & mask ) is the network
* address.
* The routing algorithm does the following:
* First, check if the network address we are looking for
* falls into the network for the current route.
* Therefore, calculate the network address for both, the
* current route and for the destination we are searching.
* If the destination is a smaller network (for instance a
* single host), we have to mask again with the netmask of
* the routing entry that we are checking in order to obtain
* the network address in the context of the current route.
* If both network addresses match, we have found a candidate
* for a route.
* However, there might be another route for a smaller network,
* therefore repeat this and only store the resulting route
* when the mask is at least as large as the one we may
* have already found in a previous iteration (a larger
* netmask corresponds to a smaller network in this
* representation, and has a higher prority by default).
* Also, only consider routing entries for which the
* netmask is not larger than the netmask used in the
* argument when calling the function - so that we can
* distinguish between different routing entries for subnets
* of different size but with the same network address.
* For routing entries with the same destination and
* the same netmask the metric can be used for adjusting
* the priority (this is not supported on mac).
* If the metric is larger than one found for this network
* size, skip the current route (smaller numbers denote
* less hops and therefore have a higher priority).
*/

if (((dest & mask) == (rtdest & rtmask & mask))
&& (mask >= route_mask(route).s_addr)
&& (mask <= rtmask)) {
#ifndef __APPLE__
if (((mask == route_mask(route).s_addr)
&& (metric <= route->rt_metric))
|| (rtfound == 0)
|| (mask > route_mask(route).s_addr)) {
#endif
rtfound = 1;
// Requested route has been found
route_dest(route).s_addr = dest;
route_mask(route).s_addr = mask;
route_gtw(route).s_addr = gtw;
route->rt_flags = flags;

strncpy(route_iface(route), iface,
ROUTE_IFACE_LEN - 1);

if (dest == route_dest(route).s_addr &&
mask == route_mask(route).s_addr) {
// Requested route has been found
route_gtw(route).s_addr = gtw;
route->rt_flags = flags;
#ifndef __APPLE__
// we do not have these values from Mac OS X netstat,
// so stay with defaults denoted by values of 0
route->rt_metric = metric;
route->rt_mtu = mtu;
route->rt_window = window;
route->rt_irtt = irtt;
// we do not have these values from Mac OS X netstat,
// so stay with defaults denoted by values of 0
route->rt_metric = metric;
route->rt_mtu = mtu;
route->rt_window = window;
route->rt_irtt = irtt;
}
#endif
strncpy(route_iface(route), iface,
ROUTE_IFACE_LEN - 1);
return 0;
}
line = strtok_r(NULL, "\n", &saveptr1);
}
log_debug("Route not found.\n");
#ifdef __APPLE__
end:
#endif
if (rtfound==0) {
// should not occur anymore unless there is no default route
log_debug("Route not found.\n");
// at least restore input values
route_dest(route).s_addr = rtdest;
route_mask(route).s_addr = rtmask;
route_gtw(route).s_addr = rtgtw;
return ERR_IPV4_NO_SUCH_ROUTE;
}

return ERR_IPV4_NO_SUCH_ROUTE;
return 0;
}

static int ipv4_set_route(struct rtentry *route)
Expand Down Expand Up @@ -485,18 +578,24 @@ int ipv4_protect_tunnel_route(struct tunnel *tunnel)
}


// Set the default route as the route to the tunnel gateway
char *iface = route_iface(gtw_rt);
memcpy(gtw_rt, def_rt, sizeof(*gtw_rt));
route_iface(gtw_rt) = iface;
strncpy(route_iface(gtw_rt), route_iface(def_rt), ROUTE_IFACE_LEN - 1);
// Set the up a route to the tunnel gateway
route_dest(gtw_rt).s_addr = tunnel->config->gateway_ip.s_addr;
route_mask(gtw_rt).s_addr = inet_addr("255.255.255.255");
ret = ipv4_get_route(gtw_rt);
if (ret != 0) {
log_warn("Could not get route to gateway (%s).\n",
err_ipv4_str(ret));
log_warn("Protecting tunnel route has failed. But this can be working except for some cases.\n");
goto err_destroy;
}
route_dest(gtw_rt).s_addr = tunnel->config->gateway_ip.s_addr;
route_mask(gtw_rt).s_addr = inet_addr("255.255.255.255");
gtw_rt->rt_flags |= RTF_HOST;
gtw_rt->rt_metric = 0;

tunnel->ipv4.route_to_vpn_is_added = 1;
log_debug("Setting route to vpn server...\n");
log_debug("ip route show %s\n", ipv4_show_route(gtw_rt));
ret = ipv4_set_route(gtw_rt);
if (ret == ERR_IPV4_SEE_ERRNO && errno == EEXIST) {
log_warn("Route to vpn server exists already.\n");
Expand Down

0 comments on commit e30ea4c

Please sign in to comment.