[network-manager-applet] Initial IPv6 config support



commit a1a3a305ac476e95e4357a8990b30b2ca762af22
Author: Dan Winship <danw gnome org>
Date:   Thu Jun 25 10:30:58 2009 -0400

    Initial IPv6 config support

 src/connection-editor/Makefile.am            |    5 +
 src/connection-editor/ce-page-ip6.glade      |  456 +++++++++++++
 src/connection-editor/ip6-routes-dialog.c    |  617 ++++++++++++++++++
 src/connection-editor/ip6-routes-dialog.h    |   35 +
 src/connection-editor/nm-connection-editor.c |    6 +
 src/connection-editor/page-ip6.c             |  880 ++++++++++++++++++++++++++
 src/connection-editor/page-ip6.h             |   53 ++
 7 files changed, 2052 insertions(+), 0 deletions(-)
---
diff --git a/src/connection-editor/Makefile.am b/src/connection-editor/Makefile.am
index 0f59066..3eea3ee 100644
--- a/src/connection-editor/Makefile.am
+++ b/src/connection-editor/Makefile.am
@@ -40,6 +40,8 @@ nm_connection_editor_SOURCES = \
 	page-wireless-security.c \
 	page-ip4.h \
 	page-ip4.c \
+	page-ip6.h \
+	page-ip6.c \
 	page-dsl.h \
 	page-dsl.c \
 	page-mobile.h \
@@ -52,6 +54,8 @@ nm_connection_editor_SOURCES = \
 	vpn-helpers.c \
 	ip4-routes-dialog.h \
 	ip4-routes-dialog.c \
+	ip6-routes-dialog.h \
+	ip6-routes-dialog.c \
 	ppp-auth-methods-dialog.c \
 	ppp-auth-methods-dialog.h \
 	polkit-helpers.c \
@@ -80,6 +84,7 @@ glade_DATA = \
 	ce-page-wireless.glade \
 	ce-page-wireless-security.glade \
 	ce-page-ip4.glade \
+	ce-page-ip6.glade \
 	ce-page-dsl.glade \
 	ce-page-mobile.glade \
 	ce-page-ppp.glade \
