mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-05-04 05:58:08 +02:00
For VPN connections, the interface name would be that of the VPN's IP interface, but the script environment would be the that of the VPN's parent device. Enhance the environment by adding any VPN specific details as additional environment variables prefixed by "VPN_". Leave the existing environment setup intact for backwards compatiblity. Additionally, the dispatcher never got updated for IPv6 support, so push IPv6 configuration and DHCPv6 configuration into the environment too. Even better, push everything the dispatcher needs to it instead of making the dispatcher make D-Bus requests back to NM, which sometimes fails if NM has already torn down the device or the connection which the device was using. And add some testcases to ensure that we don't break backwards compat, the testcases here were grabbed from a 0.8.4 machine with a hacked up dispatcher to dump everything it was given from NM.
615 lines
16 KiB
C
615 lines
16 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
|
|
/*
|
|
* 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, or (at your option)
|
|
* any later version.
|
|
*
|
|
* 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Copyright (C) 2011 Red Hat, Inc.
|
|
*
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <arpa/inet.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <glib.h>
|
|
#include <glib-object.h>
|
|
|
|
#include "nm-connection.h"
|
|
#include "nm-setting-connection.h"
|
|
#include "nm-dispatcher-utils.h"
|
|
#include "nm-dbus-glib-types.h"
|
|
#include "nm-dispatcher-action.h"
|
|
#include "nm-utils.h"
|
|
|
|
/*******************************************/
|
|
|
|
static void
|
|
value_destroy (gpointer data)
|
|
{
|
|
GValue *value = (GValue *) data;
|
|
|
|
g_value_unset (value);
|
|
g_slice_free (GValue, value);
|
|
}
|
|
|
|
static GHashTable *
|
|
value_hash_create (void)
|
|
{
|
|
return g_hash_table_new_full (g_str_hash, g_str_equal, g_free, value_destroy);
|
|
}
|
|
|
|
static void
|
|
value_hash_add (GHashTable *hash,
|
|
const char *key,
|
|
GValue *value)
|
|
{
|
|
g_hash_table_insert (hash, g_strdup (key), value);
|
|
}
|
|
|
|
static void
|
|
value_hash_add_string (GHashTable *hash,
|
|
const char *key,
|
|
const char *str)
|
|
{
|
|
GValue *value;
|
|
|
|
value = g_slice_new0 (GValue);
|
|
g_value_init (value, G_TYPE_STRING);
|
|
g_value_set_string (value, str);
|
|
|
|
value_hash_add (hash, key, value);
|
|
}
|
|
|
|
static void
|
|
value_hash_add_object_path (GHashTable *hash,
|
|
const char *key,
|
|
const char *op)
|
|
{
|
|
GValue *value;
|
|
|
|
value = g_slice_new0 (GValue);
|
|
g_value_init (value, DBUS_TYPE_G_OBJECT_PATH);
|
|
g_value_set_boxed (value, op);
|
|
|
|
value_hash_add (hash, key, value);
|
|
}
|
|
|
|
static void
|
|
value_hash_add_uint (GHashTable *hash,
|
|
const char *key,
|
|
guint32 val)
|
|
{
|
|
GValue *value;
|
|
|
|
value = g_slice_new0 (GValue);
|
|
g_value_init (value, G_TYPE_UINT);
|
|
g_value_set_uint (value, val);
|
|
|
|
value_hash_add (hash, key, value);
|
|
}
|
|
|
|
static void
|
|
value_hash_add_strv (GHashTable *hash,
|
|
const char *key,
|
|
GPtrArray *array)
|
|
{
|
|
GValue *value;
|
|
|
|
value = g_slice_new0 (GValue);
|
|
g_value_init (value, DBUS_TYPE_G_ARRAY_OF_STRING);
|
|
g_value_take_boxed (value, array);
|
|
value_hash_add (hash, key, value);
|
|
}
|
|
|
|
static void
|
|
value_hash_add_uint_array (GHashTable *hash,
|
|
const char *key,
|
|
GArray *array)
|
|
{
|
|
GValue *value;
|
|
|
|
value = g_slice_new0 (GValue);
|
|
g_value_init (value, DBUS_TYPE_G_UINT_ARRAY);
|
|
g_value_take_boxed (value, array);
|
|
value_hash_add (hash, key, value);
|
|
}
|
|
|
|
static gboolean
|
|
parse_main (GKeyFile *kf,
|
|
GHashTable **out_con_hash,
|
|
GHashTable **out_con_props,
|
|
char **out_iface,
|
|
char **out_action,
|
|
GError **error)
|
|
{
|
|
char *uuid, *id;
|
|
NMConnection *connection;
|
|
NMSettingConnection *s_con;
|
|
|
|
*out_iface = g_key_file_get_string (kf, "main", "iface", error);
|
|
if (*out_iface == NULL)
|
|
return FALSE;
|
|
|
|
*out_action = g_key_file_get_string (kf, "main", "action", error);
|
|
if (*out_action == NULL)
|
|
return FALSE;
|
|
|
|
uuid = g_key_file_get_string (kf, "main", "uuid", error);
|
|
if (uuid == NULL)
|
|
return FALSE;
|
|
id = g_key_file_get_string (kf, "main", "id", error);
|
|
if (id == NULL)
|
|
return FALSE;
|
|
|
|
connection = nm_connection_new ();
|
|
g_assert (connection);
|
|
s_con = (NMSettingConnection *) nm_setting_connection_new ();
|
|
g_assert (s_con);
|
|
g_object_set (s_con,
|
|
NM_SETTING_CONNECTION_UUID, uuid,
|
|
NM_SETTING_CONNECTION_ID, id,
|
|
NULL);
|
|
g_free (uuid);
|
|
g_free (id);
|
|
nm_connection_add_setting (connection, NM_SETTING (s_con));
|
|
|
|
*out_con_hash = nm_connection_to_hash (connection, NM_SETTING_HASH_FLAG_ALL);
|
|
g_object_unref (connection);
|
|
|
|
*out_con_props = value_hash_create ();
|
|
value_hash_add_object_path (*out_con_props, "connection-path", "/org/freedesktop/NetworkManager/Connections/5");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_device (GKeyFile *kf, GHashTable **out_device_props, GError **error)
|
|
{
|
|
char *tmp;
|
|
gint i;
|
|
|
|
*out_device_props = value_hash_create ();
|
|
|
|
i = g_key_file_get_integer (kf, "device", "state", error);
|
|
if (i == 0)
|
|
return FALSE;
|
|
value_hash_add_uint (*out_device_props, NMD_DEVICE_PROPS_STATE, (guint) i);
|
|
|
|
i = g_key_file_get_integer (kf, "device", "type", error);
|
|
if (i == 0)
|
|
return FALSE;
|
|
value_hash_add_uint (*out_device_props, NMD_DEVICE_PROPS_TYPE, (guint) i);
|
|
|
|
tmp = g_key_file_get_string (kf, "device", "interface", error);
|
|
if (tmp == NULL)
|
|
return FALSE;
|
|
value_hash_add_string (*out_device_props, NMD_DEVICE_PROPS_INTERFACE, tmp);
|
|
g_free (tmp);
|
|
|
|
tmp = g_key_file_get_string (kf, "device", "ip-interface", error);
|
|
if (tmp == NULL)
|
|
return FALSE;
|
|
value_hash_add_string (*out_device_props, NMD_DEVICE_PROPS_IP_INTERFACE, tmp);
|
|
g_free (tmp);
|
|
|
|
tmp = g_key_file_get_string (kf, "device", "path", error);
|
|
if (tmp == NULL)
|
|
return FALSE;
|
|
value_hash_add_object_path (*out_device_props, NMD_DEVICE_PROPS_PATH, tmp);
|
|
g_free (tmp);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
add_uint_array (GKeyFile *kf,
|
|
GHashTable *props,
|
|
const char *section,
|
|
const char *key,
|
|
GError **error)
|
|
{
|
|
char *tmp;
|
|
char **split, **iter;
|
|
GArray *items;
|
|
|
|
tmp = g_key_file_get_string (kf, section, key, error);
|
|
if (tmp == NULL) {
|
|
g_clear_error (error);
|
|
return TRUE;
|
|
}
|
|
split = g_strsplit_set (tmp, " ", -1);
|
|
g_free (tmp);
|
|
|
|
if (g_strv_length (split) > 0) {
|
|
items = g_array_sized_new (FALSE, TRUE, sizeof (guint32), g_strv_length (split));
|
|
for (iter = split; iter && *iter; iter++) {
|
|
if (strlen (g_strstrip (*iter))) {
|
|
struct in_addr addr;
|
|
|
|
g_assert_cmpint (inet_pton (AF_INET, *iter, &addr), ==, 1);
|
|
g_array_append_val (items, addr.s_addr);
|
|
}
|
|
}
|
|
value_hash_add_uint_array (props, key, items);
|
|
}
|
|
g_strfreev (split);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_ip4 (GKeyFile *kf, GHashTable **out_props, const char *section, GError **error)
|
|
{
|
|
char *tmp;
|
|
char **split, **iter;
|
|
GPtrArray *domains;
|
|
GSList *list;
|
|
GValue *val;
|
|
|
|
*out_props = value_hash_create ();
|
|
|
|
/* search domains */
|
|
tmp = g_key_file_get_string (kf, section, "domains", error);
|
|
if (tmp == NULL)
|
|
return FALSE;
|
|
split = g_strsplit_set (tmp, " ", -1);
|
|
g_free (tmp);
|
|
|
|
if (g_strv_length (split) > 0) {
|
|
domains = g_ptr_array_sized_new (g_strv_length (split));
|
|
for (iter = split; iter && *iter; iter++) {
|
|
if (strlen (g_strstrip (*iter)))
|
|
g_ptr_array_add (domains, g_strdup (*iter));
|
|
}
|
|
value_hash_add_strv (*out_props, "domains", domains);
|
|
}
|
|
g_strfreev (split);
|
|
|
|
/* nameservers */
|
|
if (!add_uint_array (kf, *out_props, "ip4", "nameservers", error))
|
|
return FALSE;
|
|
/* wins-servers */
|
|
if (!add_uint_array (kf, *out_props, "ip4", "wins-servers", error))
|
|
return FALSE;
|
|
|
|
/* Addresses */
|
|
tmp = g_key_file_get_string (kf, section, "addresses", error);
|
|
if (tmp == NULL)
|
|
return FALSE;
|
|
split = g_strsplit_set (tmp, ",", -1);
|
|
g_free (tmp);
|
|
|
|
if (g_strv_length (split) > 0) {
|
|
list = NULL;
|
|
for (iter = split; iter && *iter; iter++) {
|
|
NMIP4Address *addr;
|
|
struct in_addr a;
|
|
char *p;
|
|
|
|
if (strlen (g_strstrip (*iter)) == 0)
|
|
continue;
|
|
|
|
addr = nm_ip4_address_new ();
|
|
|
|
p = strchr (*iter, '/');
|
|
g_assert (p);
|
|
*p++ = '\0';
|
|
|
|
g_assert_cmpint (inet_pton (AF_INET, *iter, &a), ==, 1);
|
|
nm_ip4_address_set_address (addr, a.s_addr);
|
|
nm_ip4_address_set_prefix (addr, (guint) atoi (p));
|
|
|
|
p = strchr (p, ' ');
|
|
g_assert (p);
|
|
p++;
|
|
|
|
g_assert_cmpint (inet_pton (AF_INET, p, &a), ==, 1);
|
|
nm_ip4_address_set_gateway (addr, a.s_addr);
|
|
|
|
list = g_slist_append (list, addr);
|
|
}
|
|
|
|
val = g_slice_new0 (GValue);
|
|
g_value_init (val, DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UINT);
|
|
nm_utils_ip4_addresses_to_gvalue (list, val);
|
|
value_hash_add (*out_props, "addresses", val);
|
|
}
|
|
g_strfreev (split);
|
|
|
|
/* Routes */
|
|
tmp = g_key_file_get_string (kf, section, "routes", error);
|
|
g_clear_error (error);
|
|
if (tmp) {
|
|
split = g_strsplit_set (tmp, ",", -1);
|
|
g_free (tmp);
|
|
|
|
if (g_strv_length (split) > 0) {
|
|
list = NULL;
|
|
for (iter = split; iter && *iter; iter++) {
|
|
NMIP4Route *route;
|
|
struct in_addr a;
|
|
char *p;
|
|
|
|
if (strlen (g_strstrip (*iter)) == 0)
|
|
continue;
|
|
|
|
route = nm_ip4_route_new ();
|
|
|
|
p = strchr (*iter, '/');
|
|
g_assert (p);
|
|
*p++ = '\0';
|
|
|
|
g_assert_cmpint (inet_pton (AF_INET, *iter, &a), ==, 1);
|
|
nm_ip4_route_set_dest (route, a.s_addr);
|
|
nm_ip4_route_set_prefix (route, (guint) atoi (p));
|
|
|
|
p = strchr (p, ' ');
|
|
g_assert (p);
|
|
p++;
|
|
|
|
g_assert_cmpint (inet_pton (AF_INET, p, &a), ==, 1);
|
|
nm_ip4_route_set_next_hop (route, a.s_addr);
|
|
|
|
p = strchr (p, ' ');
|
|
g_assert (p);
|
|
p++;
|
|
nm_ip4_route_set_metric (route, (guint) atoi (p));
|
|
|
|
list = g_slist_append (list, route);
|
|
}
|
|
|
|
val = g_slice_new0 (GValue);
|
|
g_value_init (val, DBUS_TYPE_G_ARRAY_OF_ARRAY_OF_UINT);
|
|
nm_utils_ip4_routes_to_gvalue (list, val);
|
|
value_hash_add (*out_props, "routes", val);
|
|
}
|
|
g_strfreev (split);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
parse_dhcp (GKeyFile *kf,
|
|
const char *group_name,
|
|
GHashTable **out_props,
|
|
GError **error)
|
|
{
|
|
char **keys, **iter, *val;
|
|
|
|
keys = g_key_file_get_keys (kf, group_name, NULL, error);
|
|
if (!keys)
|
|
return FALSE;
|
|
|
|
*out_props = value_hash_create ();
|
|
for (iter = keys; iter && *iter; iter++) {
|
|
val = g_key_file_get_string (kf, group_name, *iter, error);
|
|
if (!val)
|
|
return FALSE;
|
|
value_hash_add_string (*out_props, *iter, val);
|
|
g_free (val);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
get_dispatcher_file (const char *file,
|
|
GHashTable **out_con_hash,
|
|
GHashTable **out_con_props,
|
|
GHashTable **out_device_props,
|
|
GHashTable **out_device_ip4_props,
|
|
GHashTable **out_device_ip6_props,
|
|
GHashTable **out_device_dhcp4_props,
|
|
GHashTable **out_device_dhcp6_props,
|
|
const char **out_vpn_ip_iface,
|
|
GHashTable **out_vpn_ip4_props,
|
|
GHashTable **out_vpn_ip6_props,
|
|
char **out_expected_iface,
|
|
char **out_action,
|
|
GHashTable **out_env,
|
|
GError **error)
|
|
{
|
|
GKeyFile *kf;
|
|
gboolean success = FALSE;
|
|
char **keys, **iter, *val;
|
|
|
|
kf = g_key_file_new ();
|
|
if (!g_key_file_load_from_file (kf, file, G_KEY_FILE_NONE, error))
|
|
return FALSE;
|
|
|
|
if (!parse_main (kf, out_con_hash, out_con_props, out_expected_iface, out_action, error))
|
|
goto out;
|
|
|
|
if (!parse_device (kf, out_device_props, error))
|
|
goto out;
|
|
|
|
if (g_key_file_has_group (kf, "ip4")) {
|
|
if (!parse_ip4 (kf, out_device_ip4_props, "ip4", error))
|
|
goto out;
|
|
}
|
|
|
|
if (g_key_file_has_group (kf, "dhcp4")) {
|
|
if (!parse_dhcp (kf, "dhcp4", out_device_dhcp4_props, error))
|
|
goto out;
|
|
}
|
|
|
|
if (g_key_file_has_group (kf, "dhcp6")) {
|
|
if (!parse_dhcp (kf, "dhcp6", out_device_dhcp4_props, error))
|
|
goto out;
|
|
}
|
|
|
|
g_assert (g_key_file_has_group (kf, "env"));
|
|
keys = g_key_file_get_keys (kf, "env", NULL, error);
|
|
*out_env = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
for (iter = keys; iter && *iter; iter++) {
|
|
val = g_key_file_get_string (kf, "env", *iter, error);
|
|
if (!val)
|
|
goto out;
|
|
g_hash_table_insert (*out_env,
|
|
g_strdup_printf ("%s=%s", *iter, val),
|
|
GUINT_TO_POINTER (1));
|
|
g_free (val);
|
|
}
|
|
g_strfreev (keys);
|
|
|
|
success = TRUE;
|
|
|
|
out:
|
|
g_key_file_free (kf);
|
|
return success;
|
|
}
|
|
|
|
/*******************************************/
|
|
|
|
static void
|
|
test_generic (const char *path, const char *file)
|
|
{
|
|
GHashTable *con_hash = NULL;
|
|
GHashTable *con_props = NULL;
|
|
GHashTable *device_props = NULL;
|
|
GHashTable *device_ip4_props = NULL;
|
|
GHashTable *device_ip6_props = NULL;
|
|
GHashTable *device_dhcp4_props = NULL;
|
|
GHashTable *device_dhcp6_props = NULL;
|
|
const char *vpn_ip_iface = NULL;
|
|
GHashTable *vpn_ip4_props = NULL;
|
|
GHashTable *vpn_ip6_props = NULL;
|
|
char *expected_iface = NULL;
|
|
char *action = NULL;
|
|
char *out_iface = NULL;
|
|
GHashTable *expected_env = NULL;
|
|
GError *error = NULL;
|
|
gboolean success;
|
|
char *p;
|
|
char **denv, **iter;
|
|
|
|
/* Read in the test file */
|
|
p = g_strdup_printf ("%s/%s", path, file);
|
|
success = get_dispatcher_file (p,
|
|
&con_hash,
|
|
&con_props,
|
|
&device_props,
|
|
&device_ip4_props,
|
|
&device_ip6_props,
|
|
&device_dhcp4_props,
|
|
&device_dhcp6_props,
|
|
&vpn_ip_iface,
|
|
&vpn_ip4_props,
|
|
&vpn_ip6_props,
|
|
&expected_iface,
|
|
&action,
|
|
&expected_env,
|
|
&error);
|
|
g_free (p);
|
|
g_assert_no_error (error);
|
|
g_assert (success);
|
|
|
|
/* Get the environment from the dispatcher code */
|
|
denv = nm_dispatcher_utils_construct_envp (action,
|
|
con_hash,
|
|
con_props,
|
|
device_props,
|
|
device_ip4_props,
|
|
device_ip6_props,
|
|
device_dhcp4_props,
|
|
device_dhcp6_props,
|
|
vpn_ip_iface,
|
|
vpn_ip4_props,
|
|
vpn_ip6_props,
|
|
&out_iface);
|
|
|
|
/* Print out environment for now */
|
|
g_message ("\n");
|
|
for (iter = denv; iter && *iter; iter++)
|
|
g_message (" %s", *iter);
|
|
|
|
g_assert_cmpint (g_strv_length (denv), ==, g_hash_table_size (expected_env));
|
|
|
|
{
|
|
GHashTableIter k;
|
|
const char *key;
|
|
g_hash_table_iter_init (&k, expected_env);
|
|
while (g_hash_table_iter_next (&k, (gpointer) &key, NULL))
|
|
g_message (" %s", key);
|
|
}
|
|
|
|
/* Compare dispatcher generated env and expected env */
|
|
for (iter = denv; iter && *iter; iter++) {
|
|
gpointer foo;
|
|
|
|
foo = g_hash_table_lookup (expected_env, *iter);
|
|
if (!foo)
|
|
g_warning ("Failed to find %s in environment", *iter);
|
|
g_assert (foo);
|
|
}
|
|
|
|
g_assert_cmpstr (expected_iface, ==, out_iface);
|
|
}
|
|
|
|
/*******************************************/
|
|
|
|
static void
|
|
test_old_up (const char *path)
|
|
{
|
|
test_generic (path, "dispatcher-old-up");
|
|
}
|
|
|
|
static void
|
|
test_old_down (const char *path)
|
|
{
|
|
test_generic (path, "dispatcher-old-down");
|
|
}
|
|
|
|
static void
|
|
test_old_vpn_up (const char *path)
|
|
{
|
|
test_generic (path, "dispatcher-old-vpn-up");
|
|
}
|
|
|
|
static void
|
|
test_old_vpn_down (const char *path)
|
|
{
|
|
test_generic (path, "dispatcher-old-vpn-down");
|
|
}
|
|
|
|
/*******************************************/
|
|
|
|
#if GLIB_CHECK_VERSION(2,25,12)
|
|
typedef GTestFixtureFunc TCFunc;
|
|
#else
|
|
typedef void (*TCFunc)(void);
|
|
#endif
|
|
|
|
#define TESTCASE(t, d) g_test_create_case (#t, 0, d, NULL, (TCFunc) t, NULL)
|
|
|
|
int main (int argc, char **argv)
|
|
{
|
|
GTestSuite *suite;
|
|
|
|
g_assert (argc > 1);
|
|
|
|
g_test_init (&argc, &argv, NULL);
|
|
g_type_init ();
|
|
|
|
suite = g_test_get_root ();
|
|
|
|
g_test_suite_add (suite, TESTCASE (test_old_up, argv[1]));
|
|
g_test_suite_add (suite, TESTCASE (test_old_down, argv[1]));
|
|
g_test_suite_add (suite, TESTCASE (test_old_vpn_up, argv[1]));
|
|
g_test_suite_add (suite, TESTCASE (test_old_vpn_down, argv[1]));
|
|
|
|
return g_test_run ();
|
|
}
|
|
|