diff --git a/ChangeLog b/ChangeLog index ae43ecd4e..185954cfe 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,27 @@ +2008-08-15 Dan Williams + + Do connection sharing in a cleaner manner; all required iptables rules + are now stored in the activation request and pertain only to the device + which is being shared to other computers. + + * src/nm-activation-request.c + src/nm-activation-request.h + - (nm_act_request_add_share_rule): new function; add a sharing rule to + the activation request which will get torn down automatically when + the activation request dies + - (nm_act_request_set_shared): push sharing rules to iptables when sharing + is started, and tear them down when sharing is stopped + + * src/nm-device.c + - (start_sharing): start up sharing by doing the required iptables magic + - (share_init): poke the right bits of the kernel and load the right + modules for NAT + - (nm_device_activate_stage5_ip_config_commit): start NAT-ing this + connection if it's a 'shared' connection + + * src/NetworkManagerPolicy.c + - Remove all sharing stuff; done in the device code itself + 2008-08-15 Dan Williams * src/dnsmasq-manager/nm-dnsmasq-manager.c diff --git a/src/NetworkManagerPolicy.c b/src/NetworkManagerPolicy.c index 76c36ddae..c8fa251b1 100644 --- a/src/NetworkManagerPolicy.c +++ b/src/NetworkManagerPolicy.c @@ -20,17 +20,7 @@ * (C) Copyright 2005 Red Hat, Inc. */ -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include #include "NetworkManagerPolicy.h" #include "NetworkManagerUtils.h" @@ -345,178 +335,6 @@ get_device_connection (NMDevice *device) return nm_act_request_get_connection (req); } -static gboolean -do_cmd (const char *fmt, ...) -{ - va_list args; - char *cmd; - int ret; - - va_start (args, fmt); - cmd = g_strdup_vprintf (fmt, args); - va_end (args); - - nm_info ("Executing: %s", cmd); - ret = system (cmd); - g_free (cmd); - - if (ret == -1) { - nm_info ("** Error executing command."); - return FALSE; - } else if (WEXITSTATUS (ret)) { - nm_info ("** Command returned exit status %d.", WEXITSTATUS (ret)); - return FALSE; - } - - return TRUE; -} - -static void -sharing_init (void) -{ - do_cmd ("echo \"1\" > /proc/sys/net/ipv4/ip_forward"); - do_cmd ("echo \"1\" > /proc/sys/net/ipv4/ip_dynaddr"); - do_cmd ("/sbin/modprobe ip_tables iptable_nat ip_nat_ftp ip_nat_irc"); - do_cmd ("/sbin/iptables -P INPUT ACCEPT"); - do_cmd ("/sbin/iptables -F INPUT"); - do_cmd ("/sbin/iptables -P OUTPUT ACCEPT"); - do_cmd ("/sbin/iptables -F OUTPUT"); - do_cmd ("/sbin/iptables -P FORWARD DROP"); - do_cmd ("/sbin/iptables -F FORWARD"); - do_cmd ("/sbin/iptables -t nat -F"); -} - -static void -sharing_stop (NMActRequest *req) -{ - do_cmd ("/sbin/iptables -F INPUT"); - do_cmd ("/sbin/iptables -F OUTPUT"); - do_cmd ("/sbin/iptables -P FORWARD DROP"); - do_cmd ("/sbin/iptables -F FORWARD"); - do_cmd ("/sbin/iptables -F -t nat"); - - // Delete all User-specified chains - do_cmd ("/sbin/iptables -X"); - - // Reset all IPTABLES counters - do_cmd ("/sbin/iptables -Z"); - - nm_act_request_set_shared (req, FALSE); -} - -/* Given a default activation request, start NAT-ing if there are any shared - * connections. - */ -static void -sharing_restart (NMPolicy *policy, NMActRequest *req) -{ - GSList *devices, *iter; - const char *extif; - gboolean have_shared = FALSE; - - if (nm_act_request_get_shared (req)) - sharing_stop (req); - - extif = nm_device_get_ip_iface (NM_DEVICE (nm_act_request_get_device (req))); - g_assert (extif); - - /* Start NAT-ing every 'shared' connection */ - devices = nm_manager_get_devices (policy->manager); - for (iter = devices; iter; iter = g_slist_next (iter)) { - NMDevice *candidate = NM_DEVICE (iter->data); - NMSettingIP4Config *s_ip4; - NMConnection *connection; - const char *intif; - - if (nm_device_get_state (candidate) != NM_DEVICE_STATE_ACTIVATED) - continue; - - connection = get_device_connection (candidate); - g_assert (connection); - - s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG); - if (!s_ip4 || strcmp (s_ip4->method, "shared")) - continue; - - /* Init sharing if there's a shared connection to NAT */ - if (!have_shared) { - sharing_init (); - have_shared = TRUE; - } - - // FWD: Allow all connections OUT and only existing and related ones IN - intif = nm_device_get_ip_iface (candidate); - g_assert (intif); - do_cmd ("/sbin/iptables -A FORWARD -i %s -o %s -m state --state ESTABLISHED,RELATED -j ACCEPT", extif, intif); - do_cmd ("/sbin/iptables -A FORWARD -i %s -o %s -j ACCEPT", extif, intif); - do_cmd ("/sbin/iptables -A FORWARD -i %s -o %s -j ACCEPT", intif, extif); - } - - if (have_shared) { - // Enabling SNAT (MASQUERADE) functionality on $EXTIF - do_cmd ("/sbin/iptables -t nat -A POSTROUTING -o %s -j MASQUERADE", extif); - - nm_act_request_set_shared (req, TRUE); - } -} - -static void -check_sharing (NMPolicy *policy, NMDevice *device, NMConnection *connection) -{ - NMSettingIP4Config *s_ip4; - GSList *devices, *iter; - NMActRequest *default_req = NULL; - - if (!connection) - return; - - /* We only care about 'shared' connections going up or down */ - s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG); - if (!s_ip4 || strcmp (s_ip4->method, "shared")) - return; - - /* Find the default connection, if any */ - devices = nm_manager_get_devices (policy->manager); - for (iter = devices; iter; iter = g_slist_next (iter)) { - NMDevice *candidate = NM_DEVICE (iter->data); - NMActRequest *req = nm_device_get_act_request (candidate); - - if (req && nm_act_request_get_default (req)) { - default_req = req; - break; - } - } - - /* Restart sharing if there's a default active connection */ - if (default_req) - sharing_restart (policy, default_req); -} - -static void -active_connection_default_changed (NMActRequest *req, - GParamSpec *pspec, - NMPolicy *policy) -{ - gboolean is_default = nm_act_request_get_default (req); - - if (is_default) { - if (nm_act_request_get_shared (req)) { - /* Already shared, shouldn't get here */ - nm_warning ("%s: Active connection '%s' already shared.", - __func__, nm_act_request_get_active_connection_path (req)); - return; - } - - sharing_restart (policy, req); - } else { - if (!nm_act_request_get_shared (req)) - return; /* Don't care about non-shared connections */ - - /* Tear down all NAT-ing */ - sharing_stop (req); - } -} - static void device_state_changed (NMDevice *device, NMDeviceState new_state, @@ -535,27 +353,19 @@ device_state_changed (NMDevice *device, nm_info ("Marking connection '%s' invalid.", get_connection_id (connection)); } schedule_activate_check (policy, device); - check_sharing (policy, device, connection); break; case NM_DEVICE_STATE_ACTIVATED: /* Clear the invalid tag on the connection */ if (connection) g_object_set_data (G_OBJECT (connection), INVALID_TAG, NULL); - g_signal_connect (G_OBJECT (nm_device_get_act_request (device)), - "notify::default", - G_CALLBACK (active_connection_default_changed), - policy); - update_routing_and_dns (policy, FALSE); - check_sharing (policy, device, connection); break; case NM_DEVICE_STATE_UNMANAGED: case NM_DEVICE_STATE_UNAVAILABLE: case NM_DEVICE_STATE_DISCONNECTED: update_routing_and_dns (policy, FALSE); schedule_activate_check (policy, device); - check_sharing (policy, device, connection); break; default: break; diff --git a/src/nm-activation-request.c b/src/nm-activation-request.c index 4df9f887e..071511792 100644 --- a/src/nm-activation-request.c +++ b/src/nm-activation-request.c @@ -20,6 +20,9 @@ */ #include +#include +#include +#include #include #include "nm-activation-request.h" @@ -49,6 +52,10 @@ enum { static guint signals[LAST_SIGNAL] = { 0 }; +typedef struct { + char *table; + char *rule; +} ShareRule; typedef struct { gboolean disposed; @@ -63,6 +70,7 @@ typedef struct { NMActiveConnectionState state; gboolean is_default; gboolean shared; + GSList *share_rules; char *ac_path; } NMActRequestPrivate; @@ -206,11 +214,32 @@ dispose (GObject *object) cleanup_secrets_dbus_call (NM_ACT_REQUEST (object)); + /* Clear any share rules */ + nm_act_request_set_shared (NM_ACT_REQUEST (object), FALSE); + g_object_unref (priv->connection); G_OBJECT_CLASS (nm_act_request_parent_class)->dispose (object); } +static void +clear_share_rules (NMActRequest *req) +{ + NMActRequestPrivate *priv = NM_ACT_REQUEST_GET_PRIVATE (req); + GSList *iter; + + for (iter = priv->share_rules; iter; iter = g_slist_next (iter)) { + ShareRule *rule = (ShareRule *) iter->data; + + g_free (rule->table); + g_free (rule->rule); + g_free (rule); + } + + g_slist_free (priv->share_rules); + priv->share_rules = NULL; +} + static void finalize (GObject *object) { @@ -219,6 +248,8 @@ finalize (GObject *object) g_free (priv->specific_object); g_free (priv->ac_path); + clear_share_rules (NM_ACT_REQUEST (object)); + G_OBJECT_CLASS (nm_act_request_parent_class)->finalize (object); } @@ -646,12 +677,69 @@ nm_act_request_get_default (NMActRequest *req) return NM_ACT_REQUEST_GET_PRIVATE (req)->is_default; } +static void +share_child_setup (gpointer user_data G_GNUC_UNUSED) +{ + /* We are in the child process at this point */ + pid_t pid = getpid (); + setpgid (pid, pid); +} + void nm_act_request_set_shared (NMActRequest *req, gboolean shared) { + NMActRequestPrivate *priv = NM_ACT_REQUEST_GET_PRIVATE (req); + GSList *list, *iter; + g_return_if_fail (NM_IS_ACT_REQUEST (req)); NM_ACT_REQUEST_GET_PRIVATE (req)->shared = shared; + + /* Tear the rules down in reverse order when sharing is stopped */ + list = g_slist_copy (priv->share_rules); + if (!shared) + list = g_slist_reverse (list); + + /* Send the rules to iptables */ + for (iter = list; iter; iter = g_slist_next (iter)) { + ShareRule *rule = (ShareRule *) iter->data; + char *envp[1] = { NULL }; + char **argv; + char *cmd; + int status; + GError *error = NULL; + + if (shared) + cmd = g_strdup_printf ("/sbin/iptables --table %s --insert %s", rule->table, rule->rule); + else + cmd = g_strdup_printf ("/sbin/iptables --table %s --delete %s", rule->table, rule->rule); + + argv = g_strsplit (cmd, " ", 0); + if (!argv || !argv[0]) { + continue; + g_free (cmd); + } + + nm_info ("Executing: %s", cmd); + g_free (cmd); + + if (!g_spawn_sync ("/", argv, envp, G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, + share_child_setup, NULL, NULL, NULL, &status, &error)) { + nm_info ("Error executing command: (%d) %s", + error ? error->code : 0, (error && error->message) ? error->message : "unknown"); + if (error) + g_error_free (error); + } else if (WEXITSTATUS (status)) + nm_info ("** Command returned exit status %d.", WEXITSTATUS (status)); + + g_strfreev (argv); + } + + g_slist_free (list); + + /* Clear the share rule list when sharing is stopped */ + if (!shared) + clear_share_rules (req); } gboolean @@ -662,6 +750,24 @@ nm_act_request_get_shared (NMActRequest *req) return NM_ACT_REQUEST_GET_PRIVATE (req)->shared; } +void +nm_act_request_add_share_rule (NMActRequest *req, + const char *table, + const char *table_rule) +{ + NMActRequestPrivate *priv = NM_ACT_REQUEST_GET_PRIVATE (req); + ShareRule *rule; + + g_return_if_fail (NM_IS_ACT_REQUEST (req)); + g_return_if_fail (table != NULL); + g_return_if_fail (table_rule != NULL); + + rule = g_malloc0 (sizeof (ShareRule)); + rule->table = g_strdup (table); + rule->rule = g_strdup (table_rule); + priv->share_rules = g_slist_append (priv->share_rules, rule); +} + GObject * nm_act_request_get_device (NMActRequest *req) { diff --git a/src/nm-activation-request.h b/src/nm-activation-request.h index a76f2db80..44dc297f8 100644 --- a/src/nm-activation-request.h +++ b/src/nm-activation-request.h @@ -95,6 +95,10 @@ gboolean nm_act_request_get_shared (NMActRequest *req); void nm_act_request_set_shared (NMActRequest *req, gboolean shared); +void nm_act_request_add_share_rule (NMActRequest *req, + const char *table, + const char *rule); + GObject * nm_act_request_get_device (NMActRequest *req); #endif /* NM_ACTIVATION_REQUEST_H */ diff --git a/src/nm-device.c b/src/nm-device.c index dd1084ba6..37cb50049 100644 --- a/src/nm-device.c +++ b/src/nm-device.c @@ -33,6 +33,7 @@ #include #include #include +#include #include "nm-device-interface.h" #include "nm-device.h" @@ -1199,6 +1200,140 @@ nm_device_activate_schedule_stage4_ip_config_timeout (NMDevice *self) nm_device_get_iface (self)); } +static void +share_child_setup (gpointer user_data G_GNUC_UNUSED) +{ + /* We are in the child process at this point */ + pid_t pid = getpid (); + setpgid (pid, pid); +} + +static gboolean +share_init (void) +{ + int fd, count, status; + char *modules[] = { "ip_tables", "iptable_nat", "nf_nat_ftp", "nf_nat_irc", + "nf_nat_sip", "nf_nat_tftp", "nf_nat_pptp", "nf_nat_h323", + NULL }; + char **iter; + + fd = open ("/proc/sys/net/ipv4/ip_forward", O_WRONLY | O_TRUNC); + if (fd) { + count = write (fd, "1\n", 2); + if (count != 2) { + nm_warning ("%s: Error starting IP forwarding: (%d) %s", + __func__, errno, strerror (errno)); + return FALSE; + } + close (fd); + } + + fd = open ("/proc/sys/net/ipv4/ip_dynaddr", O_WRONLY | O_TRUNC); + if (fd) { + count = write (fd, "1\n", 2); + if (count != 2) { + nm_warning ("%s: Error starting IP forwarding: (%d) %s", + __func__, errno, strerror (errno)); + } + close (fd); + } + + for (iter = modules; *iter; iter++) { + char *argv[3] = { "/sbin/modprobe", *iter, NULL }; + char *envp[1] = { NULL }; + GError *error = NULL; + + if (!g_spawn_sync ("/", argv, envp, G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, + share_child_setup, NULL, NULL, NULL, &status, &error)) { + nm_info ("%s: Error loading NAT module %s: (%d) %s", + __func__, *iter, error ? error->code : 0, + (error && error->message) ? error->message : "unknown"); + if (error) + g_error_free (error); + } + } + + return TRUE; +} + +static void +add_share_rule (NMActRequest *req, const char *table, const char *fmt, ...) +{ + va_list args; + char *cmd; + + va_start (args, fmt); + cmd = g_strdup_vprintf (fmt, args); + va_end (args); + + nm_act_request_add_share_rule (req, table, cmd); + g_free (cmd); +} + +static gboolean +start_sharing (NMDevice *self) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self); + NMActRequest *req; + GError *error = NULL; + char str_addr[INET_ADDRSTRLEN + 1]; + char str_mask[INET_ADDRSTRLEN + 1]; + guint32 netmask, network; + NMIP4Config *ip4_config; + const NMSettingIP4Address *ip4_addr; + const char *iface; + + iface = nm_device_get_ip_iface (self); + if (!iface) + iface = nm_device_get_iface (self); + + ip4_config = nm_device_get_ip4_config (self); + if (!ip4_config) + return FALSE; + + ip4_addr = nm_ip4_config_get_address (ip4_config, 0); + if (!ip4_addr || !ip4_addr->address) + return FALSE; + + netmask = nm_utils_ip4_prefix_to_netmask (ip4_addr->prefix); + if (!inet_ntop (AF_INET, &netmask, str_mask, sizeof (str_mask))) + return FALSE; + + network = ip4_addr->address & netmask; + if (!inet_ntop (AF_INET, &network, str_addr, sizeof (str_addr))) + return FALSE; + + if (!share_init ()) + return FALSE; + + req = nm_device_get_act_request (self); + g_assert (req); + + add_share_rule (req, "filter", "INPUT --in-interface %s --protocol tcp --destination-port 53 --jump ACCEPT", iface); + add_share_rule (req, "filter", "INPUT --in-interface %s --protocol udp --destination-port 53 --jump ACCEPT", iface); + add_share_rule (req, "filter", "INPUT --in-interface %s --protocol tcp --destination-port 67 --jump ACCEPT", iface); + add_share_rule (req, "filter", "INPUT --in-interface %s --protocol udp --destination-port 67 --jump ACCEPT", iface); + add_share_rule (req, "filter", "FORWARD --in-interface %s --jump REJECT", iface); + add_share_rule (req, "filter", "FORWARD --out-interface %s --jump REJECT", iface); + add_share_rule (req, "filter", "FORWARD --in-interface %s --out-interface %s --jump ACCEPT", iface, iface); + add_share_rule (req, "filter", "FORWARD --source %s/%s --in-interface %s --jump ACCEPT", str_addr, str_mask, iface); + add_share_rule (req, "filter", "FORWARD --destination %s/%s --out-interface %s --match state --state ESTABLISHED,RELATED --jump ACCEPT", str_addr, str_mask, iface); + add_share_rule (req, "nat", "POSTROUTING --source %s/%s --destination ! %s/%s --jump MASQUERADE", str_addr, str_mask, str_addr, str_mask); + + nm_act_request_set_shared (req, TRUE); + + if (!nm_dnsmasq_manager_start (priv->dnsmasq_manager, ip4_config, &error)) { + nm_warning ("(%s): failed to start dnsmasq: %s", iface, error->message); + g_error_free (error); + nm_act_request_set_shared (req, FALSE); + return FALSE; + } + + priv->dnsmasq_state_id = g_signal_connect (priv->dnsmasq_manager, "state-changed", + G_CALLBACK (dnsmasq_state_changed_cb), + self); + return TRUE; +} /* * nm_device_activate_stage5_ip_config_commit @@ -1237,18 +1372,11 @@ nm_device_activate_stage5_ip_config_commit (gpointer user_data) connection = nm_act_request_get_connection (nm_device_get_act_request (self)); s_ip4 = (NMSettingIP4Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP4_CONFIG); if (s_ip4 && !strcmp (s_ip4->method, "shared")) { - GError *error = NULL; - - if (!nm_dnsmasq_manager_start (priv->dnsmasq_manager, ip4_config, &error)) { - nm_warning ("(%s): failed to start dnsmasq: %s", iface, error->message); - g_error_free (error); + if (!start_sharing (self)) { + nm_warning ("Activation (%s) Stage 5 of 5 (IP Configure Commit) start sharing failed.", iface); nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SHARED_START_FAILED); goto out; } - - priv->dnsmasq_state_id = g_signal_connect (priv->dnsmasq_manager, "state-changed", - G_CALLBACK (dnsmasq_state_changed_cb), - self); } nm_device_state_changed (self, NM_DEVICE_STATE_ACTIVATED, NM_DEVICE_STATE_REASON_NONE);