diff --git a/src/connection-editor/ce-page-ip6.glade b/src/connection-editor/ce-page-ip6.glade
new file mode 100644
index 0000000..40fbe14
--- /dev/null
+++ b/src/connection-editor/ce-page-ip6.glade
@@ -0,0 +1,456 @@
+<?xml version="1.0"?>
+<glade-interface>
+  <!-- interface-requires gtk+ 2.16 -->
+  <!-- interface-naming-policy toplevel-contextual -->
+  <widget class="GtkWindow" id="ip6_page_widget">
+    <property name="title" translatable="yes">window3</property>
+    <child>
+      <widget class="GtkVBox" id="IP6Page">
+        <property name="visible">True</property>
+        <property name="border_width">12</property>
+        <property name="spacing">18</property>
+        <child>
+          <widget class="GtkHBox" id="hbox1">
+            <property name="visible">True</property>
+            <property name="spacing">12</property>
+            <child>
+              <widget class="GtkLabel" id="label38">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">_Method:</property>
+                <property name="use_underline">True</property>
+                <property name="mnemonic_widget">ip6_method</property>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkComboBox" id="ip6_method">
+                <property name="visible">True</property>
+                <property name="items" translatable="yes">Automatic
+Automatic with manual DNS settings
+Manual
+Link-Local
+Shared to other computers</property>
+              </widget>
+              <packing>
+                <property name="pack_type">end</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <widget class="GtkVBox" id="ip6_addresses_box">
+            <property name="visible">True</property>
+            <property name="spacing">6</property>
+            <child>
+              <widget class="GtkLabel" id="ip6_addr_label">
+                <property name="visible">True</property>
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">&lt;b&gt;Addresses&lt;/b&gt;</property>
+                <property name="use_markup">True</property>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkAlignment" id="alignment2">
+                <property name="visible">True</property>
+                <property name="left_padding">12</property>
+                <child>
+                  <widget class="GtkVBox" id="vbox1">
+                    <property name="visible">True</property>
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <widget class="GtkHBox" id="hbox2">
+                        <property name="visible">True</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <widget class="GtkScrolledWindow" id="scrolledwindow1">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="hscrollbar_policy">never</property>
+                            <property name="vscrollbar_policy">automatic</property>
+                            <property name="shadow_type">in</property>
+                            <child>
+                              <widget class="GtkTreeView" id="ip6_addresses">
+                                <property name="height_request">100</property>
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="has_tooltip">True</property>
+                                <property name="tooltip" translatable="yes">IP addresses identify your computer on the network.  Click the "Add" button to add an IP address.</property>
+                                <property name="rules_hint">True</property>
+                              </widget>
+                            </child>
+                          </widget>
+                          <packing>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkVButtonBox" id="vbuttonbox1">
+                            <property name="visible">True</property>
+                            <property name="spacing">6</property>
+                            <property name="layout_style">start</property>
+                            <child>
+                              <widget class="GtkButton" id="ip6_addr_add_button">
+                                <property name="label">gtk-add</property>
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="can_default">True</property>
+                                <property name="receives_default">True</property>
+                                <property name="use_stock">True</property>
+                              </widget>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">False</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <widget class="GtkButton" id="ip6_addr_delete_button">
+                                <property name="label">gtk-delete</property>
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="can_default">True</property>
+                                <property name="receives_default">True</property>
+                                <property name="use_stock">True</property>
+                              </widget>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">False</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </widget>
+                      <packing>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkTable" id="table8">
+                        <property name="visible">True</property>
+                        <property name="n_rows">2</property>
+                        <property name="n_columns">2</property>
+                        <property name="column_spacing">12</property>
+                        <property name="row_spacing">6</property>
+                        <child>
+                          <widget class="GtkEntry" id="ip6_dns_searches_entry">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="tooltip" translatable="yes">IP addresses identify your computer on the network.  Click the "Add" button to add an IP address.</property>
+                            <property name="invisible_char">&#x25CF;</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
+                            <property name="y_options"></property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="ip6_dns_searches_label">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">_Search domains:</property>
+                            <property name="use_underline">True</property>
+                            <property name="mnemonic_widget">ip6_dns_searches_entry</property>
+                          </widget>
+                          <packing>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options"></property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="ip6_dns_servers_label">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label" translatable="yes">_DNS servers:</property>
+                            <property name="use_underline">True</property>
+                            <property name="mnemonic_widget">ip6_dns_servers_entry</property>
+                          </widget>
+                          <packing>
+                            <property name="x_options">GTK_FILL</property>
+                            <property name="y_options"></property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkEntry" id="ip6_dns_servers_entry">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="tooltip" translatable="yes">IP addresses identify your computer on the network.  Click the "Add" button to add an IP address.</property>
+                            <property name="invisible_char">&#x25CF;</property>
+                          </widget>
+                          <packing>
+                            <property name="left_attach">1</property>
+                            <property name="right_attach">2</property>
+                            <property name="y_options"></property>
+                          </packing>
+                        </child>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkHButtonBox" id="hbuttonbox1">
+                        <property name="visible">True</property>
+                        <property name="layout_style">end</property>
+                        <child>
+                          <widget class="GtkButton" id="ip6_routes_button">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">True</property>
+                            <child>
+                              <widget class="GtkHBox" id="hbox4">
+                                <property name="visible">True</property>
+                                <property name="spacing">3</property>
+                                <child>
+                                  <widget class="GtkImage" id="image1">
+                                    <property name="visible">True</property>
+                                  </widget>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="position">0</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <widget class="GtkLabel" id="label1">
+                                    <property name="visible">True</property>
+                                    <property name="label" translatable="yes">_Routes&#x2026;</property>
+                                    <property name="use_underline">True</property>
+                                  </widget>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                              </widget>
+                            </child>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="padding">6</property>
+                            <property name="pack_type">end</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                  </widget>
+                </child>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+  <widget class="GtkDialog" id="ip6_routes_dialog">
+    <property name="width_request">450</property>
+    <property name="height_request">250</property>
+    <property name="border_width">5</property>
+    <property name="modal">True</property>
+    <property name="window_position">center-on-parent</property>
+    <property name="icon_name">stock-peferences</property>
+    <property name="type_hint">dialog</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <widget class="GtkVBox" id="vbox2">
+            <property name="visible">True</property>
+            <property name="border_width">5</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">6</property>
+            <child>
+              <widget class="GtkHBox" id="hbox5">
+                <property name="visible">True</property>
+                <property name="spacing">6</property>
+                <child>
+                  <widget class="GtkScrolledWindow" id="scrolledwindow2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="hscrollbar_policy">never</property>
+                    <property name="vscrollbar_policy">automatic</property>
+                    <property name="shadow_type">in</property>
+                    <child>
+                      <widget class="GtkTreeView" id="ip6_routes">
+                        <property name="height_request">100</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="has_tooltip">True</property>
+                        <property name="tooltip" translatable="yes">IP addresses identify your computer on the network.  Click the "Add" button to add an IP address.</property>
+                        <property name="rules_hint">True</property>
+                      </widget>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkVButtonBox" id="vbuttonbox2">
+                    <property name="visible">True</property>
+                    <property name="spacing">6</property>
+                    <property name="layout_style">start</property>
+                    <child>
+                      <widget class="GtkButton" id="ip6_route_add_button">
+                        <property name="label">gtk-add</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="can_default">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="use_stock">True</property>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkButton" id="ip6_route_delete_button">
+                        <property name="label">gtk-delete</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="can_default">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="use_stock">True</property>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkCheckButton" id="ip6_ignore_auto_routes">
+                <property name="label" translatable="yes">Ig_nore automatically obtained routes</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="use_underline">True</property>
+                <property name="draw_indicator">True</property>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkCheckButton" id="ip6_never_default">
+                <property name="label" translatable="yes">Use this c_onnection only for resources on its network</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="has_tooltip">True</property>
+                <property name="tooltip" translatable="yes">If enabled, this connection will never be used as the default network connection.</property>
+                <property name="use_underline">True</property>
+                <property name="draw_indicator">True</property>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="dialog-action_area1">
+            <property name="visible">True</property>
+            <property name="layout_style">end</property>
+            <child>
+              <widget class="GtkButton" id="cancel_button">
+                <property name="label">gtk-cancel</property>
+                <property name="response_id">-6</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="use_stock">True</property>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkButton" id="ok_button">
+                <property name="label">gtk-ok</property>
+                <property name="response_id">-5</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="use_stock">True</property>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+</glade-interface>
diff --git a/src/connection-editor/ip6-routes-dialog.c b/src/connection-editor/ip6-routes-dialog.c
new file mode 100644
index 0000000..9470516
--- /dev/null
+++ b/src/connection-editor/ip6-routes-dialog.c
@@ -0,0 +1,617 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Connection editor -- Connection editor for NetworkManager
+ *
+ * Dan Williams <dcbw redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * (C) Copyright 2008 Red Hat, Inc.
+ */
+
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <glade/glade.h>
+#include <glib/gi18n.h>
+
+#include <nm-utils.h>
+
+#include "ip6-routes-dialog.h"
+
+#define COL_ADDRESS 0
+#define COL_PREFIX  1
+#define COL_NEXT_HOP 2
+#define COL_METRIC  3
+#define COL_LAST COL_METRIC
+
+static gboolean
+get_one_int (GtkTreeModel *model,
+             GtkTreeIter *iter,
+             int column,
+             guint32 max_value,
+             gboolean fail_if_missing,
+             guint *out)
+{
+	char *item = NULL;
+	gboolean success = FALSE;
+	long int tmp_int;
+
+	gtk_tree_model_get (model, iter, column, &item, -1);
+	if (!item || !strlen (item)) {
+		g_free (item);
+		return fail_if_missing ? FALSE : TRUE;
+	}
+
+	errno = 0;
+	tmp_int = strtol (item, NULL, 10);
+	if (errno || tmp_int < 0 || tmp_int > max_value)
+		goto out;
+
+	*out = (guint) tmp_int;
+	success = TRUE;
+
+out:
+	g_free (item);
+	return success;
+}
+
+static gboolean
+get_one_addr (GtkTreeModel *model,
+              GtkTreeIter *iter,
+              int column,
+              gboolean fail_if_missing,
+              struct in6_addr *out)
+{
+	char *item = NULL;
+	gboolean success = FALSE;
+
+	gtk_tree_model_get (model, iter, column, &item, -1);
+	if (!item || !strlen (item)) {
+		g_free (item);
+		return fail_if_missing ? FALSE : TRUE;
+	}
+
+	if (inet_pton (AF_INET6, item, out) > 0)
+		success = TRUE;
+
+	g_free (item);
+	return success;
+}
+
+static void
+validate (GtkWidget *dialog)
+{
+	GladeXML *xml;
+	GtkWidget *widget;
+	GtkTreeModel *model;
+	GtkTreeIter tree_iter;
+	gboolean valid = FALSE, iter_valid = FALSE;
+
+	g_return_if_fail (dialog != NULL);
+
+	xml = g_object_get_data (G_OBJECT (dialog), "glade-xml");
+	g_return_if_fail (xml != NULL);
+	g_return_if_fail (GLADE_IS_XML (xml));
+
+	widget = glade_xml_get_widget (xml, "ip6_routes");
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
+	iter_valid = gtk_tree_model_get_iter_first (model, &tree_iter);
+
+	while (iter_valid) {
+		struct in6_addr dest, next_hop;
+		guint prefix = 0, metric = 0;
+
+		/* Address */
+		if (!get_one_addr (model, &tree_iter, COL_ADDRESS, TRUE, &dest))
+			goto done;
+
+		/* Prefix */
+		if (!get_one_int (model, &tree_iter, COL_PREFIX, 128, TRUE, &prefix))
+			goto done;
+
+		/* Next hop (optional) */
+		if (!get_one_addr (model, &tree_iter, COL_NEXT_HOP, FALSE, &next_hop))
+			goto done;
+
+		/* Metric (optional) */
+		if (!get_one_int (model, &tree_iter, COL_METRIC, G_MAXUINT32, FALSE, &metric))
+			goto done;
+
+		iter_valid = gtk_tree_model_iter_next (model, &tree_iter);
+	}
+	valid = TRUE;
+
+done:
+	widget = glade_xml_get_widget (xml, "ok_button");
+	gtk_widget_set_sensitive (widget, valid);
+}
+
+static void
+route_add_clicked (GtkButton *button, gpointer user_data)
+{
+	GladeXML *xml = GLADE_XML (user_data);
+	GtkWidget *widget;
+	GtkListStore *store;
+	GtkTreeIter iter;
+	GtkTreeSelection *selection;
+	GtkTreeViewColumn *column;
+	GtkTreePath *path;
+	GList *cells;
+
+	widget = glade_xml_get_widget (xml, "ip6_routes");
+	store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (widget)));
+	gtk_list_store_append (store, &iter);
+	gtk_list_store_set (store, &iter, COL_ADDRESS, "", -1);
+
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
+	gtk_tree_selection_select_iter (selection, &iter);
+
+	path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
+	column = gtk_tree_view_get_column (GTK_TREE_VIEW (widget), COL_ADDRESS);
+
+	/* FIXME: using cells->data is pretty fragile but GTK apparently doesn't
+	 * have a way to get a cell renderer from a column based on path or iter
+	 * or whatever.
+	 */
+	cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
+	gtk_tree_view_set_cursor_on_cell (GTK_TREE_VIEW (widget), path, column, cells->data, TRUE);
+
+	g_list_free (cells);
+	gtk_tree_path_free (path);
+
+	validate (glade_xml_get_widget (xml, "ip6_routes_dialog"));
+}
+
+static void
+route_delete_clicked (GtkButton *button, gpointer user_data)
+{
+	GladeXML *xml = GLADE_XML (user_data);
+	GtkTreeView *treeview;
+	GtkTreeSelection *selection;
+	GList *selected_rows;
+	GtkTreeModel *model = NULL;
+	GtkTreeIter iter;
+	int num_rows;
+
+	treeview = GTK_TREE_VIEW (glade_xml_get_widget (xml, "ip6_routes"));
+
+	selection = gtk_tree_view_get_selection (treeview);
+	if (gtk_tree_selection_count_selected_rows (selection) != 1)
+		return;
+
+	selected_rows = gtk_tree_selection_get_selected_rows (selection, &model);
+	if (!selected_rows)
+		return;
+
+	if (gtk_tree_model_get_iter (model, &iter, (GtkTreePath *) selected_rows->data))
+		gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+
+	g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
+	g_list_free (selected_rows);
+
+	num_rows = gtk_tree_model_iter_n_children (model, NULL);
+	if (num_rows && gtk_tree_model_iter_nth_child (model, &iter, NULL, num_rows - 1)) {
+		selection = gtk_tree_view_get_selection (treeview);
+		gtk_tree_selection_select_iter (selection, &iter);
+	}
+
+	validate (glade_xml_get_widget (xml, "ip6_routes_dialog"));
+}
+
+static void
+list_selection_changed (GtkTreeSelection *selection, gpointer user_data)
+{
+	GtkWidget *button = GTK_WIDGET (user_data);
+	GtkTreeIter iter;
+	GtkTreeModel *model = NULL;
+
+	if (gtk_tree_selection_get_selected (selection, &model, &iter))
+		gtk_widget_set_sensitive (button, TRUE);
+	else
+		gtk_widget_set_sensitive (button, FALSE);
+}
+
+static void
+cell_edited (GtkCellRendererText *cell,
+             const gchar *path_string,
+             const gchar *new_text,
+             gpointer user_data)
+{
+	GladeXML *xml = GLADE_XML (user_data);
+	GtkWidget *widget, *dialog;
+	GtkListStore *store;
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	guint32 column;
+	GtkTreeViewColumn *next_col;
+	GtkCellRenderer *next_cell;
+
+	widget = glade_xml_get_widget (xml, "ip6_routes");
+	store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (widget)));
+	path = gtk_tree_path_new_from_string (path_string);
+	column = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (cell), "column"));
+
+	gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path);
+	gtk_list_store_set (store, &iter, column, new_text, -1);
+
+	/* Move focus to the next column */
+	column = (column >= COL_LAST) ? 0 : column + 1;
+	next_col = gtk_tree_view_get_column (GTK_TREE_VIEW (widget), column);
+	dialog = glade_xml_get_widget (xml, "ip6_routes_dialog");
+	next_cell = g_slist_nth_data (g_object_get_data (G_OBJECT (dialog), "renderers"), column);
+
+	gtk_tree_view_set_cursor_on_cell (GTK_TREE_VIEW (widget), path, next_col, next_cell, TRUE);
+	gtk_widget_grab_focus (widget);
+
+	gtk_tree_path_free (path);
+
+	validate (dialog);
+}
+
+static void
+ip_address_filter_cb (GtkEntry *   entry,
+                      const gchar *text,
+                      gint         length,
+                      gint *       position,
+                      gpointer     user_data)
+{
+	GtkWidget *ok_button = user_data;
+	GtkEditable *editable = GTK_EDITABLE (entry);
+	int i, count = 0;
+	gchar *result = g_new (gchar, length);
+
+	for (i = 0; i < length; i++) {
+		if (g_ascii_isxdigit(text[i]) || (text[i] == ':'))
+			result[count++] = text[i];
+	}
+
+	if (count > 0) {
+		g_signal_handlers_block_by_func (G_OBJECT (editable),
+		                                 G_CALLBACK (ip_address_filter_cb),
+		                                 user_data);
+		gtk_editable_insert_text (editable, result, count, position);
+		g_signal_handlers_unblock_by_func (G_OBJECT (editable),
+		                                   G_CALLBACK (ip_address_filter_cb),
+		                                   user_data);
+	}
+
+	g_signal_stop_emission_by_name (G_OBJECT (editable), "insert-text");
+	g_free (result);
+
+	/* Desensitize the OK button during input to simplify input validation.
+	 * All routes will be validated on focus-out, which will then re-enable
+	 * the OK button if the routes are valid.
+	 */
+	gtk_widget_set_sensitive (ok_button, FALSE);
+}
+
+static void
+ip6_cell_editing_started (GtkCellRenderer *cell,
+                          GtkCellEditable *editable,
+                          const gchar     *path,
+                          gpointer         user_data)
+{
+	if (!GTK_IS_ENTRY (editable)) {
+		g_warning ("%s: Unexpected cell editable type.", __func__);
+		return;
+	}
+
+	/* Set up the entry filter */
+	g_signal_connect (G_OBJECT (editable), "insert-text",
+	                  (GCallback) ip_address_filter_cb,
+	                  user_data);
+}
+
+static void
+uint_filter_cb (GtkEntry *   entry,
+                const gchar *text,
+                gint         length,
+                gint *       position,
+                gpointer     user_data)
+{
+	GtkWidget *ok_button = user_data;
+	GtkEditable *editable = GTK_EDITABLE (entry);
+	int i, count = 0;
+	gchar *result = g_new (gchar, length);
+
+	for (i = 0; i < length; i++) {
+		if ((text[i] >= '0' && text[i] <= '9'))
+			result[count++] = text[i];
+	}
+
+	if (count > 0) {
+		g_signal_handlers_block_by_func (G_OBJECT (editable),
+		                                 G_CALLBACK (uint_filter_cb),
+		                                 user_data);
+		gtk_editable_insert_text (editable, result, count, position);
+		g_signal_handlers_unblock_by_func (G_OBJECT (editable),
+		                                   G_CALLBACK (uint_filter_cb),
+		                                   user_data);
+	}
+
+	g_signal_stop_emission_by_name (G_OBJECT (editable), "insert-text");
+	g_free (result);
+
+	/* Desensitize the OK button during input to simplify input validation.
+	 * All routes will be validated on focus-out, which will then re-enable
+	 * the OK button if the routes are valid.
+	 */
+	gtk_widget_set_sensitive (ok_button, FALSE);
+}
+
+static void
+uint_cell_editing_started (GtkCellRenderer *cell,
+                           GtkCellEditable *editable,
+                           const gchar     *path,
+                           gpointer         user_data)
+{
+	if (!GTK_IS_ENTRY (editable)) {
+		g_warning ("%s: Unexpected cell editable type.", __func__);
+		return;
+	}
+
+	/* Set up the entry filter */
+	g_signal_connect (G_OBJECT (editable), "insert-text",
+	                  (GCallback) uint_filter_cb,
+	                  user_data);
+}
+
+GtkWidget *
+ip6_routes_dialog_new (NMSettingIP6Config *s_ip6, gboolean automatic)
+{
+	GladeXML *xml;
+	GtkWidget *dialog, *widget, *ok_button;
+	GtkListStore *store;
+	GtkTreeIter model_iter;
+	GtkTreeSelection *selection;
+	gint offset;
+	GtkTreeViewColumn *column;
+	GtkCellRenderer *renderer;
+	int i;
+	GSList *renderers = NULL;
+
+	xml = glade_xml_new (GLADEDIR "/ce-page-ip6.glade", "ip6_routes_dialog", NULL);
+	if (!xml) {
+		g_warning ("%s: Couldn't load ip6 page glade file.", __func__);
+		return NULL;
+	}
+
+	dialog = glade_xml_get_widget (xml, "ip6_routes_dialog");
+	if (!dialog) {
+		g_warning ("%s: Couldn't load ip6 routes dialog from glade file.", __func__);
+		g_object_unref (xml);
+		return NULL;
+	}
+
+	gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+
+	g_object_set_data_full (G_OBJECT (dialog), "glade-xml",
+	                        xml, (GDestroyNotify) g_object_unref);
+
+	ok_button = glade_xml_get_widget (xml, "ok_button");
+
+	store = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
+
+	/* Add existing routes */
+	for (i = 0; i < nm_setting_ip6_config_get_num_routes (s_ip6); i++) {
+		NMIP6Route *route = nm_setting_ip6_config_get_route (s_ip6, i);
+		const struct in6_addr *tmp_addr;
+		char ip_string[INET6_ADDRSTRLEN];
+		char *tmp;
+
+		if (!route) {
+			g_warning ("%s: empty IP6 route structure!", __func__);
+			continue;
+		}
+
+		gtk_list_store_append (store, &model_iter);
+
+		tmp_addr = nm_ip6_route_get_dest (route);
+		if (inet_ntop (AF_INET6, tmp_addr, ip_string, sizeof (ip_string)))
+			gtk_list_store_set (store, &model_iter, COL_ADDRESS, ip_string, -1);
+
+		tmp = g_strdup_printf ("%u", nm_ip6_route_get_prefix (route));
+		gtk_list_store_set (store, &model_iter, COL_PREFIX, tmp, -1);
+		g_free (tmp);
+
+		tmp_addr = nm_ip6_route_get_next_hop (route);
+		if (tmp_addr && !IN6_IS_ADDR_UNSPECIFIED (tmp_addr) &&
+			inet_ntop (AF_INET6, tmp_addr, ip_string, sizeof (ip_string)))
+			gtk_list_store_set (store, &model_iter, COL_NEXT_HOP, ip_string, -1);
+
+		if (nm_ip6_route_get_metric (route)) {
+			tmp = g_strdup_printf ("%u", nm_ip6_route_get_metric (route));
+			gtk_list_store_set (store, &model_iter, COL_METRIC, tmp, -1);
+			g_free (tmp);
+		}
+	}
+
+	widget = glade_xml_get_widget (xml, "ip6_routes");
+	gtk_tree_view_set_model (GTK_TREE_VIEW (widget), GTK_TREE_MODEL (store));
+	g_object_unref (store);
+
+	/* IP Address column */
+	renderer = gtk_cell_renderer_text_new ();
+	g_object_set (renderer, "editable", TRUE, NULL);
+	g_signal_connect (renderer, "edited", G_CALLBACK (cell_edited), xml);
+	g_object_set_data (G_OBJECT (renderer), "column", GUINT_TO_POINTER (COL_ADDRESS));
+	g_signal_connect (renderer, "editing-started", G_CALLBACK (ip6_cell_editing_started), ok_button);
+	renderers = g_slist_append (renderers, renderer);
+
+	offset = gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (widget),
+	                                                      -1, _("Address"), renderer,
+	                                                      "text", COL_ADDRESS,
+	                                                      NULL);
+	column = gtk_tree_view_get_column (GTK_TREE_VIEW (widget), offset - 1);
+	gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column), TRUE);
+	gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), TRUE);
+
+	/* Prefix column */
+	renderer = gtk_cell_renderer_text_new ();
+	g_object_set (renderer, "editable", TRUE, NULL);
+	g_signal_connect (renderer, "edited", G_CALLBACK (cell_edited), xml);
+	g_object_set_data (G_OBJECT (renderer), "column", GUINT_TO_POINTER (COL_PREFIX));
+	g_signal_connect (renderer, "editing-started", G_CALLBACK (uint_cell_editing_started), ok_button);
+	renderers = g_slist_append (renderers, renderer);
+
+	offset = gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (widget),
+	                                                      -1, _("Prefix"), renderer,
+	                                                      "text", COL_PREFIX,
+	                                                      NULL);
+	column = gtk_tree_view_get_column (GTK_TREE_VIEW (widget), offset - 1);
+	gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column), TRUE);
+	gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), TRUE);
+
+	/* Gateway column */
+	renderer = gtk_cell_renderer_text_new ();
+	g_object_set (renderer, "editable", TRUE, NULL);
+	g_signal_connect (renderer, "edited", G_CALLBACK (cell_edited), xml);
+	g_object_set_data (G_OBJECT (renderer), "column", GUINT_TO_POINTER (COL_NEXT_HOP));
+	g_signal_connect (renderer, "editing-started", G_CALLBACK (ip6_cell_editing_started), ok_button);
+	renderers = g_slist_append (renderers, renderer);
+
+	offset = gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (widget),
+	                                                      -1, _("Gateway"), renderer,
+	                                                      "text", COL_NEXT_HOP,
+	                                                      NULL);
+	column = gtk_tree_view_get_column (GTK_TREE_VIEW (widget), offset - 1);
+	gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column), TRUE);
+	gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), TRUE);
+
+	/* Metric column */
+	renderer = gtk_cell_renderer_text_new ();
+	g_object_set (renderer, "editable", TRUE, NULL);
+	g_signal_connect (renderer, "edited", G_CALLBACK (cell_edited), xml);
+	g_object_set_data (G_OBJECT (renderer), "column", GUINT_TO_POINTER (COL_METRIC));
+	g_signal_connect (renderer, "editing-started", G_CALLBACK (uint_cell_editing_started), ok_button);
+	renderers = g_slist_append (renderers, renderer);
+
+	offset = gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (widget),
+	                                                      -1, _("Metric"), renderer,
+	                                                      "text", COL_METRIC,
+	                                                      NULL);
+	column = gtk_tree_view_get_column (GTK_TREE_VIEW (widget), offset - 1);
+	gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column), TRUE);
+	gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), TRUE);
+
+	g_object_set_data_full (G_OBJECT (dialog), "renderers", renderers, (GDestroyNotify) g_slist_free);
+
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
+	g_signal_connect (selection, "changed",
+	                  G_CALLBACK (list_selection_changed),
+	                  glade_xml_get_widget (xml, "ip6_route_delete_button"));
+
+	widget = glade_xml_get_widget (xml, "ip6_route_add_button");
+	gtk_widget_set_sensitive (widget, TRUE);
+	g_signal_connect (widget, "clicked", G_CALLBACK (route_add_clicked), xml);
+
+	widget = glade_xml_get_widget (xml, "ip6_route_delete_button");
+	gtk_widget_set_sensitive (widget, FALSE);
+	g_signal_connect (widget, "clicked", G_CALLBACK (route_delete_clicked), xml);
+
+	widget = glade_xml_get_widget (xml, "ip6_ignore_auto_routes");
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget),
+	                              nm_setting_ip6_config_get_ignore_auto_routes (s_ip6));
+	gtk_widget_set_sensitive (widget, automatic);
+
+	widget = glade_xml_get_widget (xml, "ip6_never_default");
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget),
+	                              nm_setting_ip6_config_get_never_default (s_ip6));
+
+	/* Update initial validity */
+	validate (dialog);
+
+	return dialog;
+}
+
+void
+ip6_routes_dialog_update_setting (GtkWidget *dialog, NMSettingIP6Config *s_ip6)
+{
+	GladeXML *xml;
+	GtkWidget *widget;
+	GtkTreeModel *model;
+	GtkTreeIter tree_iter;
+	gboolean iter_valid;
+
+	g_return_if_fail (dialog != NULL);
+	g_return_if_fail (s_ip6 != NULL);
+
+	xml = g_object_get_data (G_OBJECT (dialog), "glade-xml");
+	g_return_if_fail (xml != NULL);
+	g_return_if_fail (GLADE_IS_XML (xml));
+
+	widget = glade_xml_get_widget (xml, "ip6_routes");
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
+	iter_valid = gtk_tree_model_get_iter_first (model, &tree_iter);
+
+	nm_setting_ip6_config_clear_routes (s_ip6);
+
+	while (iter_valid) {
+		struct in6_addr dest, next_hop;
+		guint prefix = 0, metric = 0;
+		NMIP6Route *route;
+
+		/* Address */
+		if (!get_one_addr (model, &tree_iter, COL_ADDRESS, TRUE, &dest)) {
+			g_warning ("%s: IPv6 address missing or invalid!", __func__);
+			goto next;
+		}
+
+		/* Prefix */
+		if (!get_one_int (model, &tree_iter, COL_PREFIX, 128, TRUE, &prefix)) {
+			g_warning ("%s: IPv6 prefix missing or invalid!", __func__);
+			goto next;
+		}
+
+		/* Next hop (optional) */
+		if (!get_one_addr (model, &tree_iter, COL_NEXT_HOP, FALSE, &next_hop)) {
+			g_warning ("%s: IPv6 next hop invalid!", __func__);
+			goto next;
+		}
+
+		/* Metric (optional) */
+		if (!get_one_int (model, &tree_iter, COL_METRIC, G_MAXUINT32, FALSE, &metric)) {
+			g_warning ("%s: IPv6 metric invalid!", __func__);
+			goto next;
+		}
+
+		route = nm_ip6_route_new ();
+		nm_ip6_route_set_dest (route, &dest);
+		nm_ip6_route_set_prefix (route, prefix);
+		nm_ip6_route_set_next_hop (route, &next_hop);
+		nm_ip6_route_set_metric (route, metric);
+		nm_setting_ip6_config_add_route (s_ip6, route);
+		nm_ip6_route_unref (route);
+
+	next:
+		iter_valid = gtk_tree_model_iter_next (model, &tree_iter);
+	}
+
+	widget = glade_xml_get_widget (xml, "ip6_ignore_auto_routes");
+	g_object_set (s_ip6, NM_SETTING_IP6_CONFIG_IGNORE_AUTO_ROUTES,
+	              gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)),
+	              NULL);
+
+	widget = glade_xml_get_widget (xml, "ip6_never_default");
+	g_object_set (s_ip6, NM_SETTING_IP6_CONFIG_NEVER_DEFAULT,
+	              gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)),
+	              NULL);
+}
+
diff --git a/src/connection-editor/ip6-routes-dialog.h b/src/connection-editor/ip6-routes-dialog.h
new file mode 100644
index 0000000..e7d5d09
--- /dev/null
+++ b/src/connection-editor/ip6-routes-dialog.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Connection editor -- Connection editor for NetworkManager
+ *
+ * Dan Williams <dcbw redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * (C) Copyright 2008 Red Hat, Inc.
+ */
+
+#ifndef IP6_ROUTES_DIALOG_H
+#define IP6_ROUTES_DIALOG_H
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "nm-setting-ip6-config.h"
+
+GtkWidget *ip6_routes_dialog_new (NMSettingIP6Config *s_ip6, gboolean automatic);
+
+void ip6_routes_dialog_update_setting (GtkWidget *dialog, NMSettingIP6Config *s_ip6);
+
+#endif /* IP6_ROUTES_DIALOG_H */
diff --git a/src/connection-editor/nm-connection-editor.c b/src/connection-editor/nm-connection-editor.c
index c695c03..90135b5 100644
--- a/src/connection-editor/nm-connection-editor.c
+++ b/src/connection-editor/nm-connection-editor.c
@@ -48,6 +48,7 @@
 
 #include <nm-setting-connection.h>
 #include <nm-setting-ip4-config.h>
