strongswan/src/starter/interfaces.c

596 lines
14 KiB
C

/* strongSwan IPsec interfaces management
* Copyright (C) 2001-2002 Mathieu Lafon - Arkoon Network Security
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* RCSID $Id: interfaces.c,v 1.15 2006/02/05 10:51:55 as Exp $
*/
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/if.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <freeswan.h>
#include <ipsec_tunnel.h>
#include <constants.h>
#include <defs.h>
#include <log.h>
#include "interfaces.h"
#include "exec.h"
#include "files.h"
#define MIN(a,b) ( ((a)>(b)) ? (b) : (a) )
#define N_IPSEC_IF 4
struct st_ipsec_if {
char name[IFNAMSIZ];
char phys[IFNAMSIZ];
int up;
};
static struct st_ipsec_if _ipsec_if[N_IPSEC_IF];
static char *
_find_physical_iface(int sock, char *iface)
{
static char _if[IFNAMSIZ];
char *b;
struct ifreq req;
FILE *fd;
char line[BUF_LEN];
strncpy(req.ifr_name, iface, IFNAMSIZ);
if (ioctl(sock, SIOCGIFFLAGS, &req)==0)
{
if (req.ifr_flags & IFF_UP)
{
strncpy(_if, iface, IFNAMSIZ);
return _if;
}
}
else
{
/* If there is a file named /var/run/dynip/<iface>, look if we
* can get interface name from there (IP_PHYS)
*/
b = (char *)alloc_bytes(strlen(DYNIP_DIR) + strlen(iface) + 10, "iface");
if (b)
{
sprintf(b, "%s/%s", DYNIP_DIR, iface);
fd = fopen(b, "r");
pfree(b);
if (fd)
{
memset(_if, 0, sizeof(_if));
memset(line, 0, sizeof(line));
while (fgets(line, sizeof(line), fd) != 0)
{
if ((strncmp(line,"IP_PHYS=\"", 9) == 0)
&& (line[strlen(line) - 2] == '"')
&& (line[strlen(line) - 1] == '\n'))
{
strncpy(_if, line + 9, MIN(strlen(line) - 11, IFNAMSIZ));
break;
}
else if ((strncmp(line,"IP_PHYS=", 8) == 0)
&& (line[8] != '"')
&& (line[strlen(line) - 1] == '\n'))
{
strncpy(_if, line + 8, MIN(strlen(line) - 9, IFNAMSIZ));
break;
}
}
fclose(fd);
if (*_if)
{
strncpy(req.ifr_name, _if, IFNAMSIZ);
if (ioctl(sock, SIOCGIFFLAGS, &req) == 0)
{
if (req.ifr_flags & IFF_UP)
return _if;
}
}
}
}
}
return NULL;
}
int
starter_iface_find(char *iface, int af, ip_address *dst, ip_address *nh)
{
char *phys;
struct ifreq req;
struct sockaddr_in *sa = (struct sockaddr_in *)(&req.ifr_addr);
int sock;
if (!iface)
return -1;
sock = socket(af, SOCK_DGRAM, 0);
if (sock < 0)
return -1;
phys = _find_physical_iface(sock, iface);
if (!phys)
goto failed;
strncpy(req.ifr_name, phys, IFNAMSIZ);
if (ioctl(sock, SIOCGIFFLAGS, &req)!=0)
goto failed;
if (!(req.ifr_flags & IFF_UP))
goto failed;
if ((req.ifr_flags & IFF_POINTOPOINT)
&& nh
&& ioctl(sock, SIOCGIFDSTADDR, &req) == 0)
{
if (sa->sin_family == af)
initaddr((const void *)&sa->sin_addr, sizeof(struct in_addr), af, nh);
}
if ((dst) && (ioctl(sock, SIOCGIFADDR, &req) == 0))
{
if (sa->sin_family == af)
initaddr((const void *)&sa->sin_addr, sizeof(struct in_addr), af, dst);
}
close(sock);
return 0;
failed:
close(sock);
return -1;
}
static int
valid_str(char *str, unsigned int *pn, char **pphys
, defaultroute_t *defaultroute)
{
if (streq(str, "%defaultroute"))
{
if (!defaultroute->defined)
{
return 0;
}
*pn = 0;
*pphys = defaultroute->iface;
}
else
{
if (strlen(str) < 8
|| str[0] != 'i' || str[1] != 'p' || str[2] !='s' || str[3] != 'e'
|| str[4] != 'c' || str[5] < '0' || str[5] > '9' || str[6] != '=')
{
return 0;
}
*pn = str[5] - '0';
*pphys = &(str[7]);
}
return 1;
}
static int
_iface_up (int sock, struct st_ipsec_if *iface, char *phys
, unsigned int mtu, bool nat_t)
{
struct ifreq req;
struct ipsectunnelconf *shc=(struct ipsectunnelconf *)&req.ifr_data;
short phys_flags;
int ret = 0;
/* sscholz@astaro.com: for network mask 32 bit
struct sockaddr_in *inp;
*/
strncpy(req.ifr_name, phys, IFNAMSIZ);
if (ioctl(sock, SIOCGIFFLAGS, &req) !=0 )
return ret;
phys_flags = req.ifr_flags;
strncpy(req.ifr_name, iface->name, IFNAMSIZ);
if (ioctl(sock, SIOCGIFFLAGS, &req) != 0)
return ret;
if ((!(req.ifr_flags & IFF_UP)) || (!iface->up))
{
DBG(DBG_CONTROL,
DBG_log("attaching interface %s to %s", iface->name, phys)
)
ret = 1;
}
if ((*iface->phys) && (strcmp(iface->phys, phys) != 0 ))
{
/* tncfg --detach if phys has changed */
strncpy(req.ifr_name, iface->name, IFNAMSIZ);
ioctl(sock, IPSEC_DEL_DEV, &req);
ret = 1;
}
/* tncfg --attach */
strncpy(req.ifr_name, iface->name, IFNAMSIZ);
strncpy(shc->cf_name, phys, sizeof(shc->cf_name));
ioctl(sock, IPSEC_SET_DEV, &req);
/* set ipsec addr = phys addr */
strncpy(req.ifr_name, phys, IFNAMSIZ);
if (ioctl(sock, SIOCGIFADDR, &req) == 0)
{
strncpy(req.ifr_name, iface->name, IFNAMSIZ);
ioctl(sock, SIOCSIFADDR, &req);
}
/* set ipsec mask = phys mask */
strncpy(req.ifr_name, phys, IFNAMSIZ);
if (ioctl(sock, SIOCGIFNETMASK, &req) == 0)
{
strncpy(req.ifr_name, iface->name, IFNAMSIZ);
/* sscholz@astaro.com: changed netmask to 32 bit
* in order to prevent network routes from being created
inp = (struct sockaddr_in *)&req.ifr_addr;
inp->sin_addr.s_addr = 0xFFFFFFFFL;
*/
ioctl(sock, SIOCSIFNETMASK, &req);
}
/* set other flags & addr */
strncpy(req.ifr_name, iface->name, IFNAMSIZ);
if (ioctl(sock, SIOCGIFFLAGS, &req)==0)
{
/* removed by sscholz@astaro.com (caused trouble with DSL/ppp0) */
/* if (phys_flags & IFF_POINTOPOINT)
{
req.ifr_flags |= IFF_POINTOPOINT;
req.ifr_flags &= ~IFF_BROADCAST;
ioctl(sock, SIOCSIFFLAGS, &req);
strncpy(req.ifr_name, phys, IFNAMSIZ);
if (ioctl(sock, SIOCGIFDSTADDR, &req) == 0)
{
strncpy(req.ifr_name, iface->name, IFNAMSIZ);
ioctl(sock, SIOCSIFDSTADDR, &req);
}
}
else
*/
if (phys_flags & IFF_BROADCAST)
{
req.ifr_flags &= ~IFF_POINTOPOINT;
req.ifr_flags |= IFF_BROADCAST;
ioctl(sock, SIOCSIFFLAGS, &req);
strncpy(req.ifr_name, phys, IFNAMSIZ);
if (ioctl(sock, SIOCGIFBRDADDR, &req) == 0)
{
strncpy(req.ifr_name, iface->name, IFNAMSIZ);
ioctl(sock, SIOCSIFBRDADDR, &req);
}
}
else
{
req.ifr_flags &= ~IFF_POINTOPOINT;
req.ifr_flags &= ~IFF_BROADCAST;
ioctl(sock, SIOCSIFFLAGS, &req);
}
}
/*
* guess MTU = phys interface MTU - ESP Overhead
*
* ESP overhead : 10+16+7+2+12=57 -> 60 by security
* NAT-T overhead : 20
*/
if (mtu == 0)
{
strncpy(req.ifr_name, phys, IFNAMSIZ);
ioctl(sock, SIOCGIFMTU, &req);
mtu = req.ifr_mtu - 60;
if (nat_t)
mtu -= 20;
}
/* set MTU */
if (mtu)
{
strncpy(req.ifr_name, iface->name, IFNAMSIZ);
req.ifr_mtu = mtu;
ioctl(sock, SIOCSIFMTU, &req);
}
/* ipsec interface UP */
strncpy(req.ifr_name, iface->name, IFNAMSIZ);
if (ioctl(sock, SIOCGIFFLAGS, &req) == 0)
{
req.ifr_flags |= IFF_UP;
ioctl(sock, SIOCSIFFLAGS, &req);
}
iface->up = 1;
strncpy(iface->phys, phys, IFNAMSIZ);
return ret;
}
static int
_iface_down(int sock, struct st_ipsec_if *iface)
{
struct ifreq req;
int ret = 0;
iface->up = 0;
strncpy(req.ifr_name, iface->name, IFNAMSIZ);
if (ioctl(sock, SIOCGIFFLAGS, &req)!=0)
return ret;
if (req.ifr_flags & IFF_UP)
{
DBG(DBG_CONTROL,
DBG_log("shutting down interface %s/%s", iface->name, iface->phys)
)
req.ifr_flags &= ~IFF_UP;
ioctl(sock, SIOCSIFFLAGS, &req);
ret = 1;
}
/* unset addr */
memset(&req.ifr_addr, 0, sizeof(req.ifr_addr));
req.ifr_addr.sa_family = AF_INET;
ioctl(sock, SIOCSIFADDR, &req);
/* tncfg --detach */
ioctl(sock, IPSEC_DEL_DEV, &req);
memset(iface->phys, 0, sizeof(iface->phys));
return ret;
}
void
starter_ifaces_init(void)
{
int i;
memset(_ipsec_if, 0, sizeof(_ipsec_if));
for (i = 0; i < N_IPSEC_IF; i++)
snprintf(_ipsec_if[i].name, IFNAMSIZ, "ipsec%d", i);
}
void
starter_ifaces_clear (void)
{
int sock;
unsigned int i;
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
return;
for (i = 0; i < N_IPSEC_IF; i++)
_iface_down (sock, &(_ipsec_if[i]));
}
int
starter_ifaces_load(char **ifaces, unsigned int omtu, bool nat_t
, defaultroute_t *defaultroute)
{
char *tmp_phys, *phys;
int n;
char **i;
int sock;
int j, found;
int ret = 0;
struct ifreq physreq, ipsecreq; // re-attach interface
struct sockaddr_in *inp1, *inp2; // re-attach interface
DBG(DBG_CONTROL,
DBG_log("starter_ifaces_load()")
)
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
return -1;
for (j = 0; j < N_IPSEC_IF; j++)
{
found = 0;
for (i = ifaces; i && *i; i++)
{
if (valid_str(*i, &n, &tmp_phys, defaultroute)
&& tmp_phys
&& n >= 0
&& n < N_IPSEC_IF)
{
if (n==j)
{
if (found)
{
plog( "ignoring duplicate entry for interface ipsec%d", j);
}
else
{
found++;
phys = _find_physical_iface(sock, tmp_phys);
/* Re-attach ipsec interface if IP address changes
* sscholz@astaro.com
*/
if (phys)
{
memset ((void*)&physreq, 0, sizeof(physreq));
memset ((void*)&ipsecreq, 0, sizeof(ipsecreq));
strncpy(physreq.ifr_name, phys, IFNAMSIZ);
sprintf(ipsecreq.ifr_name, "ipsec%d", j);
ioctl(sock, SIOCGIFADDR, &physreq);
ioctl(sock, SIOCGIFADDR, &ipsecreq);
inp1 = (struct sockaddr_in *)&physreq.ifr_addr;
inp2 = (struct sockaddr_in *)&ipsecreq.ifr_addr;
if (inp1->sin_addr.s_addr != inp2->sin_addr.s_addr)
{
plog("IP address of physical interface changed "
"-> reinit of ipsec interface");
_iface_down (sock, &(_ipsec_if[n]));
}
ret += _iface_up (sock, &(_ipsec_if[n]), phys, omtu, nat_t);
}
else
{
ret += _iface_down (sock, &(_ipsec_if[n]));
}
}
}
}
else if (j == 0)
{
/* Only log in the first loop */
plog("ignoring invalid interface '%s'", *i);
}
}
if (!found)
ret += _iface_down (sock, &(_ipsec_if[j]));
}
close(sock);
return ret; /* = number of changes - 'whack --listen' if > 0 */
}
/*
* initialize a defaultroute_t struct
*/
static void
init_defaultroute(defaultroute_t *defaultroute)
{
memset(defaultroute, 0, sizeof(defaultroute_t));
}
/*
* discover the default route via /proc/net/route
*/
void
get_defaultroute(defaultroute_t *defaultroute)
{
FILE *fd;
char line[BUF_LEN];
bool first = TRUE;
init_defaultroute(defaultroute);
fd = fopen("/proc/net/route", "r");
if (!fd)
{
plog("could not open 'proc/net/route'");
return;
}
while (fgets(line, sizeof(line), fd) != 0)
{
char iface[11];
char destination[9];
char gateway[11];
char flags[5];
char mask[9];
int refcnt;
int use;
int metric;
int items;
/* proc/net/route returns IP addresses in host order */
strcpy(gateway, "0h");
/* skip the header line */
if (first)
{
first = FALSE;
continue;
}
/* parsing a single line of proc/net/route */
items = sscanf(line, "%10s\t%8s\t%8s\t%5s\t%d\t%d\t%d\t%8s\t"
, iface, destination, gateway+2, flags, &refcnt, &use, &metric, mask);
if (items < 8)
{
plog("parsing error while scanning /proc/net/route");
continue;
}
/* check for defaultroute (destination 0.0.0.0 and mask 0.0.0.0) */
if (streq(destination, "00000000") && streq(mask, "00000000"))
{
if (defaultroute->defined)
{
plog("multiple default routes - cannot cope with %%defaultroute!!!");
defaultroute->defined = FALSE;
fclose(fd);
return;
}
ttoaddr(gateway, strlen(gateway), AF_INET, &defaultroute->nexthop);
strncpy(defaultroute->iface, iface, IFNAMSIZ);
defaultroute->defined = TRUE;
}
}
fclose(fd);
if (!defaultroute->defined)
{
plog("no default route - cannot cope with %%defaultroute!!!");
}
else
{
char addr_buf[20], nexthop_buf[20];
struct ifreq physreq;
int sock = socket(AF_INET, SOCK_DGRAM, 0);
/* determine IP address of iface */
if (sock < 0)
{
plog("could not open SOCK_DGRAM socket");
defaultroute->defined = FALSE;
return;
}
memset ((void*)&physreq, 0, sizeof(physreq));
strncpy(physreq.ifr_name, defaultroute->iface, IFNAMSIZ);
ioctl(sock, SIOCGIFADDR, &physreq);
close(sock);
defaultroute->addr.u.v4 = *((struct sockaddr_in *)&physreq.ifr_addr);
addrtot(&defaultroute->addr, 0, addr_buf, sizeof(addr_buf));
addrtot(&defaultroute->nexthop, 0, nexthop_buf, sizeof(nexthop_buf));
DBG(DBG_CONTROL,
DBG_log("Default route found: iface=%s, addr=%s, nexthop=%s"
, defaultroute->iface, addr_buf, nexthop_buf)
)
/* for backwards-compatibility with the awk shell scripts
* store the defaultroute in /var/run/ipsec.info
*/
fd = fopen(INFO_FILE, "w");
if (fd)
{
fprintf(fd, "defaultroutephys=%s\n", defaultroute->iface );
fprintf(fd, "defaultroutevirt=ipsec0\n");
fprintf(fd, "defaultrouteaddr=%s\n", addr_buf);
fprintf(fd, "defaultroutenexthop=%s\n", nexthop_buf);
fclose(fd);
}
}
return;
}