+#include <nm-setting-ip6-config.h>
 #include <nm-setting-wired.h>
 #include <nm-setting-8021x.h>
 #include <nm-setting-wireless.h>
@@ -69,6 +70,7 @@
 #include "page-wireless.h"
 #include "page-wireless-security.h"
 #include "page-ip4.h"
+#include "page-ip6.h"
 #include "page-dsl.h"
 #include "page-mobile.h"
 #include "page-ppp.h"
@@ -604,6 +606,8 @@ nm_connection_editor_set_connection (NMConnectionEditor *editor,
 			goto out;
 		if (!add_page (editor, ce_page_ip4_new, connection, error))
 			goto out;
+		if (!add_page (editor, ce_page_ip6_new, connection, error))
+			goto out;
 	} else if (!strcmp (connection_type, NM_SETTING_WIRELESS_SETTING_NAME)) {
 		if (!add_page (editor, ce_page_wireless_new, connection, error))
 			goto out;
@@ -611,6 +615,8 @@ nm_connection_editor_set_connection (NMConnectionEditor *editor,
 			goto out;
 		if (!add_page (editor, ce_page_ip4_new, connection, error))
 			goto out;
+		if (!add_page (editor, ce_page_ip6_new, connection, error))
+			goto out;
 	} else if (!strcmp (connection_type, NM_SETTING_VPN_SETTING_NAME)) {
 		if (!add_page (editor, ce_page_vpn_new, connection, error))
 			goto out;
diff --git a/src/connection-editor/page-ip6.c b/src/connection-editor/page-ip6.c
new file mode 100644
index 0000000..380cd32
--- /dev/null
+++ b/src/connection-editor/page-ip6.c
@@ -0,0 +1,880 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Connection editor -- Connection editor for NetworkManager
+ *
+ * Dan Williams <dcbw redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * (C) Copyright 2008 Red Hat, Inc.
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <nm-setting-connection.h>
+#include <nm-setting-ip6-config.h>
+#include <nm-setting-gsm.h>
+#include <nm-setting-cdma.h>
+#include <nm-setting-pppoe.h>
+#include <nm-setting-vpn.h>
+#include <nm-utils.h>
+
+#include "page-ip6.h"
+#include "ip6-routes-dialog.h"
+
+G_DEFINE_TYPE (CEPageIP6, ce_page_ip6, CE_TYPE_PAGE)
+
+#define CE_PAGE_IP6_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CE_TYPE_PAGE_IP6, CEPageIP6Private))
+
+#define COL_ADDRESS 0
+#define COL_PREFIX 1
+#define COL_LAST COL_PREFIX
+
+typedef struct {
+	NMSettingIP6Config *setting;
+	char *connection_id;
+	GType connection_type;
+
+	GtkComboBox *method;
+	GtkListStore *method_store;
+
+	/* Addresses */
+	GtkWidget *addr_label;
+	GtkButton *addr_add;
+	GtkButton *addr_delete;
+	GtkTreeView *addr_list;
+	GtkCellRenderer *addr_cells[COL_LAST + 1];
+
+	/* DNS servers */
+	GtkWidget *dns_servers_label;
+	GtkEntry *dns_servers;
+
+	/* Search domains */
+	GtkWidget *dns_searches_label;
+	GtkEntry *dns_searches;
+
+	GtkButton *routes_button;
+
+	GtkWindowGroup *window_group;
+	gboolean window_added;
+} CEPageIP6Private;
+
+#define METHOD_COL_NAME 0
+#define METHOD_COL_NUM  1
+#define METHOD_COL_ENABLED 2
+
+#define IP6_METHOD_IGNORE          0
+#define IP6_METHOD_AUTO            1
+#define IP6_METHOD_AUTO_ADDRESSES  2
+#define IP6_METHOD_MANUAL          3
+#define IP6_METHOD_LINK_LOCAL      4
+#define IP6_METHOD_SHARED          5
+
+static void
+ip6_private_init (CEPageIP6 *self, NMConnection *connection)
+{
+	CEPageIP6Private *priv = CE_PAGE_IP6_GET_PRIVATE (self);
+	GladeXML *xml;
+	GtkTreeIter iter;
+	NMSettingConnection *s_con;
+	const char *connection_type;
+	char *str_auto = NULL, *str_auto_only = NULL;
+	GList *cells;
+
+	xml = CE_PAGE (self)->xml;
+
+	s_con = NM_SETTING_CONNECTION (nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION));
+	g_assert (s_con);
+	connection_type = nm_setting_connection_get_connection_type (s_con);
+	g_assert (connection_type);
+
+	priv->connection_type = nm_connection_lookup_setting_type (connection_type);
+
+	if (priv->connection_type == NM_TYPE_SETTING_VPN) {
+		str_auto = _("Automatic (VPN)");
+		str_auto_only = _("Automatic (VPN) addresses only");
+	} else if (   priv->connection_type == NM_TYPE_SETTING_GSM
+	           || priv->connection_type == NM_TYPE_SETTING_CDMA) {
+		str_auto = _("Automatic (PPP)");
+		str_auto_only = _("Automatic (PPP) addresses only");
+	} else if (priv->connection_type == NM_TYPE_SETTING_PPPOE) {
+		str_auto = _("Automatic (PPPoE)");
+		str_auto_only = _("Automatic (PPPoE) addresses only");
+	} else {
+		str_auto = _("Automatic");
+		str_auto_only = _("Automatic, addresses only");
+	}
+
+	priv->method = GTK_COMBO_BOX (glade_xml_get_widget (xml, "ip6_method"));
+	cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (priv->method));
+	gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (priv->method), cells->data,
+								   "sensitive", METHOD_COL_ENABLED);
+
+	priv->method_store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_BOOLEAN);
+
+	gtk_list_store_append (priv->method_store, &iter);
+	gtk_list_store_set (priv->method_store, &iter,
+	                    METHOD_COL_NAME, _("Ignore"),
+	                    METHOD_COL_NUM, IP6_METHOD_IGNORE,
+						METHOD_COL_ENABLED, TRUE,
+	                    -1);
+
+	gtk_list_store_append (priv->method_store, &iter);
+	gtk_list_store_set (priv->method_store, &iter,
+	                    METHOD_COL_NAME, str_auto,
+	                    METHOD_COL_NUM, IP6_METHOD_AUTO,
+						METHOD_COL_ENABLED, FALSE,
+	                    -1);
+
+	gtk_list_store_append (priv->method_store, &iter);
+	gtk_list_store_set (priv->method_store, &iter,
+	                    METHOD_COL_NAME, str_auto_only,
+	                    METHOD_COL_NUM, IP6_METHOD_AUTO_ADDRESSES,
+						METHOD_COL_ENABLED, FALSE,
+	                    -1);
+
+	/* Manual is pointless for Mobile Broadband */
+	if (   priv->connection_type != NM_TYPE_SETTING_GSM
+	    && priv->connection_type != NM_TYPE_SETTING_CDMA
+	    && priv->connection_type != NM_TYPE_SETTING_VPN) {
+		gtk_list_store_append (priv->method_store, &iter);
+		gtk_list_store_set (priv->method_store, &iter,
+		                    METHOD_COL_NAME, _("Manual"),
+		                    METHOD_COL_NUM, IP6_METHOD_MANUAL,
+							METHOD_COL_ENABLED, TRUE,
+		                    -1);
+	}
+
+	/* Link-local is pointless for VPNs, Mobile Broadband, and PPPoE */
+	if (   priv->connection_type != NM_TYPE_SETTING_VPN
+	    && priv->connection_type != NM_TYPE_SETTING_PPPOE
+	    && priv->connection_type != NM_TYPE_SETTING_GSM
+	    && priv->connection_type != NM_TYPE_SETTING_CDMA) {
+		gtk_list_store_append (priv->method_store, &iter);
+		gtk_list_store_set (priv->method_store, &iter,
+		                    METHOD_COL_NAME, _("Link-Local Only"),
+		                    METHOD_COL_NUM, IP6_METHOD_LINK_LOCAL,
+							METHOD_COL_ENABLED, FALSE,
+		                    -1);
+
+		gtk_list_store_append (priv->method_store, &iter);
+		gtk_list_store_set (priv->method_store, &iter,
+		                    METHOD_COL_NAME, _("Shared to other computers"),
+		                    METHOD_COL_NUM, IP6_METHOD_SHARED,
+							METHOD_COL_ENABLED, FALSE,
+		                    -1);
+	}
+
+	gtk_combo_box_set_model (priv->method, GTK_TREE_MODEL (priv->method_store));
+
+	priv->addr_label = glade_xml_get_widget (xml, "ip6_addr_label");
+	priv->addr_add = GTK_BUTTON (glade_xml_get_widget (xml, "ip6_addr_add_button"));
+	priv->addr_delete = GTK_BUTTON (glade_xml_get_widget (xml, "ip6_addr_delete_button"));
+	priv->addr_list = GTK_TREE_VIEW (glade_xml_get_widget (xml, "ip6_addresses"));
+
+	priv->dns_servers_label = glade_xml_get_widget (xml, "ip6_dns_servers_label");
+	priv->dns_servers = GTK_ENTRY (glade_xml_get_widget (xml, "ip6_dns_servers_entry"));
+
+	priv->dns_searches_label = glade_xml_get_widget (xml, "ip6_dns_searches_label");
+	priv->dns_searches = GTK_ENTRY (glade_xml_get_widget (xml, "ip6_dns_searches_entry"));
+
+	priv->routes_button = GTK_BUTTON (glade_xml_get_widget (xml, "ip6_routes_button"));
+}
+
+static void
+method_changed (GtkComboBox *combo, gpointer user_data)
+{
+	CEPageIP6Private *priv = CE_PAGE_IP6_GET_PRIVATE (user_data);
+	guint32 method = IP6_METHOD_AUTO;
+	gboolean addr_enabled = FALSE;
+	gboolean dns_enabled = FALSE;
+	gboolean routes_enabled = FALSE;
+	GtkTreeIter iter;
+
+	if (gtk_combo_box_get_active_iter (priv->method, &iter)) {
+		gtk_tree_model_get (GTK_TREE_MODEL (priv->method_store), &iter,
+		                    METHOD_COL_NUM, &method, -1);
+	}
+
+	switch (method) {
+	case IP6_METHOD_AUTO:
+		addr_enabled = FALSE;
+		routes_enabled = TRUE;
+		break;
+	case IP6_METHOD_AUTO_ADDRESSES:
+		addr_enabled = FALSE;
+		dns_enabled = routes_enabled = TRUE;
+		break;
+	case IP6_METHOD_MANUAL:
+		addr_enabled = dns_enabled = routes_enabled = TRUE;
+		break;
+	default:
+		break;
+	}
+
+	gtk_widget_set_sensitive (priv->addr_label, addr_enabled);
+	gtk_widget_set_sensitive (GTK_WIDGET (priv->addr_add), addr_enabled);
+	gtk_widget_set_sensitive (GTK_WIDGET (priv->addr_delete), addr_enabled);
+	gtk_widget_set_sensitive (GTK_WIDGET (priv->addr_list), addr_enabled);
+	if (!addr_enabled) {
+		GtkListStore *store;
+
+		store = GTK_LIST_STORE (gtk_tree_view_get_model (priv->addr_list));
+		gtk_list_store_clear (store);
+	}
+
+	gtk_widget_set_sensitive (priv->dns_servers_label, dns_enabled);
+	gtk_widget_set_sensitive (GTK_WIDGET (priv->dns_servers), dns_enabled);
+	if (!dns_enabled)
+		gtk_entry_set_text (priv->dns_servers, "");
+
+	gtk_widget_set_sensitive (priv->dns_searches_label, dns_enabled);
+	gtk_widget_set_sensitive (GTK_WIDGET (priv->dns_searches), dns_enabled);
+	if (!dns_enabled)
+		gtk_entry_set_text (priv->dns_searches, "");
+
+	gtk_widget_set_sensitive (GTK_WIDGET (priv->routes_button), routes_enabled);
+
+	ce_page_changed (CE_PAGE (user_data));
+}
+
+typedef struct {
+	int method;
+	GtkComboBox *combo;
+} SetMethodInfo;
+
+static gboolean
+set_method (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data)
+{
+	SetMethodInfo *info = (SetMethodInfo *) user_data;
+	int method = 0;
+
+	gtk_tree_model_get (model, iter, METHOD_COL_NUM, &method, -1);
+	if (method == info->method) {
+		gtk_combo_box_set_active_iter (info->combo, iter);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static void
+populate_ui (CEPageIP6 *self)
+{
+	CEPageIP6Private *priv = CE_PAGE_IP6_GET_PRIVATE (self);
+	NMSettingIP6Config *setting = priv->setting;
+	GtkListStore *store;
+	GtkTreeIter model_iter;
+	int method = IP6_METHOD_IGNORE;
+	GString *string = NULL;
+	SetMethodInfo info;
+	const char *str_method;
+	int i;
+
+	/* Method */
+	gtk_combo_box_set_active (priv->method, 0);
+	str_method = nm_setting_ip6_config_get_method (setting);
+	if (str_method) {
+		if (!strcmp (str_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO))
+			method = IP6_METHOD_AUTO;
+		else if (!strcmp (str_method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL))
+			method = IP6_METHOD_LINK_LOCAL;
+		else if (!strcmp (str_method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL))
+			method = IP6_METHOD_MANUAL;
+		else if (!strcmp (str_method, NM_SETTING_IP6_CONFIG_METHOD_SHARED))
+			method = IP6_METHOD_SHARED;
+	}
+
+	if (method == IP6_METHOD_AUTO && nm_setting_ip6_config_get_ignore_auto_dns (setting))
+		method = IP6_METHOD_AUTO_ADDRESSES;
+
+	info.method = method;
+	info.combo = priv->method;
+	gtk_tree_model_foreach (GTK_TREE_MODEL (priv->method_store), set_method, &info);
+
+	/* Addresses */
+	store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+	for (i = 0; i < nm_setting_ip6_config_get_num_addresses (setting); i++) {
+		NMIP6Address *addr = nm_setting_ip6_config_get_address (setting, i);
+		const struct in6_addr *tmp_addr;
+		char buf[INET6_ADDRSTRLEN + 1];
+		const char *ignored;
+
+		if (!addr) {
+			g_warning ("%s: empty IP6 Address structure!", __func__);
+			continue;
+		}
+
+		gtk_list_store_append (store, &model_iter);
+
+		tmp_addr = nm_ip6_address_get_address (addr);
+		ignored = inet_ntop (AF_INET6, tmp_addr, &buf[0], sizeof (buf));
+		gtk_list_store_set (store, &model_iter, COL_ADDRESS, buf, -1);
+		snprintf (buf, sizeof (buf), "%u", nm_ip6_address_get_prefix (addr));
+		gtk_list_store_set (store, &model_iter, COL_PREFIX, buf, -1);
+	}
+
+	gtk_tree_view_set_model (priv->addr_list, GTK_TREE_MODEL (store));
+	g_signal_connect_swapped (store, "row-inserted", G_CALLBACK (ce_page_changed), self);
+	g_signal_connect_swapped (store, "row-deleted", G_CALLBACK (ce_page_changed), self);
+	g_object_unref (store);
+
+	/* DNS servers */
+	string = g_string_new ("");
+	for (i = 0; i < nm_setting_ip6_config_get_num_dns (setting); i++) {
+		const struct in6_addr *tmp_addr;
+		char buf[INET6_ADDRSTRLEN + 1];
+		const char *ignored;
+
+		tmp_addr = nm_setting_ip6_config_get_dns (setting, i);
+		if (!tmp_addr)
+			continue;
+
+		ignored = inet_ntop (AF_INET6, tmp_addr, &buf[0], sizeof (buf));
+		if (string->len)
+			g_string_append (string, ", ");
+		g_string_append (string, buf);
+	}
+	gtk_entry_set_text (priv->dns_servers, string->str);
+	g_string_free (string, TRUE);
+
+	/* DNS searches */
+	string = g_string_new ("");
+	for (i = 0; i < nm_setting_ip6_config_get_num_dns_searches (setting); i++) {
+		if (string->len)
+			g_string_append (string, ", ");
+		g_string_append (string, nm_setting_ip6_config_get_dns_search (setting, i));
+	}
+	gtk_entry_set_text (priv->dns_searches, string->str);
+	g_string_free (string, TRUE);
+}
+
+static void
+addr_add_clicked (GtkButton *button, gpointer user_data)
+{
+	CEPageIP6Private *priv = CE_PAGE_IP6_GET_PRIVATE (user_data);
+	GtkListStore *store;
+	GtkTreeIter iter;
+	GtkTreeSelection *selection;
+	GtkTreeViewColumn *column;
+	GtkTreePath *path;
+	GList *cells;
+
+	store = GTK_LIST_STORE (gtk_tree_view_get_model (priv->addr_list));
+	gtk_list_store_append (store, &iter);
+	gtk_list_store_set (store, &iter, COL_ADDRESS, "", -1);
+
+	selection = gtk_tree_view_get_selection (priv->addr_list);
+	gtk_tree_selection_select_iter (selection, &iter);
+
+	path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
+	column = gtk_tree_view_get_column (priv->addr_list, COL_ADDRESS);
+
+	/* FIXME: using cells->data is pretty fragile but GTK apparently doesn't
+	 * have a way to get a cell renderer from a column based on path or iter
+	 * or whatever.
+	 */
+	cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
+	gtk_tree_view_set_cursor_on_cell (priv->addr_list, path, column, cells->data, TRUE);
+
+	g_list_free (cells);
+	gtk_tree_path_free (path);
+}
+
+static void
+addr_delete_clicked (GtkButton *button, gpointer user_data)
+{
+	GtkTreeView *treeview = GTK_TREE_VIEW (user_data);
+	GtkTreeSelection *selection;
+	GList *selected_rows;
+	GtkTreeModel *model = NULL;
+	GtkTreeIter iter;
+	int num_rows;
+
+	selection = gtk_tree_view_get_selection (treeview);
+	if (gtk_tree_selection_count_selected_rows (selection) != 1)
+		return;
+
+	selected_rows = gtk_tree_selection_get_selected_rows (selection, &model);
+	if (!selected_rows)
+		return;
+
+	if (gtk_tree_model_get_iter (model, &iter, (GtkTreePath *) selected_rows->data))
+		gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+
+	g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
+	g_list_free (selected_rows);
+
+	num_rows = gtk_tree_model_iter_n_children (model, NULL);
+	if (num_rows && gtk_tree_model_iter_nth_child (model, &iter, NULL, num_rows - 1)) {
+		selection = gtk_tree_view_get_selection (treeview);
+		gtk_tree_selection_select_iter (selection, &iter);
+	}
+}
+
+static void
+list_selection_changed (GtkTreeSelection *selection, gpointer user_data)
+{
+	GtkWidget *button = GTK_WIDGET (user_data);
+	GtkTreeIter iter;
+	GtkTreeModel *model = NULL;
+
+	if (gtk_tree_selection_get_selected (selection, &model, &iter))
+		gtk_widget_set_sensitive (button, TRUE);
+	else
+		gtk_widget_set_sensitive (button, FALSE);
+}
+
+static void
+cell_edited (GtkCellRendererText *cell,
+             const gchar *path_string,
+             const gchar *new_text,
+             gpointer user_data)
+{
+	CEPageIP6 *self = CE_PAGE_IP6 (user_data);
+	CEPageIP6Private *priv = CE_PAGE_IP6_GET_PRIVATE (self);
+	GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (priv->addr_list));
+	GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
+	GtkTreeIter iter;
+	guint32 column;
+	GtkTreeViewColumn *next_col;
+
+	column = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (cell), "column"));
+	gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path);
+	gtk_list_store_set (store, &iter, column, new_text, -1);
+
+	/* Move focus to the next column */
+	column = (column >= COL_LAST) ? 0 : column + 1;
+	next_col = gtk_tree_view_get_column (priv->addr_list, column);
+	gtk_tree_view_set_cursor_on_cell (priv->addr_list, path, next_col, priv->addr_cells[column], TRUE);
+	gtk_widget_grab_focus (GTK_WIDGET (priv->addr_list));
+
+	gtk_tree_path_free (path);
+	ce_page_changed (CE_PAGE (self));
+}
+
+static void
+ip_address_filter_cb (GtkEntry *   entry,
+                      const gchar *text,
+                      gint         length,
+                      gint *       position,
+                      gpointer     data)
+{
+	GtkEditable *editable = GTK_EDITABLE (entry);
+	gboolean numeric = GPOINTER_TO_INT (data);
+	int i, count = 0;
+	gchar *result = g_new (gchar, length);
+
+	for (i = 0; i < length; i++) {
+		if ((numeric && g_ascii_isdigit (text[i])) ||
+			(!numeric && (g_ascii_isxdigit(text[i]) || (text[i] == ':'))))
+			result[count++] = text[i];
+	}
+
+	if (count > 0) {
+		g_signal_handlers_block_by_func (G_OBJECT (editable),
+		                                 G_CALLBACK (ip_address_filter_cb),
+		                                 data);
+		gtk_editable_insert_text (editable, result, count, position);
+		g_signal_handlers_unblock_by_func (G_OBJECT (editable),
+		                                   G_CALLBACK (ip_address_filter_cb),
+		                                   data);
+	}
+
+	g_signal_stop_emission_by_name (G_OBJECT (editable), "insert-text");
+	g_free (result);
+}
+
+static void
+cell_editing_started (GtkCellRenderer *cell,
+                      GtkCellEditable *editable,
+                      const gchar     *path,
+                      gpointer         data)
+{
+	if (!GTK_IS_ENTRY (editable)) {
+		g_warning ("%s: Unexpected cell editable type.", __func__);
+		return;
+	}
+
+	/* Set up the entry filter */
+	g_signal_connect (G_OBJECT (editable), "insert-text",
+	                  (GCallback) ip_address_filter_cb,
+	                  data);
+}
+
+static void
+routes_dialog_close_cb (GtkWidget *dialog, gpointer user_data)
+{
+	gtk_widget_hide (dialog);
+	/* gtk_widget_destroy() will remove the window from the window group */
+	gtk_widget_destroy (dialog);
+}
+
+static void
+routes_dialog_response_cb (GtkWidget *dialog, gint response, gpointer user_data)
+{
+	CEPageIP6 *self = CE_PAGE_IP6 (user_data);
+	CEPageIP6Private *priv = CE_PAGE_IP6_GET_PRIVATE (self);
+
+	if (response == GTK_RESPONSE_OK)
+		ip6_routes_dialog_update_setting (dialog, priv->setting);
+
+	routes_dialog_close_cb (dialog, NULL);
+}
+
+static void
+routes_button_clicked_cb (GtkWidget *button, gpointer user_data)
+{
+	CEPageIP6 *self = CE_PAGE_IP6 (user_data);
+	CEPageIP6Private *priv = CE_PAGE_IP6_GET_PRIVATE (self);
+	GtkWidget *dialog, *toplevel;
+	gboolean automatic = FALSE;
+	const char *method;
+	char *tmp;
+
+	toplevel = gtk_widget_get_toplevel (CE_PAGE (self)->page);
+	g_return_if_fail (GTK_WIDGET_TOPLEVEL (toplevel));
+
+	method = nm_setting_ip6_config_get_method (priv->setting);
+	if (!method || !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_AUTO))
+		automatic = TRUE;
+
+	dialog = ip6_routes_dialog_new (priv->setting, automatic);
+	if (!dialog) {
+		g_warning ("%s: failed to create the routes dialog!", __func__);
+		return;
+	}
+
+	gtk_window_group_add_window (priv->window_group, GTK_WINDOW (dialog));
+	if (!priv->window_added) {
+		gtk_window_group_add_window (priv->window_group, GTK_WINDOW (toplevel));
+		priv->window_added = TRUE;
+	}
+
+	gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (toplevel));
+	tmp = g_strdup_printf (_("Editing IPv6 routes for %s"), priv->connection_id);
+	gtk_window_set_title (GTK_WINDOW (dialog), tmp);
+	g_free (tmp);
+
+	g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (routes_dialog_response_cb), self);
+	g_signal_connect (G_OBJECT (dialog), "close", G_CALLBACK (routes_dialog_close_cb), self);
+
+	gtk_widget_show_all (dialog);
+}
+
+static void
+finish_setup (CEPageIP6 *self, gpointer unused, GError *error, gpointer user_data)
+{
+	CEPageIP6Private *priv = CE_PAGE_IP6_GET_PRIVATE (self);
+	GtkTreeSelection *selection;
+	gint offset;
+	GtkTreeViewColumn *column;
+	GtkCellRenderer *renderer;
+	GtkListStore *store;
+
+	if (error)
+		return;
+
+	populate_ui (self);
+
+	/* Address column */
+	store = GTK_LIST_STORE (gtk_tree_view_get_model (priv->addr_list));
+
+	/* IP Address column */
+	renderer = gtk_cell_renderer_text_new ();
+	g_object_set (renderer, "editable", TRUE, NULL);
+	g_signal_connect (renderer, "edited", G_CALLBACK (cell_edited), self);
+	g_object_set_data (G_OBJECT (renderer), "column", GUINT_TO_POINTER (COL_ADDRESS));
+	g_signal_connect (renderer, "editing-started", G_CALLBACK (cell_editing_started), GINT_TO_POINTER (FALSE));
+	priv->addr_cells[COL_ADDRESS] = GTK_CELL_RENDERER (renderer);
+
+	offset = gtk_tree_view_insert_column_with_attributes (priv->addr_list,
+	                                                      -1, _("Address"), renderer,
+	                                                      "text", COL_ADDRESS,
+	                                                      NULL);
+	column = gtk_tree_view_get_column (GTK_TREE_VIEW (priv->addr_list), offset - 1);
+	gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column), TRUE);
+	gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), TRUE);
+
+	/* Prefix column */
+	renderer = gtk_cell_renderer_text_new ();
+	g_object_set (renderer, "editable", TRUE, NULL);
+	g_signal_connect (renderer, "edited", G_CALLBACK (cell_edited), self);
+	g_object_set_data (G_OBJECT (renderer), "column", GUINT_TO_POINTER (COL_PREFIX));
+	g_signal_connect (renderer, "editing-started", G_CALLBACK (cell_editing_started), GINT_TO_POINTER (TRUE));
+	priv->addr_cells[COL_PREFIX] = GTK_CELL_RENDERER (renderer);
+
+	offset = gtk_tree_view_insert_column_with_attributes (priv->addr_list,
+	                                                      -1, _("Prefix"), renderer,
+	                                                      "text", COL_PREFIX,
+	                                                      NULL);
+	column = gtk_tree_view_get_column (GTK_TREE_VIEW (priv->addr_list), offset - 1);
+	gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column), TRUE);
+	gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), TRUE);
+
+	gtk_widget_set_sensitive (GTK_WIDGET (priv->addr_add), TRUE);
+	gtk_widget_set_sensitive (GTK_WIDGET (priv->addr_delete), FALSE);
+
+	g_signal_connect (priv->addr_add, "clicked", G_CALLBACK (addr_add_clicked), self);
+	g_signal_connect (priv->addr_delete, "clicked", G_CALLBACK (addr_delete_clicked), priv->addr_list);
+	selection = gtk_tree_view_get_selection (priv->addr_list);
+	g_signal_connect (selection, "changed", G_CALLBACK (list_selection_changed), priv->addr_delete);
+
+	g_signal_connect_swapped (priv->dns_servers, "changed", G_CALLBACK (ce_page_changed), self);
+	g_signal_connect_swapped (priv->dns_searches, "changed", G_CALLBACK (ce_page_changed), self);
+
+	method_changed (priv->method, self);
+	g_signal_connect (priv->method, "changed", G_CALLBACK (method_changed), self);
+
+	g_signal_connect (priv->routes_button, "clicked", G_CALLBACK (routes_button_clicked_cb), self);
+}
+
+CEPage *
+ce_page_ip6_new (NMConnection *connection, GtkWindow *parent_window, GError **error)
+{
+	CEPageIP6 *self;
+	CEPageIP6Private *priv;
+	CEPage *parent;
+	NMSettingConnection *s_con;
+
+	self = CE_PAGE_IP6 (g_object_new (CE_TYPE_PAGE_IP6,
+	                                  CE_PAGE_CONNECTION, connection,
+	                                  CE_PAGE_PARENT_WINDOW, parent_window,
+	                                  NULL));
+	parent = CE_PAGE (self);
+
+	parent->xml = glade_xml_new (GLADEDIR "/ce-page-ip6.glade", "IP6Page", NULL);
+	if (!parent->xml) {
+		g_set_error (error, 0, 0, "%s", _("Could not load IPv6 user interface."));
+		g_object_unref (self);
+		return NULL;
+	}
+
+	parent->page = glade_xml_get_widget (parent->xml, "IP6Page");
+	if (!parent->page) {
+		g_set_error (error, 0, 0, "%s", _("Could not load IPv6 user interface."));
+		g_object_unref (self);
+		return NULL;
+	}
+	g_object_ref_sink (parent->page);
+
+	parent->title = g_strdup (_("IPv6 Settings"));
+
+	ip6_private_init (self, connection);
+	priv = CE_PAGE_IP6_GET_PRIVATE (self);
+
+	priv->window_group = gtk_window_group_new ();
+
+	s_con = NM_SETTING_CONNECTION (nm_connection_get_setting (connection, NM_TYPE_SETTING_CONNECTION));
+	g_assert (s_con);
+	priv->connection_id = g_strdup (nm_setting_connection_get_id (s_con));
+
+	priv->setting = (NMSettingIP6Config *) nm_connection_get_setting (connection, NM_TYPE_SETTING_IP6_CONFIG);
+	if (!priv->setting) {
+		priv->setting = NM_SETTING_IP6_CONFIG (nm_setting_ip6_config_new ());
+		nm_connection_add_setting (connection, NM_SETTING (priv->setting));
+	}
+
+	g_signal_connect (self, "initialized", G_CALLBACK (finish_setup), NULL);
+	if (!ce_page_initialize (parent, NULL, error)) {
+		g_object_unref (self);
+		return NULL;
+	}
+
+	return CE_PAGE (self);
+}
+
+static gboolean
+ui_to_setting (CEPageIP6 *self)
+{
+	CEPageIP6Private *priv = CE_PAGE_IP6_GET_PRIVATE (self);
+	GtkTreeModel *model;
+	GtkTreeIter tree_iter;
+	int int_method = IP6_METHOD_AUTO;
+	const char *method;
+	gboolean valid = FALSE, iter_valid;
+	const char *text;
+	gboolean ignore_auto_dns = FALSE;
+	char **items = NULL, **iter;
+
+	/* Method */
+	if (gtk_combo_box_get_active_iter (priv->method, &tree_iter)) {
+		gtk_tree_model_get (GTK_TREE_MODEL (priv->method_store), &tree_iter,
+		                    METHOD_COL_NUM, &int_method, -1);
+	}
+
+	switch (int_method) {
+	case IP6_METHOD_LINK_LOCAL:
+		method = NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL;
+		break;
+	case IP6_METHOD_MANUAL:
+		method = NM_SETTING_IP6_CONFIG_METHOD_MANUAL;
+		break;
+	case IP6_METHOD_SHARED:
+		method = NM_SETTING_IP6_CONFIG_METHOD_SHARED;
+		break;
+	case IP6_METHOD_AUTO_ADDRESSES:
+		ignore_auto_dns = TRUE;
+		/* fall through */
+	default:
+		method = NM_SETTING_IP6_CONFIG_METHOD_AUTO;
+		break;
+	}
+
+	g_object_freeze_notify (G_OBJECT (priv->setting));
+	g_object_set (priv->setting,
+				  NM_SETTING_IP6_CONFIG_METHOD, method,
+				  NM_SETTING_IP6_CONFIG_IGNORE_AUTO_DNS, ignore_auto_dns,
+				  NULL);
+
+	/* IP addresses */
+	nm_setting_ip6_config_clear_addresses (priv->setting);
+	model = gtk_tree_view_get_model (priv->addr_list);
+	iter_valid = gtk_tree_model_get_iter_first (model, &tree_iter);
+	while (iter_valid) {
+		char *item = NULL, *end;
+		struct in6_addr tmp_addr;
+		NMIP6Address *addr;
+		guint32 prefix;
+
+		gtk_tree_model_get (model, &tree_iter, COL_ADDRESS, &item, -1);
+		if (!item || !inet_pton (AF_INET6, item, &tmp_addr)) {
+			g_warning ("%s: IPv6 address '%s' missing or invalid!",
+			           __func__, item ? item : "<none>");
+			g_free (item);
+			goto out;
+		}
+		g_free (item);
+
+		gtk_tree_model_get (model, &tree_iter, COL_PREFIX, &item, -1);
+		if (!item) {
+			g_warning ("%s: IPv6 prefix '%s' missing!",
+			           __func__, item ? item : "<none>");
+			goto out;
+		}
+
+		prefix = strtoul (item, &end, 10);
+		if (!end || *end || prefix == 0 || prefix > 128) {
+			g_warning ("%s: IPv6 prefix '%s' invalid!",
+			           __func__, item ? item : "<none>");
+			g_free (item);
+			goto out;
+		}
+		g_free (item);
+
+		addr = nm_ip6_address_new ();
+		nm_ip6_address_set_address (addr, &tmp_addr);
+		nm_ip6_address_set_prefix (addr, prefix);
+		nm_setting_ip6_config_add_address (priv->setting, addr);
+		nm_ip6_address_unref (addr);
+
+		iter_valid = gtk_tree_model_iter_next (model, &tree_iter);
+	}
+
+	/* DNS servers */
+	nm_setting_ip6_config_clear_dns (priv->setting);
+	text = gtk_entry_get_text (GTK_ENTRY (priv->dns_servers));
+	if (text && strlen (text)) {
+		items = g_strsplit_set (text, ", ;", 0);
+		for (iter = items; *iter; iter++) {
+			struct in6_addr tmp_addr;
+			char *stripped = g_strstrip (*iter);
+
+			if (!strlen (stripped))
+				continue;
+
+			if (inet_pton (AF_INET6, stripped, &tmp_addr)) {
+				nm_setting_ip6_config_add_dns (priv->setting, &tmp_addr);
+			} else {
+				g_strfreev (items);
+				goto out;
+			}
+		}
+		g_strfreev (items);
+	}
+
+	/* Search domains */
+	nm_setting_ip6_config_clear_dns_searches (priv->setting);
+	text = gtk_entry_get_text (GTK_ENTRY (priv->dns_searches));
+	if (text && strlen (text)) {
+		items = g_strsplit_set (text, ", ;:", 0);
+		for (iter = items; *iter; iter++) {
+			char *stripped = g_strstrip (*iter);
+
+			if (strlen (stripped))
+				nm_setting_ip6_config_add_dns_search (priv->setting, stripped);
+		}
+
+		if (items)
+			g_strfreev (items);
+	}
+
+	valid = TRUE;
+
+out:
+	g_object_thaw_notify (G_OBJECT (priv->setting));
+
+	return valid;
+}
+
+static gboolean
+validate (CEPage *page, NMConnection *connection, GError **error)
+{
+	CEPageIP6 *self = CE_PAGE_IP6 (page);
+	CEPageIP6Private *priv = CE_PAGE_IP6_GET_PRIVATE (self);
+
+	if (!ui_to_setting (self))
+		return FALSE;
+	return nm_setting_verify (NM_SETTING (priv->setting), NULL, error);
+}
+
+static void
+ce_page_ip6_init (CEPageIP6 *self)
+{
+}
+
+static void
+dispose (GObject *object)
+{
+	CEPageIP6 *self = CE_PAGE_IP6 (object);
+	CEPageIP6Private *priv = CE_PAGE_IP6_GET_PRIVATE (self);
+
+	if (priv->window_group)
+		g_object_unref (priv->window_group);
+
+	g_free (priv->connection_id);
+
+	G_OBJECT_CLASS (ce_page_ip6_parent_class)->dispose (object);
+}
+
+static void
+ce_page_ip6_class_init (CEPageIP6Class *ip6_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (ip6_class);
+	CEPageClass *parent_class = CE_PAGE_CLASS (ip6_class);
+
+	g_type_class_add_private (object_class, sizeof (CEPageIP6Private));
+
+	/* virtual methods */
+	parent_class->validate = validate;
+	object_class->dispose = dispose;
+}
diff --git a/src/connection-editor/page-ip6.h b/src/connection-editor/page-ip6.h
new file mode 100644
index 0000000..081b1fc
--- /dev/null
+++ b/src/connection-editor/page-ip6.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* NetworkManager Connection editor -- Connection editor for NetworkManager
+ *
+ * Dan Williams <dcbw redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * (C) Copyright 2008 Red Hat, Inc.
+ */
+
+#ifndef __PAGE_IP6_H__
+#define __PAGE_IP6_H__
+
+#include <nm-connection.h>
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "ce-page.h"
+
+#define CE_TYPE_PAGE_IP6            (ce_page_ip6_get_type ())
+#define CE_PAGE_IP6(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), CE_TYPE_PAGE_IP6, CEPageIP6))
+#define CE_PAGE_IP6_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), CE_TYPE_PAGE_IP6, CEPageIP6Class))
+#define CE_IS_PAGE_IP6(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CE_TYPE_PAGE_IP6))
+#define CE_IS_PAGE_IP6_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), CE_TYPE_PAGE_IP6))
+#define CE_PAGE_IP6_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), CE_TYPE_PAGE_IP6, CEPageIP6Class))
+
+typedef struct {
+	CEPage parent;
+} CEPageIP6;
+
+typedef struct {
+	CEPageClass parent;
+} CEPageIP6Class;
+
+GType ce_page_ip6_get_type (void);
+
+CEPage *ce_page_ip6_new (NMConnection *connection, GtkWindow *parent, GError **error);
+
+#endif  /* __PAGE_IP6_H__ */
+



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]