[lxc-devel] [lxd/master] RFC: Network API

stgraber on Github lxc-bot at linuxcontainers.org
Thu Sep 22 03:51:32 UTC 2016


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 1129 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20160922/81b06922/attachment.bin>
-------------- next part --------------
From 1bfc980f098a2de44ecdbdb979a07169d809b582 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 31 Aug 2016 16:38:17 -0400
Subject: [PATCH 01/46] network: API extension
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/api-extensions.md | 13 +++++++++++++
 lxd/api_1.0.go        |  1 +
 2 files changed, 14 insertions(+)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index 2bc8dc4..268bf44 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -101,3 +101,16 @@ considered as part of the container's disk space usage.
 Adds a new "storage.lvm\_mount\_options" daemon configuration option
 which defaults to "discard" and allows the user to set addition mount
 options for the filesystem used by the LVM LV.
+
+## network
+Network management API for LXD.
+
+This includes:
+ * Addition of the "managed" property on /1.0/networks entries
+ * All the network configuration options (see configuration.md for details)
+ * POST /1.0/networks (see rest-api.md for details)
+ * PUT /1.0/networks/<entry> (see rest-api.md for details)
+ * PATCH /1.0/networks/<entry> (see rest-api.md for details)
+ * DELETE /1.0/networks/<entry> (see rest-api.md for details)
+ * ipv4.address property on "nic" type devices (when nictype is "bridged")
+ * ipv6.address property on "nic" type devices (when nictype is "bridged")
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 1fa1858..d229f5f 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -69,6 +69,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 			"container_cpu_time",
 			"storage_zfs_use_refquota",
 			"storage_lvm_mount_options",
+			"network",
 		},
 
 		"api_status":  "stable",

From df5f2cbfd5ba2b18cabb7d5b34139213b987ae1e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 31 Aug 2016 17:54:05 -0400
Subject: [PATCH 02/46] network: Database schema
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db.go                      | 13 +++++++++++++
 lxd/db_update.go               | 20 ++++++++++++++++++++
 test/suites/database_update.sh |  6 +++---
 test/suites/migration.sh       |  3 +++
 4 files changed, 39 insertions(+), 3 deletions(-)

diff --git a/lxd/db.go b/lxd/db.go
index de62862..a76397b 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -134,6 +134,19 @@ CREATE TABLE IF NOT EXISTS images_source (
     alias VARCHAR(255) NOT NULL,
     FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE
 );
+CREATE TABLE IF NOT EXISTS networks (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    name VARCHAR(255) NOT NULL,
+    UNIQUE (name)
+);
+CREATE TABLE IF NOT EXISTS networks_config (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    network_id INTEGER NOT NULL,
+    key VARCHAR(255) NOT NULL,
+    value TEXT,
+    UNIQUE (network_id, key),
+    FOREIGN KEY (network_id) REFERENCES networks (id) ON DELETE CASCADE
+);
 CREATE TABLE IF NOT EXISTS patches (
     id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
     name VARCHAR(255) NOT NULL,
diff --git a/lxd/db_update.go b/lxd/db_update.go
index 936d256..b6c1ca8 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -67,6 +67,7 @@ var dbUpdates = []dbUpdate{
 	dbUpdate{version: 31, run: dbUpdateFromV30},
 	dbUpdate{version: 32, run: dbUpdateFromV31},
 	dbUpdate{version: 33, run: dbUpdateFromV32},
+	dbUpdate{version: 34, run: dbUpdateFromV33},
 }
 
 type dbUpdate struct {
@@ -123,6 +124,25 @@ func dbUpdatesApplyAll(d *Daemon) error {
 }
 
 // Schema updates begin here
+func dbUpdateFromV33(currentVersion int, version int, d *Daemon) error {
+	stmt := `
+CREATE TABLE IF NOT EXISTS networks (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    name VARCHAR(255) NOT NULL,
+    UNIQUE (name)
+);
+CREATE TABLE IF NOT EXISTS networks_config (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    network_id INTEGER NOT NULL,
+    key VARCHAR(255) NOT NULL,
+    value TEXT,
+    UNIQUE (network_id, key),
+    FOREIGN KEY (network_id) REFERENCES networks (id) ON DELETE CASCADE
+);`
+	_, err := d.db.Exec(stmt)
+	return err
+}
+
 func dbUpdateFromV32(currentVersion int, version int, d *Daemon) error {
 	_, err := d.db.Exec("ALTER TABLE containers ADD COLUMN last_use_date DATETIME;")
 	return err
diff --git a/test/suites/database_update.sh b/test/suites/database_update.sh
index e178ee9..4856470 100644
--- a/test/suites/database_update.sh
+++ b/test/suites/database_update.sh
@@ -11,12 +11,12 @@ test_database_update(){
   spawn_lxd "${LXD_MIGRATE_DIR}"
 
   # Assert there are enough tables.
-  expected_tables=17
+  expected_tables=19
   tables=$(sqlite3 "${MIGRATE_DB}" ".dump" | grep -c "CREATE TABLE")
   [ "${tables}" -eq "${expected_tables}" ] || { echo "FAIL: Wrong number of tables after database migration. Found: ${tables}, expected ${expected_tables}"; false; }
 
-  # There should be 10 "ON DELETE CASCADE" occurences
-  expected_cascades=11
+  # There should be 13 "ON DELETE CASCADE" occurences
+  expected_cascades=12
   cascades=$(sqlite3 "${MIGRATE_DB}" ".dump" | grep -c "ON DELETE CASCADE")
   [ "${cascades}" -eq "${expected_cascades}" ] || { echo "FAIL: Wrong number of ON DELETE CASCADE foreign keys. Found: ${cascades}, exected: ${expected_cascades}"; false; }
 }
diff --git a/test/suites/migration.sh b/test/suites/migration.sh
index 0c865f6..ee7a473 100644
--- a/test/suites/migration.sh
+++ b/test/suites/migration.sh
@@ -3,6 +3,9 @@
 test_migration() {
   ensure_import_testimage
 
+  # workaround for kernel/criu
+  umount /sys/kernel/debug >/dev/null 2>&1 || true
+
   if ! lxc_remote remote list | grep -q l1; then
     lxc_remote remote add l1 "${LXD_ADDR}" --accept-certificate --password foo
   fi

From d98cf680a8e6d491b5c3ae3863f6fe81e37cd4ab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 31 Aug 2016 21:36:48 -0400
Subject: [PATCH 03/46] network: Implement GET
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db_networks.go  | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 lxd/networks.go     | 26 +++++++++---------
 shared/container.go |  8 ++++++
 3 files changed, 99 insertions(+), 12 deletions(-)
 create mode 100644 lxd/db_networks.go

diff --git a/lxd/db_networks.go b/lxd/db_networks.go
new file mode 100644
index 0000000..5595cfc
--- /dev/null
+++ b/lxd/db_networks.go
@@ -0,0 +1,77 @@
+package main
+
+import (
+	"database/sql"
+	"fmt"
+
+	_ "github.com/mattn/go-sqlite3"
+
+	"github.com/lxc/lxd/shared"
+)
+
+func dbNetworkGet(db *sql.DB, network string) (int64, *shared.NetworkConfig, error) {
+	id := int64(-1)
+
+	q := "SELECT id FROM networks WHERE name=?"
+	arg1 := []interface{}{network}
+	arg2 := []interface{}{&id}
+	err := dbQueryRowScan(db, q, arg1, arg2)
+	if err != nil {
+		return -1, nil, err
+	}
+
+	config, err := dbNetworkConfigGet(db, id)
+	if err != nil {
+		return -1, nil, err
+	}
+
+	return id, &shared.NetworkConfig{
+		Name:    network,
+		Managed: true,
+		Type:    "bridge",
+		Config:  config,
+	}, nil
+}
+
+func dbNetworkConfigGet(db *sql.DB, id int64) (map[string]string, error) {
+	var key, value string
+	query := `
+        SELECT
+            key, value
+        FROM networks_config
+		WHERE network_id=?`
+	inargs := []interface{}{id}
+	outfmt := []interface{}{key, value}
+	results, err := dbQueryScan(db, query, inargs, outfmt)
+	if err != nil {
+		return nil, fmt.Errorf("Failed to get network '%d'", id)
+	}
+
+	if len(results) == 0 {
+		/*
+		 * If we didn't get any rows here, let's check to make sure the
+		 * network really exists; if it doesn't, let's send back a 404.
+		 */
+		query := "SELECT id FROM networks WHERE id=?"
+		var r int
+		results, err := dbQueryScan(db, query, []interface{}{id}, []interface{}{r})
+		if err != nil {
+			return nil, err
+		}
+
+		if len(results) == 0 {
+			return nil, NoSuchObjectError
+		}
+	}
+
+	config := map[string]string{}
+
+	for _, r := range results {
+		key = r[0].(string)
+		value = r[1].(string)
+
+		config[key] = value
+	}
+
+	return config, nil
+}
diff --git a/lxd/networks.go b/lxd/networks.go
index a5b29e3..8c164e1 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -49,7 +49,7 @@ func networksGet(d *Daemon, r *http.Request) Response {
 	}
 
 	resultString := []string{}
-	resultMap := []network{}
+	resultMap := []shared.NetworkConfig{}
 	for _, iface := range ifs {
 		if recursion == 0 {
 			resultString = append(resultString, fmt.Sprintf("/%s/networks/%s", shared.APIVersion, iface.Name))
@@ -72,12 +72,6 @@ func networksGet(d *Daemon, r *http.Request) Response {
 
 var networksCmd = Command{name: "networks", get: networksGet}
 
-type network struct {
-	Name   string   `json:"name"`
-	Type   string   `json:"type"`
-	UsedBy []string `json:"used_by"`
-}
-
 func networkGet(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 
@@ -89,27 +83,28 @@ func networkGet(d *Daemon, r *http.Request) Response {
 	return SyncResponse(true, &n)
 }
 
-func doNetworkGet(d *Daemon, name string) (network, error) {
+func doNetworkGet(d *Daemon, name string) (shared.NetworkConfig, error) {
 	iface, err := net.InterfaceByName(name)
 	if err != nil {
-		return network{}, err
+		return shared.NetworkConfig{}, err
 	}
 
 	// Prepare the response
-	n := network{}
+	n := shared.NetworkConfig{}
 	n.Name = iface.Name
 	n.UsedBy = []string{}
+	n.Config = map[string]string{}
 
 	// Look for containers using the interface
 	cts, err := dbContainersList(d.db, cTypeRegular)
 	if err != nil {
-		return network{}, err
+		return shared.NetworkConfig{}, err
 	}
 
 	for _, ct := range cts {
 		c, err := containerLoadByName(d, ct)
 		if err != nil {
-			return network{}, err
+			return shared.NetworkConfig{}, err
 		}
 
 		if networkIsInUse(c, n.Name) {
@@ -121,6 +116,13 @@ func doNetworkGet(d *Daemon, name string) (network, error) {
 	if shared.IsLoopback(iface) {
 		n.Type = "loopback"
 	} else if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", n.Name)) {
+		// Look for a DB entry
+		_, dbNetwork, err := dbNetworkGet(d.db, name)
+		if err == nil {
+			n.Managed = true
+			n.Config = dbNetwork.Config
+		}
+
 		n.Type = "bridge"
 	} else if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/device", n.Name)) {
 		n.Type = "physical"
diff --git a/shared/container.go b/shared/container.go
index eb8de7c..a1d0b7c 100644
--- a/shared/container.go
+++ b/shared/container.go
@@ -150,6 +150,14 @@ type ProfileConfig struct {
 	Devices     Devices           `json:"devices"`
 }
 
+type NetworkConfig struct {
+	Name    string            `json:"name"`
+	Config  map[string]string `json:"config"`
+	Managed bool              `json:"managed"`
+	Type    string            `json:"type"`
+	UsedBy  []string          `json:"used_by"`
+}
+
 func IsInt64(value string) error {
 	if value == "" {
 		return nil

From a3a2ac8479aa95aadca15dd748ab47d8d602d68b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 31 Aug 2016 22:50:55 -0400
Subject: [PATCH 04/46] network: Update rest-api.md
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/rest-api.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 50 insertions(+)

diff --git a/doc/rest-api.md b/doc/rest-api.md
index a49ea94..3b76670 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -1445,6 +1445,23 @@ Input (none at present):
         "/1.0/networks/lxdbr0"
     ]
 
+### POST
+ * Description: define a new network
+ * Authentication: trusted
+ * Operation: sync
+ * Return: standard return value or standard error
+
+Input:
+
+    {
+        "name": "my-network",
+        "config": {
+            "ipv4.address": "none",
+            "ipv6.address": "2001:470:b368:4242::1/64",
+            "ipv6.nat": "true"
+        }
+    }
+
 ## /1.0/networks/\<name\>
 ### GET
  * Description: information about a network
@@ -1453,13 +1470,46 @@ Input (none at present):
  * Return: dict representing a network
 
     {
+        "config": {},
         "name": "lxdbr0",
+        "managed": false,
         "type": "bridge",
         "used_by": [
             "/1.0/containers/blah"
         ]
     }
 
+### POST
+ * Description: rename a network
+ * Authentication: trusted
+ * Operation: sync
+ * Return: standard return value or standard error
+
+Input (rename a network):
+
+    {
+        "name": "new-name"
+    }
+
+
+HTTP return value must be 204 (No content) and Location must point to
+the renamed resource.
+
+Renaming to an existing name must return the 409 (Conflict) HTTP code.
+
+### DELETE
+ * Description: remove a network
+ * Authentication: trusted
+ * Operation: sync
+ * Return: standard return value or standard error
+
+Input (none at present):
+
+    {
+    }
+
+HTTP code for this should be 202 (Accepted).
+
 ## /1.0/operations
 ### GET
  * Description: list of operations

From 2fde981abc568a26904f03f5b6eaa9d8ef766a9e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 6 Sep 2016 23:13:24 -0400
Subject: [PATCH 05/46] network: Combine DB and system networks
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db_networks.go | 18 ++++++++++++++++++
 lxd/networks.go    | 27 +++++++++++++++++++++++----
 2 files changed, 41 insertions(+), 4 deletions(-)

diff --git a/lxd/db_networks.go b/lxd/db_networks.go
index 5595cfc..fb2159f 100644
--- a/lxd/db_networks.go
+++ b/lxd/db_networks.go
@@ -9,6 +9,24 @@ import (
 	"github.com/lxc/lxd/shared"
 )
 
+func dbNetworks(db *sql.DB) ([]string, error) {
+	q := fmt.Sprintf("SELECT name FROM networks")
+	inargs := []interface{}{}
+	var name string
+	outfmt := []interface{}{name}
+	result, err := dbQueryScan(db, q, inargs, outfmt)
+	if err != nil {
+		return []string{}, err
+	}
+
+	response := []string{}
+	for _, r := range result {
+		response = append(response, r[0].(string))
+	}
+
+	return response, nil
+}
+
 func dbNetworkGet(db *sql.DB, network string) (int64, *shared.NetworkConfig, error) {
 	id := int64(-1)
 
diff --git a/lxd/networks.go b/lxd/networks.go
index 8c164e1..1d90010 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -13,6 +13,26 @@ import (
 )
 
 // Helper functions
+func networkGetInterfaces(d *Daemon) ([]string, error) {
+	networks, err := dbNetworks(d.db)
+	if err != nil {
+		return nil, err
+	}
+
+	ifaces, err := net.Interfaces()
+	if err != nil {
+		return nil, err
+	}
+
+	for _, iface := range ifaces {
+		if !shared.StringInSlice(iface.Name, networks) {
+			networks = append(networks, iface.Name)
+		}
+	}
+
+	return networks, nil
+}
+
 func networkIsInUse(c container, name string) bool {
 	for _, d := range c.ExpandedDevices() {
 		if d["type"] != "nic" {
@@ -43,7 +63,7 @@ func networksGet(d *Daemon, r *http.Request) Response {
 		recursion = 0
 	}
 
-	ifs, err := net.Interfaces()
+	ifs, err := networkGetInterfaces(d)
 	if err != nil {
 		return InternalError(err)
 	}
@@ -52,14 +72,13 @@ func networksGet(d *Daemon, r *http.Request) Response {
 	resultMap := []shared.NetworkConfig{}
 	for _, iface := range ifs {
 		if recursion == 0 {
-			resultString = append(resultString, fmt.Sprintf("/%s/networks/%s", shared.APIVersion, iface.Name))
+			resultString = append(resultString, fmt.Sprintf("/%s/networks/%s", shared.APIVersion, iface))
 		} else {
-			net, err := doNetworkGet(d, iface.Name)
+			net, err := doNetworkGet(d, iface)
 			if err != nil {
 				continue
 			}
 			resultMap = append(resultMap, net)
-
 		}
 	}
 

From a880b9fbda7c6a6b010538a8693c3150dd761d03 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 6 Sep 2016 23:46:35 -0400
Subject: [PATCH 06/46] network: Implement POST
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db_networks.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
 lxd/networks.go    | 40 +++++++++++++++++++++++++++++++++++++++-
 2 files changed, 90 insertions(+), 1 deletion(-)

diff --git a/lxd/db_networks.go b/lxd/db_networks.go
index fb2159f..7df337f 100644
--- a/lxd/db_networks.go
+++ b/lxd/db_networks.go
@@ -93,3 +93,54 @@ func dbNetworkConfigGet(db *sql.DB, id int64) (map[string]string, error) {
 
 	return config, nil
 }
+
+func dbNetworkCreate(db *sql.DB, name string, config map[string]string) (int64, error) {
+	tx, err := dbBegin(db)
+	if err != nil {
+		return -1, err
+	}
+
+	result, err := tx.Exec("INSERT INTO networks (name) VALUES (?)", name)
+	if err != nil {
+		tx.Rollback()
+		return -1, err
+	}
+
+	id, err := result.LastInsertId()
+	if err != nil {
+		tx.Rollback()
+		return -1, err
+	}
+
+	err = dbNetworkConfigAdd(tx, id, config)
+	if err != nil {
+		tx.Rollback()
+		return -1, err
+	}
+
+	err = txCommit(tx)
+	if err != nil {
+		return -1, err
+	}
+
+	return id, nil
+}
+
+func dbNetworkConfigAdd(tx *sql.Tx, id int64, config map[string]string) error {
+	str := fmt.Sprintf("INSERT INTO networks_config (network_id, key, value) VALUES(?, ?, ?)")
+	stmt, err := tx.Prepare(str)
+	defer stmt.Close()
+
+	for k, v := range config {
+		if v == "" {
+			continue
+		}
+
+		_, err = stmt.Exec(id, k, v)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/lxd/networks.go b/lxd/networks.go
index 1d90010..ff3ba46 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"encoding/json"
 	"fmt"
 	"net"
 	"net/http"
@@ -89,7 +90,44 @@ func networksGet(d *Daemon, r *http.Request) Response {
 	return SyncResponse(true, resultMap)
 }
 
-var networksCmd = Command{name: "networks", get: networksGet}
+func networksPost(d *Daemon, r *http.Request) Response {
+	req := shared.NetworkConfig{}
+
+	// Parse the request
+	err := json.NewDecoder(r.Body).Decode(&req)
+	if err != nil {
+		return BadRequest(err)
+	}
+
+	// Sanity checks
+	if req.Name == "" {
+		return BadRequest(fmt.Errorf("No name provided"))
+	}
+
+	if req.Type != "" && req.Type != "bridge" {
+		return BadRequest(fmt.Errorf("Only 'bridge' type networks can be created"))
+	}
+
+	networks, err := networkGetInterfaces(d)
+	if err != nil {
+		return InternalError(err)
+	}
+
+	if shared.StringInSlice(req.Name, networks) {
+		return BadRequest(fmt.Errorf("The network already exists"))
+	}
+
+	// Create the database entry
+	_, err = dbNetworkCreate(d.db, req.Name, req.Config)
+	if err != nil {
+		return InternalError(
+			fmt.Errorf("Error inserting %s into database: %s", req.Name, err))
+	}
+
+	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/networks/%s", shared.APIVersion, req.Name))
+}
+
+var networksCmd = Command{name: "networks", get: networksGet, post: networksPost}
 
 func networkGet(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]

From 63f74a6298f1a3785ae1ac1e2a40e327b908e5cc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 6 Sep 2016 23:59:22 -0400
Subject: [PATCH 07/46] network: Fix doNetworkGet for DB-only entries
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go | 25 ++++++++++++++-----------
 1 file changed, 14 insertions(+), 11 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index ff3ba46..74fa4c7 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"net"
 	"net/http"
+	"os"
 	"os/exec"
 	"strconv"
 
@@ -134,21 +135,25 @@ func networkGet(d *Daemon, r *http.Request) Response {
 
 	n, err := doNetworkGet(d, name)
 	if err != nil {
-		return InternalError(err)
+		return SmartError(err)
 	}
 
 	return SyncResponse(true, &n)
 }
 
 func doNetworkGet(d *Daemon, name string) (shared.NetworkConfig, error) {
-	iface, err := net.InterfaceByName(name)
-	if err != nil {
-		return shared.NetworkConfig{}, err
+	// Get some information
+	osInfo, _ := net.InterfaceByName(name)
+	_, dbInfo, _ := dbNetworkGet(d.db, name)
+
+	// Sanity check
+	if osInfo == nil && dbInfo == nil {
+		return shared.NetworkConfig{}, os.ErrNotExist
 	}
 
 	// Prepare the response
 	n := shared.NetworkConfig{}
-	n.Name = iface.Name
+	n.Name = name
 	n.UsedBy = []string{}
 	n.Config = map[string]string{}
 
@@ -170,14 +175,12 @@ func doNetworkGet(d *Daemon, name string) (shared.NetworkConfig, error) {
 	}
 
 	// Set the device type as needed
-	if shared.IsLoopback(iface) {
+	if osInfo != nil && shared.IsLoopback(osInfo) {
 		n.Type = "loopback"
-	} else if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", n.Name)) {
-		// Look for a DB entry
-		_, dbNetwork, err := dbNetworkGet(d.db, name)
-		if err == nil {
+	} else if dbInfo != nil || shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", n.Name)) {
+		if dbInfo != nil {
 			n.Managed = true
-			n.Config = dbNetwork.Config
+			n.Config = dbInfo.Config
 		}
 
 		n.Type = "bridge"

From ea2a0d8c0e4bf1e7d9a8e94508efa84ee603dfc9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 7 Sep 2016 00:47:05 -0400
Subject: [PATCH 08/46] network: Implement DELETE
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db_networks.go | 35 +++++++++++++++++++++++++++++++++++
 lxd/networks.go    | 25 ++++++++++++++++++++++++-
 2 files changed, 59 insertions(+), 1 deletion(-)

diff --git a/lxd/db_networks.go b/lxd/db_networks.go
index 7df337f..2219d5b 100644
--- a/lxd/db_networks.go
+++ b/lxd/db_networks.go
@@ -144,3 +144,38 @@ func dbNetworkConfigAdd(tx *sql.Tx, id int64, config map[string]string) error {
 
 	return nil
 }
+
+func dbNetworkConfigClear(tx *sql.Tx, id int64) error {
+	_, err := tx.Exec("DELETE FROM networks_config WHERE network_id=?", id)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func dbNetworkDelete(db *sql.DB, name string) error {
+	id, _, err := dbNetworkGet(db, name)
+	if err != nil {
+		return err
+	}
+
+	tx, err := dbBegin(db)
+	if err != nil {
+		return err
+	}
+
+	_, err = tx.Exec("DELETE FROM networks WHERE id=?", id)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+
+	err = dbNetworkConfigClear(tx, id)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+
+	return txCommit(tx)
+}
diff --git a/lxd/networks.go b/lxd/networks.go
index 74fa4c7..257e6e9 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -200,4 +200,27 @@ func doNetworkGet(d *Daemon, name string) (shared.NetworkConfig, error) {
 	return n, nil
 }
 
-var networkCmd = Command{name: "networks/{name}", get: networkGet}
+func networkDelete(d *Daemon, r *http.Request) Response {
+	name := mux.Vars(r)["name"]
+
+	// Get the existing network
+	_, dbInfo, _ := dbNetworkGet(d.db, name)
+	if dbInfo == nil {
+		return NotFound
+	}
+
+	// Sanity checks
+	if len(dbInfo.UsedBy) != 0 {
+		return BadRequest(fmt.Errorf("Network is currently in use)"))
+	}
+
+	// Remove the network
+	err := dbNetworkDelete(d.db, name)
+	if err != nil {
+		return SmartError(err)
+	}
+
+	return EmptySyncResponse
+}
+
+var networkCmd = Command{name: "networks/{name}", get: networkGet, delete: networkDelete}

From e1844388e6c5f5b32a5188ff56f76d2ed5df8190 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 7 Sep 2016 16:18:24 -0400
Subject: [PATCH 09/46] network: Implement rename (POST)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db_networks.go | 20 ++++++++++++++++++++
 lxd/networks.go    | 46 +++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 65 insertions(+), 1 deletion(-)

diff --git a/lxd/db_networks.go b/lxd/db_networks.go
index 2219d5b..ae0b2be 100644
--- a/lxd/db_networks.go
+++ b/lxd/db_networks.go
@@ -179,3 +179,23 @@ func dbNetworkDelete(db *sql.DB, name string) error {
 
 	return txCommit(tx)
 }
+
+func dbNetworkRename(db *sql.DB, oldName string, newName string) error {
+	id, _, err := dbNetworkGet(db, oldName)
+	if err != nil {
+		return err
+	}
+
+	tx, err := dbBegin(db)
+	if err != nil {
+		return err
+	}
+
+	_, err = tx.Exec("UPDATE networks SET name=? WHERE id=?", newName, id)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+
+	return txCommit(tx)
+}
diff --git a/lxd/networks.go b/lxd/networks.go
index 257e6e9..760a9c1 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -223,4 +223,48 @@ func networkDelete(d *Daemon, r *http.Request) Response {
 	return EmptySyncResponse
 }
 
-var networkCmd = Command{name: "networks/{name}", get: networkGet, delete: networkDelete}
+func networkPost(d *Daemon, r *http.Request) Response {
+	name := mux.Vars(r)["name"]
+	req := shared.NetworkConfig{}
+
+	// Parse the request
+	err := json.NewDecoder(r.Body).Decode(&req)
+	if err != nil {
+		return BadRequest(err)
+	}
+
+	// Get the existing network
+	_, dbInfo, _ := dbNetworkGet(d.db, name)
+	if dbInfo == nil {
+		return NotFound
+	}
+
+	// Sanity checks
+	if len(dbInfo.UsedBy) != 0 {
+		return BadRequest(fmt.Errorf("Network is currently in use)"))
+	}
+
+	if req.Name == "" {
+		return BadRequest(fmt.Errorf("No name provided"))
+	}
+
+	// Check that the name isn't already in use
+	networks, err := networkGetInterfaces(d)
+	if err != nil {
+		return InternalError(err)
+	}
+
+	if shared.StringInSlice(req.Name, networks) {
+		return Conflict
+	}
+
+	// Rename the database entry
+	err = dbNetworkRename(d.db, name, req.Name)
+	if err != nil {
+		return SmartError(err)
+	}
+
+	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/networks/%s", shared.APIVersion, req.Name))
+}
+
+var networkCmd = Command{name: "networks/{name}", get: networkGet, delete: networkDelete, post: networkPost}

From 1a34f9dd847c86211644b73c84e17e329a095e91 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 7 Sep 2016 16:27:43 -0400
Subject: [PATCH 10/46] network: Add ETag support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index 760a9c1..21c30cc 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -138,7 +138,9 @@ func networkGet(d *Daemon, r *http.Request) Response {
 		return SmartError(err)
 	}
 
-	return SyncResponse(true, &n)
+	etag := []interface{}{n.Name, n.Managed, n.Type, n.Config}
+
+	return SyncResponseETag(true, &n, etag)
 }
 
 func doNetworkGet(d *Daemon, name string) (shared.NetworkConfig, error) {

From d5ab953241bc9cc7b0e88ef57351c948a1d8fd5f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 7 Sep 2016 16:53:24 -0400
Subject: [PATCH 11/46] network: Validate interface names
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/lxd/networks.go b/lxd/networks.go
index 21c30cc..7cf8d32 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -7,6 +7,7 @@ import (
 	"net/http"
 	"os"
 	"os/exec"
+	"regexp"
 	"strconv"
 
 	"github.com/gorilla/mux"
@@ -57,6 +58,25 @@ func networkIsInUse(c container, name string) bool {
 	return false
 }
 
+func networkValidateName(name string) error {
+	// Validate the length
+	if len(name) < 2 {
+		return fmt.Errorf("Interface name is too short (minimum 2 characters)")
+	}
+
+	if len(name) > 15 {
+		return fmt.Errorf("Interface name is too long (maximum 15 characters)")
+	}
+
+	// Validate the character set
+	match, _ := regexp.MatchString("^[-a-zA-Z0-9]*$", name)
+	if !match {
+		return fmt.Errorf("Interface name contains invalid characters")
+	}
+
+	return nil
+}
+
 // API endpoints
 func networksGet(d *Daemon, r *http.Request) Response {
 	recursionStr := r.FormValue("recursion")
@@ -105,6 +125,11 @@ func networksPost(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("No name provided"))
 	}
 
+	err = networkValidateName(req.Name)
+	if err != nil {
+		return BadRequest(err)
+	}
+
 	if req.Type != "" && req.Type != "bridge" {
 		return BadRequest(fmt.Errorf("Only 'bridge' type networks can be created"))
 	}
@@ -250,6 +275,11 @@ func networkPost(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("No name provided"))
 	}
 
+	err = networkValidateName(req.Name)
+	if err != nil {
+		return BadRequest(err)
+	}
+
 	// Check that the name isn't already in use
 	networks, err := networkGetInterfaces(d)
 	if err != nil {

From a779ea2113aef697173cefe8621b4ad1dd410efb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 7 Sep 2016 18:28:02 -0400
Subject: [PATCH 12/46] network: Implement PUT
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db_networks.go | 26 ++++++++++++++++++++++++++
 lxd/networks.go    | 36 +++++++++++++++++++++++++++++++++++-
 2 files changed, 61 insertions(+), 1 deletion(-)

diff --git a/lxd/db_networks.go b/lxd/db_networks.go
index ae0b2be..05a2f5b 100644
--- a/lxd/db_networks.go
+++ b/lxd/db_networks.go
@@ -126,6 +126,32 @@ func dbNetworkCreate(db *sql.DB, name string, config map[string]string) (int64,
 	return id, nil
 }
 
+func dbNetworkUpdate(db *sql.DB, name string, config map[string]string) error {
+	id, _, err := dbNetworkGet(db, name)
+	if err != nil {
+		return err
+	}
+
+	tx, err := dbBegin(db)
+	if err != nil {
+		return err
+	}
+
+	err = dbNetworkConfigClear(tx, id)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+
+	err = dbNetworkConfigAdd(tx, id, config)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+
+	return txCommit(tx)
+}
+
 func dbNetworkConfigAdd(tx *sql.Tx, id int64, config map[string]string) error {
 	str := fmt.Sprintf("INSERT INTO networks_config (network_id, key, value) VALUES(?, ?, ?)")
 	stmt, err := tx.Prepare(str)
diff --git a/lxd/networks.go b/lxd/networks.go
index 7cf8d32..3f310cc 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -299,4 +299,38 @@ func networkPost(d *Daemon, r *http.Request) Response {
 	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/networks/%s", shared.APIVersion, req.Name))
 }
 
-var networkCmd = Command{name: "networks/{name}", get: networkGet, delete: networkDelete, post: networkPost}
+func networkPut(d *Daemon, r *http.Request) Response {
+	name := mux.Vars(r)["name"]
+
+	// Get the existing network
+	_, dbInfo, _ := dbNetworkGet(d.db, name)
+	if dbInfo == nil {
+		return NotFound
+	}
+
+	// Validate the ETag
+	etag := []interface{}{dbInfo.Name, dbInfo.Managed, dbInfo.Type, dbInfo.Config}
+
+	err := etagCheck(r, etag)
+	if err != nil {
+		return PreconditionFailed(err)
+	}
+
+	req := shared.NetworkConfig{}
+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+		return BadRequest(err)
+	}
+
+	return doNetworkUpdate(d, name, dbInfo.Config, req.Config)
+}
+
+func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, newConfig map[string]string) Response {
+	err := dbNetworkUpdate(d.db, name, newConfig)
+	if err != nil {
+		return InternalError(err)
+	}
+
+	return EmptySyncResponse
+}
+
+var networkCmd = Command{name: "networks/{name}", get: networkGet, delete: networkDelete, post: networkPost, put: networkPut}

From c1d53bf9ce40cf4eaa2b3b9e4c3449c29c31d8d7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 7 Sep 2016 18:36:17 -0400
Subject: [PATCH 13/46] network: Implement PATCH
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go | 39 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 38 insertions(+), 1 deletion(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index 3f310cc..faa87af 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -324,6 +324,43 @@ func networkPut(d *Daemon, r *http.Request) Response {
 	return doNetworkUpdate(d, name, dbInfo.Config, req.Config)
 }
 
+func networkPatch(d *Daemon, r *http.Request) Response {
+	name := mux.Vars(r)["name"]
+
+	// Get the existing network
+	_, dbInfo, _ := dbNetworkGet(d.db, name)
+	if dbInfo == nil {
+		return NotFound
+	}
+
+	// Validate the ETag
+	etag := []interface{}{dbInfo.Name, dbInfo.Managed, dbInfo.Type, dbInfo.Config}
+
+	err := etagCheck(r, etag)
+	if err != nil {
+		return PreconditionFailed(err)
+	}
+
+	req := shared.NetworkConfig{}
+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+		return BadRequest(err)
+	}
+
+	// Config stacking
+	if req.Config == nil {
+		req.Config = map[string]string{}
+	}
+
+	for k, v := range dbInfo.Config {
+		_, ok := req.Config[k]
+		if !ok {
+			req.Config[k] = v
+		}
+	}
+
+	return doNetworkUpdate(d, name, dbInfo.Config, req.Config)
+}
+
 func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, newConfig map[string]string) Response {
 	err := dbNetworkUpdate(d.db, name, newConfig)
 	if err != nil {
@@ -333,4 +370,4 @@ func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, newCon
 	return EmptySyncResponse
 }
 
-var networkCmd = Command{name: "networks/{name}", get: networkGet, delete: networkDelete, post: networkPost, put: networkPut}
+var networkCmd = Command{name: "networks/{name}", get: networkGet, delete: networkDelete, post: networkPost, put: networkPut, patch: networkPatch}

From f1f264772f0afe168f2e8190852a7dfda29ad7a1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 8 Sep 2016 01:26:15 -0400
Subject: [PATCH 14/46] network: Config validation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go        |  12 ++-
 lxd/networks_config.go | 268 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 279 insertions(+), 1 deletion(-)
 create mode 100644 lxd/networks_config.go

diff --git a/lxd/networks.go b/lxd/networks.go
index faa87af..c2887c7 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -143,6 +143,11 @@ func networksPost(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("The network already exists"))
 	}
 
+	err = networkValidateConfig(req.Config)
+	if err != nil {
+		return BadRequest(err)
+	}
+
 	// Create the database entry
 	_, err = dbNetworkCreate(d.db, req.Name, req.Config)
 	if err != nil {
@@ -362,7 +367,12 @@ func networkPatch(d *Daemon, r *http.Request) Response {
 }
 
 func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, newConfig map[string]string) Response {
-	err := dbNetworkUpdate(d.db, name, newConfig)
+	err := networkValidateConfig(newConfig)
+	if err != nil {
+		return BadRequest(err)
+	}
+
+	err = dbNetworkUpdate(d.db, name, newConfig)
 	if err != nil {
 		return InternalError(err)
 	}
diff --git a/lxd/networks_config.go b/lxd/networks_config.go
new file mode 100644
index 0000000..fbfc748
--- /dev/null
+++ b/lxd/networks_config.go
@@ -0,0 +1,268 @@
+package main
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/lxc/lxd/shared"
+)
+
+func networkValidPort(value string) error {
+	if value == "" {
+		return nil
+	}
+
+	valueInt, err := strconv.ParseInt(value, 10, 64)
+	if err != nil {
+		return fmt.Errorf("Invalid value for an integer: %s", value)
+	}
+
+	if valueInt < 1 || valueInt > 65536 {
+		return fmt.Errorf("Invalid port number: %s", value)
+	}
+
+	return nil
+}
+
+func networkValidAddress(value string) error {
+	err := networkValidAddressV4(value)
+	if err == nil {
+		return nil
+	}
+
+	err = networkValidAddressV6(value)
+	if err == nil {
+		return nil
+	}
+
+	return fmt.Errorf("Not a valid address: %s", value)
+}
+
+func networkValidAddressV6(value string) error {
+	if value == "" {
+		return nil
+	}
+
+	ip, net, err := net.ParseCIDR(value)
+	if err != nil {
+		return err
+	}
+
+	if ip.To4() != nil {
+		return fmt.Errorf("Not an IPv6 address: %s", value)
+	}
+
+	if ip.String() == net.IP.String() {
+		return fmt.Errorf("Not a usable IPv6 address: %s", value)
+	}
+
+	return nil
+}
+
+func networkValidNetworkV6(value string) error {
+	if value == "" {
+		return nil
+	}
+
+	ip, net, err := net.ParseCIDR(value)
+	if err != nil {
+		return err
+	}
+
+	if ip.To4() != nil {
+		return fmt.Errorf("Not an IPv6 network: %s", value)
+	}
+
+	if ip.String() != net.IP.String() {
+		return fmt.Errorf("Not an IPv6 network address: %s", value)
+	}
+
+	return nil
+}
+
+func networkValidAddressV4(value string) error {
+	if value == "" {
+		return nil
+	}
+
+	ip, net, err := net.ParseCIDR(value)
+	if err != nil {
+		return err
+	}
+
+	if ip.To4() == nil {
+		return fmt.Errorf("Not an IPv4 address: %s", value)
+	}
+
+	if ip.String() == net.IP.String() {
+		return fmt.Errorf("Not a usable IPv4 address: %s", value)
+	}
+
+	return nil
+}
+
+func networkValidNetworkV4(value string) error {
+	if value == "" {
+		return nil
+	}
+
+	ip, net, err := net.ParseCIDR(value)
+	if err != nil {
+		return err
+	}
+
+	if ip.To4() == nil {
+		return fmt.Errorf("Not an IPv4 network: %s", value)
+	}
+
+	if ip.String() != net.IP.String() {
+		return fmt.Errorf("Not an IPv4 network address: %s", value)
+	}
+
+	return nil
+}
+
+var networkConfigKeys = map[string]func(value string) error{
+	"bridge.driver": func(value string) error {
+		return shared.IsOneOf(value, []string{"native", "openvswitch"})
+	},
+	"bridge.external_interfaces": func(value string) error {
+		if value == "" {
+			return nil
+		}
+
+		for _, entry := range strings.Split(value, ",") {
+			entry = strings.TrimSpace(entry)
+			if networkValidName(entry) != nil {
+				return fmt.Errorf("Invalid interface name '%s'", entry)
+			}
+		}
+
+		return nil
+	},
+	"bridge.mtu": shared.IsInt64,
+	"bridge.mode": func(value string) error {
+		return shared.IsOneOf(value, []string{"standard", "fan"})
+	},
+
+	"fan.overlay_subnet": networkValidNetworkV4,
+	"fan.underlay_subnet": func(value string) error {
+		if value == "auto" {
+			return nil
+		}
+
+		return networkValidNetworkV4(value)
+	},
+
+	"tunnel.TARGET.protocol": func(value string) error {
+		return shared.IsOneOf(value, []string{"gre", "vxlan"})
+	},
+	"tunnel.TARGET.local":  networkValidAddress,
+	"tunnel.TARGET.remote": networkValidAddress,
+	"tunnel.TARGET.port":   networkValidPort,
+	"tunnel.TARGET.id":     shared.IsInt64,
+
+	"ipv4.address": func(value string) error {
+		if shared.IsOneOf(value, []string{"none", "auto"}) == nil {
+			return nil
+		}
+
+		return networkValidAddressV4(value)
+	},
+	"ipv4.nat":         shared.IsBool,
+	"ipv4.dhcp":        shared.IsBool,
+	"ipv4.dhcp.ranges": shared.IsAny,
+	"ipv4.routing":     shared.IsBool,
+
+	"ipv6.address": func(value string) error {
+		if shared.IsOneOf(value, []string{"none", "auto"}) == nil {
+			return nil
+		}
+
+		return networkValidAddressV6(value)
+	},
+	"ipv6.nat":           shared.IsBool,
+	"ipv6.dhcp":          shared.IsBool,
+	"ipv6.dhcp.stateful": shared.IsBool,
+	"ipv6.dhcp.ranges":   shared.IsAny,
+	"ipv6.routing":       shared.IsBool,
+
+	"dns.domain": shared.IsAny,
+	"dns.mode": func(value string) error {
+		return shared.IsOneOf(value, []string{"dynamic", "managed", "none"})
+	},
+
+	"raw.dnsmasq": shared.IsAny,
+}
+
+func networkValidateConfig(config map[string]string) error {
+	bridgeMode := config["bridge.mode"]
+
+	for k, v := range config {
+		key := k
+
+		// User keys are free for all
+		if strings.HasPrefix(key, "user.") {
+			continue
+		}
+
+		// Tunnel keys have the remote name in their name, so extract the real key
+		if strings.HasPrefix(key, "tunnel.") {
+			fields := strings.Split(key, ".")
+			if len(fields) != 3 {
+				return fmt.Errorf("Invalid network configuration key: %s", k)
+			}
+
+			key = fmt.Sprintf("tunnel.TARGET.%s", fields[2])
+		}
+
+		// Then validate
+		validator, ok := networkConfigKeys[key]
+		if !ok {
+			return fmt.Errorf("Invalid network configuration key: %s", k)
+		}
+
+		err := validator(v)
+		if err != nil {
+			return err
+		}
+
+		// Bridge mode checks
+		if bridgeMode == "fan" && strings.HasPrefix(key, "ipv4.") && v != "" {
+			return fmt.Errorf("IPv4 configuration may not be set when in 'fan' mode")
+		}
+
+		if bridgeMode == "fan" && strings.HasPrefix(key, "ipv6.") && v != "" {
+			return fmt.Errorf("IPv6 configuration may not be set when in 'fan' mode")
+		}
+
+		if bridgeMode != "fan" && strings.HasPrefix(key, "fan.") && v != "" {
+			return fmt.Errorf("FAN configuration may only be set when in 'fan' mode")
+		}
+
+		// MTU checks
+		if key == "bridge.mtu" && v != "" {
+			mtu, err := strconv.ParseInt(v, 10, 64)
+			if err != nil {
+				return fmt.Errorf("Invalid value for an integer: %s", v)
+			}
+
+			ipv6 := config["ipv6.address"]
+			if ipv6 != "" && ipv6 != "none" && mtu < 1280 {
+				return fmt.Errorf("The minimum MTU for an IPv6 network is 1280")
+			}
+
+			ipv4 := config["ipv4.address"]
+			if ipv4 != "" && ipv4 != "none" && mtu < 68 {
+				return fmt.Errorf("The minimum MTU for an IPv4 network is 68")
+			}
+
+			if config["bridge.mode"] == "fan" && mtu > 1450 {
+				return fmt.Errorf("Maximum MTU for a FAN bridge is 1450")
+			}
+		}
+	}
+
+	return nil
+}

From 4c8a0483bed20ce9f76833e62a8456a1af1020b8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 8 Sep 2016 01:55:27 -0400
Subject: [PATCH 15/46] network: Automatic values
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/lxd/networks.go b/lxd/networks.go
index c2887c7..8f48d57 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -148,6 +148,27 @@ func networksPost(d *Daemon, r *http.Request) Response {
 		return BadRequest(err)
 	}
 
+	// Set some default values where needed
+	if req.Config["bridge.mode"] == "fan" {
+		if req.Config["fan.underlay_subnet"] == "" {
+			req.Config["fan.underlay_subnet"] = "auto"
+		}
+	} else {
+		if req.Config["ipv4.address"] == "" {
+			req.Config["ipv4.address"] = "auto"
+			if req.Config["ipv4.nat"] == "" {
+				req.Config["ipv4.nat"] = "true"
+			}
+		}
+
+		if req.Config["ipv6.address"] == "" {
+			req.Config["ipv6.address"] = "auto"
+			if req.Config["ipv6.nat"] == "" {
+				req.Config["ipv6.nat"] = "true"
+			}
+		}
+	}
+
 	// Create the database entry
 	_, err = dbNetworkCreate(d.db, req.Name, req.Config)
 	if err != nil {
@@ -372,6 +393,12 @@ func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, newCon
 		return BadRequest(err)
 	}
 
+	if newConfig["bridge.mode"] == "fan" {
+		if newConfig["fan.underlay_subnet"] == "" {
+			newConfig["fan.underlay_subnet"] = "auto"
+		}
+	}
+
 	err = dbNetworkUpdate(d.db, name, newConfig)
 	if err != nil {
 		return InternalError(err)

From 9ab4262dc156c4e330cacc006997d2b059e86d4e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 19 Sep 2016 01:53:50 -0400
Subject: [PATCH 16/46] network: Detect IPv6 support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index 8f48d57..7d5cfa1 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -3,6 +3,7 @@ package main
 import (
 	"encoding/json"
 	"fmt"
+	"io/ioutil"
 	"net"
 	"net/http"
 	"os"
@@ -162,9 +163,12 @@ func networksPost(d *Daemon, r *http.Request) Response {
 		}
 
 		if req.Config["ipv6.address"] == "" {
-			req.Config["ipv6.address"] = "auto"
-			if req.Config["ipv6.nat"] == "" {
-				req.Config["ipv6.nat"] = "true"
+			content, err := ioutil.ReadFile("/proc/sys/net/ipv6/conf/default/disable_ipv6")
+			if err == nil && string(content) == "0\n" {
+				req.Config["ipv6.address"] = "auto"
+				if req.Config["ipv6.nat"] == "" {
+					req.Config["ipv6.nat"] = "true"
+				}
 			}
 		}
 	}

From 064478cd0609d788de79fe89dc097e5d58397fbe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 13 Sep 2016 17:41:46 -0400
Subject: [PATCH 17/46] network: Consolidate utility functions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go        |  24 +--------
 lxd/networks_config.go | 115 ---------------------------------------
 lxd/networks_utils.go  | 142 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 144 insertions(+), 137 deletions(-)
 create mode 100644 lxd/networks_utils.go

diff --git a/lxd/networks.go b/lxd/networks.go
index 7d5cfa1..16c0a29 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -8,7 +8,6 @@ import (
 	"net/http"
 	"os"
 	"os/exec"
-	"regexp"
 	"strconv"
 
 	"github.com/gorilla/mux"
@@ -59,25 +58,6 @@ func networkIsInUse(c container, name string) bool {
 	return false
 }
 
-func networkValidateName(name string) error {
-	// Validate the length
-	if len(name) < 2 {
-		return fmt.Errorf("Interface name is too short (minimum 2 characters)")
-	}
-
-	if len(name) > 15 {
-		return fmt.Errorf("Interface name is too long (maximum 15 characters)")
-	}
-
-	// Validate the character set
-	match, _ := regexp.MatchString("^[-a-zA-Z0-9]*$", name)
-	if !match {
-		return fmt.Errorf("Interface name contains invalid characters")
-	}
-
-	return nil
-}
-
 // API endpoints
 func networksGet(d *Daemon, r *http.Request) Response {
 	recursionStr := r.FormValue("recursion")
@@ -126,7 +106,7 @@ func networksPost(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("No name provided"))
 	}
 
-	err = networkValidateName(req.Name)
+	err = networkValidName(req.Name)
 	if err != nil {
 		return BadRequest(err)
 	}
@@ -305,7 +285,7 @@ func networkPost(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("No name provided"))
 	}
 
-	err = networkValidateName(req.Name)
+	err = networkValidName(req.Name)
 	if err != nil {
 		return BadRequest(err)
 	}
diff --git a/lxd/networks_config.go b/lxd/networks_config.go
index fbfc748..6dee8a1 100644
--- a/lxd/networks_config.go
+++ b/lxd/networks_config.go
@@ -8,121 +8,6 @@ import (
 	"github.com/lxc/lxd/shared"
 )
 
-func networkValidPort(value string) error {
-	if value == "" {
-		return nil
-	}
-
-	valueInt, err := strconv.ParseInt(value, 10, 64)
-	if err != nil {
-		return fmt.Errorf("Invalid value for an integer: %s", value)
-	}
-
-	if valueInt < 1 || valueInt > 65536 {
-		return fmt.Errorf("Invalid port number: %s", value)
-	}
-
-	return nil
-}
-
-func networkValidAddress(value string) error {
-	err := networkValidAddressV4(value)
-	if err == nil {
-		return nil
-	}
-
-	err = networkValidAddressV6(value)
-	if err == nil {
-		return nil
-	}
-
-	return fmt.Errorf("Not a valid address: %s", value)
-}
-
-func networkValidAddressV6(value string) error {
-	if value == "" {
-		return nil
-	}
-
-	ip, net, err := net.ParseCIDR(value)
-	if err != nil {
-		return err
-	}
-
-	if ip.To4() != nil {
-		return fmt.Errorf("Not an IPv6 address: %s", value)
-	}
-
-	if ip.String() == net.IP.String() {
-		return fmt.Errorf("Not a usable IPv6 address: %s", value)
-	}
-
-	return nil
-}
-
-func networkValidNetworkV6(value string) error {
-	if value == "" {
-		return nil
-	}
-
-	ip, net, err := net.ParseCIDR(value)
-	if err != nil {
-		return err
-	}
-
-	if ip.To4() != nil {
-		return fmt.Errorf("Not an IPv6 network: %s", value)
-	}
-
-	if ip.String() != net.IP.String() {
-		return fmt.Errorf("Not an IPv6 network address: %s", value)
-	}
-
-	return nil
-}
-
-func networkValidAddressV4(value string) error {
-	if value == "" {
-		return nil
-	}
-
-	ip, net, err := net.ParseCIDR(value)
-	if err != nil {
-		return err
-	}
-
-	if ip.To4() == nil {
-		return fmt.Errorf("Not an IPv4 address: %s", value)
-	}
-
-	if ip.String() == net.IP.String() {
-		return fmt.Errorf("Not a usable IPv4 address: %s", value)
-	}
-
-	return nil
-}
-
-func networkValidNetworkV4(value string) error {
-	if value == "" {
-		return nil
-	}
-
-	ip, net, err := net.ParseCIDR(value)
-	if err != nil {
-		return err
-	}
-
-	if ip.To4() == nil {
-		return fmt.Errorf("Not an IPv4 network: %s", value)
-	}
-
-	if ip.String() != net.IP.String() {
-		return fmt.Errorf("Not an IPv4 network address: %s", value)
-	}
-
-	return nil
-}
-
 var networkConfigKeys = map[string]func(value string) error{
 	"bridge.driver": func(value string) error {
 		return shared.IsOneOf(value, []string{"native", "openvswitch"})
diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
new file mode 100644
index 0000000..e5547be
--- /dev/null
+++ b/lxd/networks_utils.go
@@ -0,0 +1,142 @@
+package main
+
+import (
+	"fmt"
+	"net"
+	"regexp"
+	"strconv"
+)
+
+func networkValidName(value string) error {
+	// Validate the length
+	if len(value) < 2 {
+		return fmt.Errorf("Interface name is too short (minimum 2 characters)")
+	}
+
+	if len(value) > 15 {
+		return fmt.Errorf("Interface name is too long (maximum 15 characters)")
+	}
+
+	// Validate the character set
+	match, _ := regexp.MatchString("^[-a-zA-Z0-9]*$", value)
+	if !match {
+		return fmt.Errorf("Interface name contains invalid characters")
+	}
+
+	return nil
+}
+
+func networkValidPort(value string) error {
+	if value == "" {
+		return nil
+	}
+
+	valueInt, err := strconv.ParseInt(value, 10, 64)
+	if err != nil {
+		return fmt.Errorf("Invalid value for an integer: %s", value)
+	}
+
+	if valueInt < 1 || valueInt > 65536 {
+		return fmt.Errorf("Invalid port number: %s", value)
+	}
+
+	return nil
+}
+
+func networkValidAddress(value string) error {
+	err := networkValidAddressV4(value)
+	if err == nil {
+		return nil
+	}
+
+	err = networkValidAddressV6(value)
+	if err == nil {
+		return nil
+	}
+
+	return fmt.Errorf("Not a valid address: %s", value)
+}
+
+func networkValidAddressV6(value string) error {
+	if value == "" {
+		return nil
+	}
+
+	ip, net, err := net.ParseCIDR(value)
+	if err != nil {
+		return err
+	}
+
+	if ip.To4() != nil {
+		return fmt.Errorf("Not an IPv6 address: %s", value)
+	}
+
+	if ip.String() == net.IP.String() {
+		return fmt.Errorf("Not a usable IPv6 address: %s", value)
+	}
+
+	return nil
+}
+
+func networkValidNetworkV6(value string) error {
+	if value == "" {
+		return nil
+	}
+
+	ip, net, err := net.ParseCIDR(value)
+	if err != nil {
+		return err
+	}
+
+	if ip.To4() != nil {
+		return fmt.Errorf("Not an IPv6 network: %s", value)
+	}
+
+	if ip.String() != net.IP.String() {
+		return fmt.Errorf("Not an IPv6 network address: %s", value)
+	}
+
+	return nil
+}
+
+func networkValidAddressV4(value string) error {
+	if value == "" {
+		return nil
+	}
+
+	ip, net, err := net.ParseCIDR(value)
+	if err != nil {
+		return err
+	}
+
+	if ip.To4() == nil {
+		return fmt.Errorf("Not an IPv4 address: %s", value)
+	}
+
+	if ip.String() == net.IP.String() {
+		return fmt.Errorf("Not a usable IPv4 address: %s", value)
+	}
+
+	return nil
+}
+
+func networkValidNetworkV4(value string) error {
+	if value == "" {
+		return nil
+	}
+
+	ip, net, err := net.ParseCIDR(value)
+	if err != nil {
+		return err
+	}
+
+	if ip.To4() == nil {
+		return fmt.Errorf("Not an IPv4 network: %s", value)
+	}
+
+	if ip.String() != net.IP.String() {
+		return fmt.Errorf("Not an IPv4 network address: %s", value)
+	}
+
+	return nil
+}

From cb0d9e388d6a1660314f25f6e56fe4ebf020530d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 13 Sep 2016 17:45:34 -0400
Subject: [PATCH 18/46] network: Add support for auto subnet generation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks_utils.go | 237 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 237 insertions(+)

diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index e5547be..11e9246 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -1,12 +1,249 @@
 package main
 
 import (
+	"bufio"
+	"encoding/binary"
+	"encoding/hex"
 	"fmt"
+	"math"
+	"math/big"
+	"math/rand"
 	"net"
+	"os"
+	"os/exec"
 	"regexp"
 	"strconv"
+	"strings"
+	"sync"
+	"time"
 )
 
+func networkGetIP(subnet *net.IPNet, host int64) net.IP {
+	// Convert IP to a big int
+	bigIP := big.NewInt(0)
+	bigIP.SetBytes(subnet.IP.To16())
+
+	// Deal with negative offsets
+	bigHost := big.NewInt(host)
+	bigCount := big.NewInt(host)
+	if host < 0 {
+		mask, size := subnet.Mask.Size()
+
+		bigHosts := big.NewFloat(0)
+		bigHosts.SetFloat64((math.Pow(2, float64(size-mask))))
+		bigHostsInt, _ := bigHosts.Int(nil)
+
+		bigCount.Set(bigHostsInt)
+		bigCount.Add(bigCount, bigHost)
+	}
+
+	// Get the new IP int
+	bigIP.Add(bigIP, bigCount)
+
+	// Generate an IPv6
+	if subnet.IP.To4() == nil {
+		newIp := make(net.IP, 16)
+		newIp = bigIP.Bytes()
+		return newIp
+	}
+
+	// Generate an IPv4
+	newIp := make(net.IP, 4)
+	binary.BigEndian.PutUint32(newIp, uint32(bigIP.Int64()))
+	return newIp
+}
+
+func networkPingSubnet(subnet *net.IPNet) bool {
+	var fail bool
+	var failLock sync.Mutex
+	var wgChecks sync.WaitGroup
+
+	ping := func(ip net.IP) {
+		defer wgChecks.Done()
+
+		cmd := "ping"
+		if ip.To4() == nil {
+			cmd = "ping6"
+		}
+
+		_, err := exec.Command(cmd, "-n", "-q", ip.String(), "-c", "1", "-W", "1").CombinedOutput()
+		if err != nil {
+			// Remote didn't answer
+			return
+		}
+
+		// Remote answered
+		failLock.Lock()
+		fail = true
+		failLock.Unlock()
+	}
+
+	poke := func(ip net.IP) {
+		defer wgChecks.Done()
+
+		addr := fmt.Sprintf("%s:22", ip.String())
+		if ip.To4() == nil {
+			addr = fmt.Sprintf("[%s]:22", ip.String())
+		}
+
+		_, err := net.DialTimeout("tcp", addr, time.Second)
+		if err == nil {
+			// Remote answered
+			failLock.Lock()
+			fail = true
+			failLock.Unlock()
+			return
+		}
+	}
+
+	// Ping first IP
+	wgChecks.Add(1)
+	go ping(networkGetIP(subnet, 1))
+
+	// Poke port on first IP
+	wgChecks.Add(1)
+	go poke(networkGetIP(subnet, 1))
+
+	// Ping check
+	if subnet.IP.To4() != nil {
+		// Ping last IP
+		wgChecks.Add(1)
+		go ping(networkGetIP(subnet, -2))
+
+		// Poke port on last IP
+		wgChecks.Add(1)
+		go poke(networkGetIP(subnet, -2))
+	}
+
+	wgChecks.Wait()
+
+	return fail
+}
+
+func networkInRoutingTable(subnet *net.IPNet) bool {
+	filename := "route"
+	if subnet.IP.To4() == nil {
+		filename = "ipv6_route"
+	}
+
+	file, err := os.Open(fmt.Sprintf("/proc/net/%s", filename))
+	if err != nil {
+		return false
+	}
+	defer file.Close()
+
+	scanner := bufio.NewReader(file)
+	for {
+		line, _, err := scanner.ReadLine()
+		if err != nil {
+			break
+		}
+
+		fields := strings.Fields(string(line))
+
+		// Get the IP
+		ip := net.IP{}
+		if filename == "ipv6_route" {
+			ip, err = hex.DecodeString(fields[0])
+			if err != nil {
+				continue
+			}
+		} else {
+			bytes, err := hex.DecodeString(fields[1])
+			if err != nil {
+				continue
+			}
+
+			ip = net.IPv4(bytes[3], bytes[2], bytes[1], bytes[0])
+		}
+
+		// Get the mask
+		mask := net.IPMask{}
+		if filename == "ipv6_route" {
+			size, err := strconv.ParseInt(fmt.Sprintf("0x%s", fields[1]), 0, 64)
+			if err != nil {
+				continue
+			}
+
+			mask = net.CIDRMask(int(size), 128)
+		} else {
+			bytes, err := hex.DecodeString(fields[7])
+			if err != nil {
+				continue
+			}
+
+			mask = net.IPv4Mask(bytes[3], bytes[2], bytes[1], bytes[0])
+		}
+
+		// Generate a new network
+		lineNet := net.IPNet{IP: ip, Mask: mask}
+
+		// Ignore default gateway
+		if lineNet.IP.Equal(net.ParseIP("::")) {
+			continue
+		}
+
+		if lineNet.IP.Equal(net.ParseIP("0.0.0.0")) {
+			continue
+		}
+
+		// Check if we have a route to our new subnet
+		if lineNet.Contains(subnet.IP) {
+			return true
+		}
+	}
+
+	return false
+}
+
+func networkRandomSubnetV4() string {
+	var cidr string
+
+	for {
+		cidr = fmt.Sprintf("10.%d.%d.1/24", rand.Intn(255), rand.Intn(255))
+		_, subnet, err := net.ParseCIDR(cidr)
+		if err != nil {
+			continue
+		}
+
+		if networkInRoutingTable(subnet) {
+			continue
+		}
+
+		if networkPingSubnet(subnet) {
+			continue
+		}
+
+		break
+	}
+
+	return cidr
+}
+
+func networkRandomSubnetV6() string {
+	var cidr string
+
+	for {
+		cidr = fmt.Sprintf("fd00:%x:%x:%x::1/64", rand.Intn(65535), rand.Intn(65535), rand.Intn(65535))
+		_, subnet, err := net.ParseCIDR(cidr)
+		if err != nil {
+			continue
+		}
+
+		if networkInRoutingTable(subnet) {
+			continue
+		}
+
+		if networkPingSubnet(subnet) {
+			continue
+		}
+
+		break
+	}
+
+	return cidr
+}
+
 func networkValidName(value string) error {
 	// Validate the length
 	if len(value) < 2 {

From 6f1429d72d09ab76047ce0553115677713df48c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Sep 2016 01:33:30 -0400
Subject: [PATCH 19/46] network: Add auto fan underlay selection
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks_utils.go | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 64 insertions(+)

diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index 11e9246..cbb188e 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -244,6 +244,70 @@ func networkRandomSubnetV6() string {
 	return cidr
 }
 
+func networkDefaultGatewaySubnetV4() (*net.IPNet, error) {
+	file, err := os.Open("/proc/net/route")
+	if err != nil {
+		return nil, err
+	}
+	defer file.Close()
+
+	ifaceName := ""
+
+	scanner := bufio.NewReader(file)
+	for {
+		line, _, err := scanner.ReadLine()
+		if err != nil {
+			break
+		}
+
+		fields := strings.Fields(string(line))
+
+		if fields[1] == "00000000" && fields[7] == "00000000" {
+			ifaceName = fields[0]
+			break
+		}
+	}
+
+	if ifaceName == "" {
+		return nil, fmt.Errorf("No default gateway for IPv4")
+	}
+
+	iface, err := net.InterfaceByName(ifaceName)
+	if err != nil {
+		return nil, err
+	}
+
+	addrs, err := iface.Addrs()
+	if err != nil {
+		return nil, err
+	}
+
+	var subnet *net.IPNet
+
+	for _, addr := range addrs {
+		addrIP, addrNet, err := net.ParseCIDR(addr.String())
+		if err != nil {
+			return nil, err
+		}
+
+		if addrIP.To4() == nil {
+			continue
+		}
+
+		if subnet != nil {
+			return nil, fmt.Errorf("More than one IPv4 subnet on default interface")
+		}
+
+		subnet = addrNet
+	}
+
+	if subnet == nil {
+		return nil, fmt.Errorf("No IPv4 subnet on default interface")
+	}
+
+	return subnet, nil
+}
+
 func networkValidName(value string) error {
 	// Validate the length
 	if len(value) < 2 {

From e68ce6d3425d2099ffc0d882c2119f679da58404 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Sep 2016 17:34:19 -0400
Subject: [PATCH 20/46] network: Fill in auto values
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go        | 13 +++++++++++++
 lxd/networks_config.go | 21 +++++++++++++++++++++
 2 files changed, 34 insertions(+)

diff --git a/lxd/networks.go b/lxd/networks.go
index 16c0a29..cf064a3 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -153,6 +153,12 @@ func networksPost(d *Daemon, r *http.Request) Response {
 		}
 	}
 
+	// Replace "auto" by actual values
+	err = networkFillAuto(req.Config)
+	if err != nil {
+		return InternalError(err)
+	}
+
 	// Create the database entry
 	_, err = dbNetworkCreate(d.db, req.Name, req.Config)
 	if err != nil {
@@ -372,6 +378,7 @@ func networkPatch(d *Daemon, r *http.Request) Response {
 }
 
 func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, newConfig map[string]string) Response {
+	// Validate the configuration
 	err := networkValidateConfig(newConfig)
 	if err != nil {
 		return BadRequest(err)
@@ -383,6 +390,12 @@ func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, newCon
 		}
 	}
 
+	// Replace "auto" by actual values
+	err = networkFillAuto(newConfig)
+	if err != nil {
+		return InternalError(err)
+	}
+
 	err = dbNetworkUpdate(d.db, name, newConfig)
 	if err != nil {
 		return InternalError(err)
diff --git a/lxd/networks_config.go b/lxd/networks_config.go
index 6dee8a1..e8fa12a 100644
--- a/lxd/networks_config.go
+++ b/lxd/networks_config.go
@@ -151,3 +151,24 @@ func networkValidateConfig(config map[string]string) error {
 
 	return nil
 }
+
+func networkFillAuto(config map[string]string) error {
+	if config["ipv4.address"] == "auto" {
+		config["ipv4.address"] = networkRandomSubnetV4()
+	}
+
+	if config["ipv6.address"] == "auto" {
+		config["ipv6.address"] = networkRandomSubnetV6()
+	}
+
+	if config["fan.underlay_subnet"] == "auto" {
+		subnet, err := networkDefaultGatewaySubnetV4()
+		if err != nil {
+			return err
+		}
+
+		config["fan.underlay_subnet"] = subnet.String()
+	}
+
+	return nil
+}

From 3c13224b6f70ed3ced2fccf66e5f5e50351dc57e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Sep 2016 19:08:55 -0400
Subject: [PATCH 21/46] lxc: Implement the network commands
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go      |  70 +++++++++
 lxc/main.go    |   1 +
 lxc/network.go | 475 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 po/lxd.pot     |  97 ++++++++++--
 4 files changed, 632 insertions(+), 11 deletions(-)
 create mode 100644 lxc/network.go

diff --git a/client.go b/client.go
index 0191656..58586a3 100644
--- a/client.go
+++ b/client.go
@@ -2618,3 +2618,73 @@ func (c *Client) ImageFromContainer(cname string, public bool, aliases []string,
 
 	return fingerprint, nil
 }
+
+// Network functions
+func (c *Client) NetworkCreate(name string, config map[string]string) error {
+	if c.Remote.Public {
+		return fmt.Errorf("This function isn't supported by public remotes.")
+	}
+
+	body := shared.Jmap{"name": name, "config": config}
+
+	_, err := c.post("networks", body, Sync)
+	return err
+}
+
+func (c *Client) NetworkGet(name string) (shared.NetworkConfig, error) {
+	if c.Remote.Public {
+		return shared.NetworkConfig{}, fmt.Errorf("This function isn't supported by public remotes.")
+	}
+
+	resp, err := c.get(fmt.Sprintf("networks/%s", name))
+	if err != nil {
+		return shared.NetworkConfig{}, err
+	}
+
+	network := shared.NetworkConfig{}
+	if err := json.Unmarshal(resp.Metadata, &network); err != nil {
+		return shared.NetworkConfig{}, err
+	}
+
+	return network, nil
+}
+
+func (c *Client) NetworkPut(name string, network shared.NetworkConfig) error {
+	if c.Remote.Public {
+		return fmt.Errorf("This function isn't supported by public remotes.")
+	}
+
+	if network.Name != name {
+		return fmt.Errorf("Cannot change network name")
+	}
+
+	_, err := c.put(fmt.Sprintf("networks/%s", name), network, Sync)
+	return err
+}
+
+func (c *Client) NetworkDelete(name string) error {
+	if c.Remote.Public {
+		return fmt.Errorf("This function isn't supported by public remotes.")
+	}
+
+	_, err := c.delete(fmt.Sprintf("networks/%s", name), nil, Sync)
+	return err
+}
+
+func (c *Client) ListNetworks() ([]shared.NetworkConfig, error) {
+	if c.Remote.Public {
+		return nil, fmt.Errorf("This function isn't supported by public remotes.")
+	}
+
+	resp, err := c.get("networks?recursion=1")
+	if err != nil {
+		return nil, err
+	}
+
+	networks := []shared.NetworkConfig{}
+	if err := json.Unmarshal(resp.Metadata, &networks); err != nil {
+		return nil, err
+	}
+
+	return networks, nil
+}
diff --git a/lxc/main.go b/lxc/main.go
index a3dcc70..3dc7378 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -174,6 +174,7 @@ var commands = map[string]command{
 	"manpage": &manpageCmd{},
 	"monitor": &monitorCmd{},
 	"move":    &moveCmd{},
+	"network": &networkCmd{},
 	"pause": &actionCmd{
 		action:         shared.Freeze,
 		name:           "pause",
diff --git a/lxc/network.go b/lxc/network.go
new file mode 100644
index 0000000..61be42e
--- /dev/null
+++ b/lxc/network.go
@@ -0,0 +1,475 @@
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"sort"
+	"strings"
+	"syscall"
+
+	"github.com/olekukonko/tablewriter"
+	"gopkg.in/yaml.v2"
+
+	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/i18n"
+	"github.com/lxc/lxd/shared/termios"
+)
+
+type networkCmd struct {
+}
+
+func (c *networkCmd) showByDefault() bool {
+	return true
+}
+
+func (c *networkCmd) networkEditHelp() string {
+	return i18n.G(
+		`### This is a yaml representation of the network.
+### Any line starting with a '# will be ignored.
+###
+### A network consists of a set of configuration items.
+###
+### An example would look like:
+### name: lxdbr0
+### config:
+###   ipv4.address: 10.62.42.1/24
+###   ipv4.nat: true
+###   ipv6.address: fd00:56ad:9f7a:9800::1/64
+###   ipv6.nat: true
+### managed: true
+### type: bridge
+###
+### Note that only the configuration can be changed.`)
+}
+
+func (c *networkCmd) usage() string {
+	return i18n.G(
+		`Manage networks.
+
+lxc network list                               List available networks.
+lxc network show <network>                     Show details of a network.
+lxc network create <network> [key=value]...    Create a network.
+lxc network get <network> <key>                Get network configuration.
+lxc network set <network> <key> <value>        Set network configuration.
+lxc network unset <network> <key>              Unset network configuration.
+lxc network delete <network>                   Delete a network.
+lxc network edit <network>
+    Edit network, either by launching external editor or reading STDIN.
+    Example: lxc network edit <network> # launch editor
+             cat network.yml | lxc network edit <network> # read from network.yml
+
+lxc network attach <network> <container> [device name]
+lxc network attach-profile <network> <profile> [device name]
+
+lxc network detach <network> <container> [device name]
+lxc network detach-profile <network> <container> [device name]
+`)
+}
+
+func (c *networkCmd) flags() {}
+
+func (c *networkCmd) run(config *lxd.Config, args []string) error {
+	if len(args) < 1 {
+		return errArgs
+	}
+
+	if args[0] == "list" {
+		return c.doNetworkList(config, args)
+	}
+
+	if len(args) < 2 {
+		return errArgs
+	}
+
+	remote, network := config.ParseRemoteAndContainer(args[1])
+	client, err := lxd.NewClient(config, remote)
+	if err != nil {
+		return err
+	}
+
+	switch args[0] {
+	case "attach":
+		return c.doNetworkAttach(client, network, args[2:])
+	case "attach-profile":
+		return c.doNetworkAttachProfile(client, network, args[2:])
+	case "create":
+		return c.doNetworkCreate(client, network, args[2:])
+	case "delete":
+		return c.doNetworkDelete(client, network)
+	case "detach":
+		return c.doNetworkDetach(client, network, args[2:])
+	case "detach-profile":
+		return c.doNetworkDetachProfile(client, network, args[2:])
+	case "edit":
+		return c.doNetworkEdit(client, network)
+	case "get":
+		return c.doNetworkGet(client, network, args[2:])
+	case "set":
+		return c.doNetworkSet(client, network, args[2:])
+	case "unset":
+		return c.doNetworkSet(client, network, args[2:])
+	case "show":
+		return c.doNetworkShow(client, network)
+	default:
+		return errArgs
+	}
+}
+
+func (c *networkCmd) doNetworkAttach(client *lxd.Client, name string, args []string) error {
+	if len(args) < 1 || len(args) > 2 {
+		return errArgs
+	}
+
+	container := args[0]
+	devName := name
+	if len(args) > 1 {
+		devName = args[1]
+	}
+
+	network, err := client.NetworkGet(name)
+	if err != nil {
+		return err
+	}
+
+	nicType := "macvlan"
+	if network.Type == "bridge" {
+		nicType = "bridged"
+	}
+
+	props := []string{fmt.Sprintf("nictype=%s", nicType), fmt.Sprintf("parent=%s", name)}
+	resp, err := client.ContainerDeviceAdd(container, devName, "nic", props)
+	if err != nil {
+		return err
+	}
+
+	return client.WaitForSuccess(resp.Operation)
+}
+
+func (c *networkCmd) doNetworkAttachProfile(client *lxd.Client, name string, args []string) error {
+	if len(args) < 1 || len(args) > 2 {
+		return errArgs
+	}
+
+	profile := args[0]
+	devName := name
+	if len(args) > 1 {
+		devName = args[1]
+	}
+
+	network, err := client.NetworkGet(name)
+	if err != nil {
+		return err
+	}
+
+	nicType := "macvlan"
+	if network.Type == "bridge" {
+		nicType = "bridged"
+	}
+
+	props := []string{fmt.Sprintf("nictype=%s", nicType), fmt.Sprintf("parent=%s", name)}
+	_, err = client.ProfileDeviceAdd(profile, devName, "nic", props)
+	return err
+}
+
+func (c *networkCmd) doNetworkCreate(client *lxd.Client, name string, args []string) error {
+	config := map[string]string{}
+
+	for i := 0; i < len(args); i++ {
+		entry := strings.SplitN(args[i], "=", 2)
+		if len(entry) < 2 {
+			return errArgs
+		}
+
+		config[entry[0]] = entry[1]
+	}
+
+	err := client.NetworkCreate(name, config)
+	if err == nil {
+		fmt.Printf(i18n.G("Network %s created")+"\n", name)
+	}
+
+	return err
+}
+
+func (c *networkCmd) doNetworkDetach(client *lxd.Client, name string, args []string) error {
+	if len(args) < 1 || len(args) > 2 {
+		return errArgs
+	}
+
+	containerName := args[0]
+	devName := ""
+	if len(args) > 1 {
+		devName = args[1]
+	}
+
+	container, err := client.ContainerInfo(containerName)
+	if err != nil {
+		return err
+	}
+
+	if devName == "" {
+		for n, d := range container.Devices {
+			if d["type"] == "nic" && d["parent"] == name {
+				if devName != "" {
+					return fmt.Errorf(i18n.G("More than one device matches, specify the device name."))
+				}
+
+				devName = n
+			}
+		}
+	}
+
+	if devName == "" {
+		return fmt.Errorf(i18n.G("No device found for this network"))
+	}
+
+	device, ok := container.Devices[devName]
+	if !ok {
+		return fmt.Errorf(i18n.G("The specified device doesn't exist"))
+	}
+
+	if device["type"] != "nic" || device["parent"] != name {
+		return fmt.Errorf(i18n.G("The specified device doesn't match the network"))
+	}
+
+	resp, err := client.ContainerDeviceDelete(containerName, devName)
+	if err != nil {
+		return err
+	}
+
+	return client.WaitForSuccess(resp.Operation)
+}
+
+func (c *networkCmd) doNetworkDetachProfile(client *lxd.Client, name string, args []string) error {
+	if len(args) < 1 || len(args) > 2 {
+		return errArgs
+	}
+
+	profileName := args[0]
+	devName := ""
+	if len(args) > 1 {
+		devName = args[1]
+	}
+
+	profile, err := client.ProfileConfig(profileName)
+	if err != nil {
+		return err
+	}
+
+	if devName == "" {
+		for n, d := range profile.Devices {
+			if d["type"] == "nic" && d["parent"] == name {
+				if devName != "" {
+					return fmt.Errorf(i18n.G("More than one device matches, specify the device name."))
+				}
+
+				devName = n
+			}
+		}
+	}
+
+	if devName == "" {
+		return fmt.Errorf(i18n.G("No device found for this network"))
+	}
+
+	device, ok := profile.Devices[devName]
+	if !ok {
+		return fmt.Errorf(i18n.G("The specified device doesn't exist"))
+	}
+
+	if device["type"] != "nic" || device["parent"] != name {
+		return fmt.Errorf(i18n.G("The specified device doesn't match the network"))
+	}
+
+	_, err = client.ProfileDeviceDelete(profileName, devName)
+	return err
+}
+
+func (c *networkCmd) doNetworkDelete(client *lxd.Client, name string) error {
+	err := client.NetworkDelete(name)
+	if err == nil {
+		fmt.Printf(i18n.G("Network %s deleted")+"\n", name)
+	}
+
+	return err
+}
+
+func (c *networkCmd) doNetworkEdit(client *lxd.Client, name string) error {
+	// If stdin isn't a terminal, read text from it
+	if !termios.IsTerminal(int(syscall.Stdin)) {
+		contents, err := ioutil.ReadAll(os.Stdin)
+		if err != nil {
+			return err
+		}
+
+		newdata := shared.NetworkConfig{}
+		err = yaml.Unmarshal(contents, &newdata)
+		if err != nil {
+			return err
+		}
+		return client.NetworkPut(name, newdata)
+	}
+
+	// Extract the current value
+	network, err := client.NetworkGet(name)
+	if err != nil {
+		return err
+	}
+
+	data, err := yaml.Marshal(&network)
+	if err != nil {
+		return err
+	}
+
+	// Spawn the editor
+	content, err := shared.TextEditor("", []byte(c.networkEditHelp()+"\n\n"+string(data)))
+	if err != nil {
+		return err
+	}
+
+	for {
+		// Parse the text received from the editor
+		newdata := shared.NetworkConfig{}
+		err = yaml.Unmarshal(content, &newdata)
+		if err == nil {
+			err = client.NetworkPut(name, newdata)
+		}
+
+		// Respawn the editor
+		if err != nil {
+			fmt.Fprintf(os.Stderr, i18n.G("Config parsing error: %s")+"\n", err)
+			fmt.Println(i18n.G("Press enter to open the editor again"))
+
+			_, err := os.Stdin.Read(make([]byte, 1))
+			if err != nil {
+				return err
+			}
+
+			content, err = shared.TextEditor("", content)
+			if err != nil {
+				return err
+			}
+			continue
+		}
+		break
+	}
+	return nil
+}
+
+func (c *networkCmd) doNetworkGet(client *lxd.Client, name string, args []string) error {
+	// we shifted @args so so it should read "<key>"
+	if len(args) != 1 {
+		return errArgs
+	}
+
+	resp, err := client.NetworkGet(name)
+	if err != nil {
+		return err
+	}
+
+	for k, v := range resp.Config {
+		if k == args[0] {
+			fmt.Printf("%s\n", v)
+		}
+	}
+	return nil
+}
+
+func (c *networkCmd) doNetworkList(config *lxd.Config, args []string) error {
+	var remote string
+	if len(args) > 1 {
+		var name string
+		remote, name = config.ParseRemoteAndContainer(args[1])
+		if name != "" {
+			return fmt.Errorf(i18n.G("Cannot provide container name to list"))
+		}
+	} else {
+		remote = config.DefaultRemote
+	}
+
+	client, err := lxd.NewClient(config, remote)
+	if err != nil {
+		return err
+	}
+
+	networks, err := client.ListNetworks()
+	if err != nil {
+		return err
+	}
+
+	data := [][]string{}
+	for _, network := range networks {
+		if shared.StringInSlice(network.Type, []string{"loopback", "unknown"}) {
+			continue
+		}
+
+		strManaged := i18n.G("NO")
+		if network.Managed {
+			strManaged = i18n.G("YES")
+		}
+
+		strUsedBy := fmt.Sprintf("%d", len(network.UsedBy))
+		data = append(data, []string{network.Name, network.Type, strManaged, strUsedBy})
+	}
+
+	table := tablewriter.NewWriter(os.Stdout)
+	table.SetAutoWrapText(false)
+	table.SetAlignment(tablewriter.ALIGN_LEFT)
+	table.SetRowLine(true)
+	table.SetHeader([]string{
+		i18n.G("NAME"),
+		i18n.G("TYPE"),
+		i18n.G("MANAGED"),
+		i18n.G("USED BY")})
+	sort.Sort(byName(data))
+	table.AppendBulk(data)
+	table.Render()
+
+	return nil
+}
+
+func (c *networkCmd) doNetworkSet(client *lxd.Client, name string, args []string) error {
+	// we shifted @args so so it should read "<key> [<value>]"
+	if len(args) < 1 {
+		return errArgs
+	}
+
+	network, err := client.NetworkGet(name)
+	if err != nil {
+		return err
+	}
+
+	key := args[0]
+	var value string
+	if len(args) < 2 {
+		value = ""
+	} else {
+		value = args[1]
+	}
+
+	if !termios.IsTerminal(int(syscall.Stdin)) && value == "-" {
+		buf, err := ioutil.ReadAll(os.Stdin)
+		if err != nil {
+			return fmt.Errorf("Can't read from stdin: %s", err)
+		}
+		value = string(buf[:])
+	}
+
+	network.Config[key] = value
+
+	return client.NetworkPut(name, network)
+}
+
+func (c *networkCmd) doNetworkShow(client *lxd.Client, name string) error {
+	network, err := client.NetworkGet(name)
+	if err != nil {
+		return err
+	}
+
+	data, err := yaml.Marshal(&network)
+	fmt.Printf("%s", data)
+
+	return nil
+}
diff --git a/po/lxd.pot b/po/lxd.pot
index c2c6bb5..69bcd14 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-09-19 18:00+0200\n"
+        "POT-Creation-Date: 2016-09-19 13:13-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -61,6 +61,25 @@ msgid   "### This is a yaml representation of the image properties.\n"
         "###  description: My custom image"
 msgstr  ""
 
+#: lxc/network.go:28
+msgid   "### This is a yaml representation of the network.\n"
+        "### Any line starting with a '# will be ignored.\n"
+        "###\n"
+        "### A network consists of a set of configuration items.\n"
+        "###\n"
+        "### An example would look like:\n"
+        "### name: lxdbr0\n"
+        "### config:\n"
+        "###   ipv4.address: 10.62.42.1/24\n"
+        "###   ipv4.nat: true\n"
+        "###   ipv6.address: fd00:56ad:9f7a:9800::1/64\n"
+        "###   ipv6.nat: true\n"
+        "### managed: true\n"
+        "### type: bridge\n"
+        "###\n"
+        "### Note that only the configuration can be changed."
+msgstr  ""
+
 #: lxc/profile.go:26
 msgid   "### This is a yaml representation of the profile.\n"
         "### Any line starting with a '# will be ignored.\n"
@@ -167,7 +186,7 @@ msgstr  ""
 msgid   "Can't unset key '%s', it's not currently set."
 msgstr  ""
 
-#: lxc/profile.go:419
+#: lxc/network.go:386 lxc/profile.go:419
 msgid   "Cannot provide container name to list"
 msgstr  ""
 
@@ -195,7 +214,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:734 lxc/profile.go:217
+#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:734 lxc/network.go:342 lxc/profile.go:217
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -551,6 +570,10 @@ msgstr  ""
 msgid   "Log:"
 msgstr  ""
 
+#: lxc/network.go:424
+msgid   "MANAGED"
+msgstr  ""
+
 #: lxc/image.go:165
 msgid   "Make image public"
 msgstr  ""
@@ -656,6 +679,28 @@ msgid   "Manage files on a container.\n"
         "  lxc file pull foo/etc/hosts .\n"
 msgstr  ""
 
+#: lxc/network.go:48
+msgid   "Manage networks.\n"
+        "\n"
+        "lxc network list                               List available networks.\n"
+        "lxc network show <network>                     Show details of a network.\n"
+        "lxc network create <network> [key=value]...    Create a network.\n"
+        "lxc network get <network> <key>                Get network configuration.\n"
+        "lxc network set <network> <key> <value>        Set network configuration.\n"
+        "lxc network unset <network> <key>              Unset network configuration.\n"
+        "lxc network delete <network>                   Delete a network.\n"
+        "lxc network edit <network>\n"
+        "    Edit network, either by launching external editor or reading STDIN.\n"
+        "    Example: lxc network edit <network> # launch editor\n"
+        "             cat network.yml | lxc network edit <network> # read from network.yml\n"
+        "\n"
+        "lxc network attach <network> <container> [device name]\n"
+        "lxc network attach-profile <network> <profile> [device name]\n"
+        "\n"
+        "lxc network detach <network> <container> [device name]\n"
+        "lxc network detach-profile <network> <container> [device name]\n"
+msgstr  ""
+
 #: lxc/remote.go:38
 msgid   "Manage remote LXD servers.\n"
         "\n"
@@ -763,6 +808,10 @@ msgid   "Monitor activity on the LXD server.\n"
         "lxc monitor --type=logging"
 msgstr  ""
 
+#: lxc/network.go:216 lxc/network.go:265
+msgid   "More than one device matches, specify the device name."
+msgstr  ""
+
 #: lxc/file.go:226
 msgid   "More than one file to download, but target is not a directory"
 msgstr  ""
@@ -781,11 +830,11 @@ msgstr  ""
 msgid   "Must supply container name for: "
 msgstr  ""
 
-#: lxc/list.go:428 lxc/remote.go:375
+#: lxc/list.go:428 lxc/network.go:422 lxc/remote.go:375
 msgid   "NAME"
 msgstr  ""
 
-#: lxc/remote.go:349 lxc/remote.go:354
+#: lxc/network.go:408 lxc/remote.go:349 lxc/remote.go:354
 msgid   "NO"
 msgstr  ""
 
@@ -794,6 +843,16 @@ msgstr  ""
 msgid   "Name: %s"
 msgstr  ""
 
+#: lxc/network.go:190
+#, c-format
+msgid   "Network %s created"
+msgstr  ""
+
+#: lxc/network.go:293
+#, c-format
+msgid   "Network %s deleted"
+msgstr  ""
+
 #: lxc/image.go:168 lxc/publish.go:34
 msgid   "New alias to define at target"
 msgstr  ""
@@ -802,6 +861,10 @@ msgstr  ""
 msgid   "No certificate provided to add"
 msgstr  ""
 
+#: lxc/network.go:225 lxc/network.go:274
+msgid   "No device found for this network"
+msgstr  ""
+
 #: lxc/config.go:307
 msgid   "No fingerprint specified."
 msgstr  ""
@@ -878,7 +941,7 @@ msgid   "Presents details on how to use LXD.\n"
         "lxd help [--all]"
 msgstr  ""
 
-#: lxc/profile.go:218
+#: lxc/network.go:343 lxc/profile.go:218
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
@@ -1105,7 +1168,7 @@ msgstr  ""
 msgid   "Swap (peak)"
 msgstr  ""
 
-#: lxc/list.go:433
+#: lxc/list.go:433 lxc/network.go:423
 msgid   "TYPE"
 msgstr  ""
 
@@ -1126,10 +1189,18 @@ msgstr  ""
 msgid   "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr  ""
 
-#: lxc/main.go:180
+#: lxc/main.go:181
 msgid   "The opposite of `lxc pause` is `lxc start`."
 msgstr  ""
 
+#: lxc/network.go:230 lxc/network.go:279
+msgid   "The specified device doesn't exist"
+msgstr  ""
+
+#: lxc/network.go:234 lxc/network.go:283
+msgid   "The specified device doesn't match the network"
+msgstr  ""
+
 #: lxc/publish.go:64
 msgid   "There is no \"image name\".  Did you want an alias?"
 msgstr  ""
@@ -1172,6 +1243,10 @@ msgstr  ""
 msgid   "URL"
 msgstr  ""
 
+#: lxc/network.go:425
+msgid   "USED BY"
+msgstr  ""
+
 #: lxc/remote.go:96
 msgid   "Unable to read remote TLS certificate"
 msgstr  ""
@@ -1206,7 +1281,7 @@ msgstr  ""
 msgid   "Whether to show the expanded configuration"
 msgstr  ""
 
-#: lxc/remote.go:351 lxc/remote.go:356
+#: lxc/network.go:410 lxc/remote.go:351 lxc/remote.go:356
 msgid   "YES"
 msgstr  ""
 
@@ -1280,7 +1355,7 @@ msgstr  ""
 msgid   "ok (y/n)?"
 msgstr  ""
 
-#: lxc/main.go:303 lxc/main.go:307
+#: lxc/main.go:304 lxc/main.go:308
 #, c-format
 msgid   "processing aliases failed %s\n"
 msgstr  ""
@@ -1326,7 +1401,7 @@ msgstr  ""
 msgid   "unreachable return reached"
 msgstr  ""
 
-#: lxc/main.go:235
+#: lxc/main.go:236
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 

From 04b41d8b6d67271502b9123814f7d9584297f36c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Sep 2016 18:36:24 -0400
Subject: [PATCH 22/46] lxd-bridge: Remove from codebase
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This is now replaced by the new network management API.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/production-setup.md             |   7 --
 lxd-bridge/lxd-bridge               | 239 ------------------------------------
 lxd-bridge/lxd-bridge-proxy/main.go |  15 ---
 test/suites/static_analysis.sh      |   4 +-
 4 files changed, 2 insertions(+), 263 deletions(-)
 delete mode 100755 lxd-bridge/lxd-bridge
 delete mode 100644 lxd-bridge/lxd-bridge-proxy/main.go

diff --git a/doc/production-setup.md b/doc/production-setup.md
index eb71a2a..3e7f474 100644
--- a/doc/production-setup.md
+++ b/doc/production-setup.md
@@ -55,13 +55,6 @@ If you have at least 1GbE NIC on your lxd host with a lot of local activity (con
 You need to change `txqueuelen` of your real NIC to 10000 (not sure about the best possible value for you), and change and change lxdbr0 interface `txqueuelen` to 10000.  
 In Debian-based distros you can change `txqueuelen` permanently in `/etc/network/interfaces`  
 You can add for ex.: `up ip link set eth0 txqueuelen 10000` to your interface configuration to set txqueuelen value on boot.  
-For permanent lxdbr0 txqueuelen value change I prefer edit `/usr/lib/lxd/lxd-bridge`. You can add `ifconfig lxdbr0 txqueuelen 10000` in start section, just after iptables rules. For ex.:
-```bash
-iptables "${use_iptables_lock}" -I FORWARD -o "${LXD_BRIDGE}" -j ACCEPT -m comment --comment "managed by lxd-bridge"
-iptables "${use_iptables_lock}" -t mangle -A POSTROUTING -o "${LXD_BRIDGE}" -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill -m comment --comment "managed by lxd-bridge"
-ifconfig lxdbr0 txqueuelen 10000
-```
-If you use lxd master in production or find this inappropriate you can set in `rc.local` or in another way you like.
 You could set it txqueuelen temporary (for test purpose) with `ifconfig interfacename# txqueuelen 10000`
 
 #### /etc/sysctl.conf
diff --git a/lxd-bridge/lxd-bridge b/lxd-bridge/lxd-bridge
deleted file mode 100755
index 14d8f4d..0000000
--- a/lxd-bridge/lxd-bridge
+++ /dev/null
@@ -1,239 +0,0 @@
-#!/bin/sh
-config="/etc/default/lxd-bridge"
-varrun="/run/lxd-bridge/"
-varlib="/var/lib/lxd-bridge/"
-
-# lxdbr0 defaults to only setting up the standard IPv6 link-local network
-# to enable routable IPv4 and/or IPv6, please edit /etc/default/lxd
-
-# The values below are defaults
-USE_LXD_BRIDGE="true"
-LXD_BRIDGE="lxdbr0"
-LXD_CONFILE=""
-LXD_DOMAIN=""
-
-# IPv4
-LXD_IPV4_ADDR=""
-LXD_IPV4_NETMASK=""
-LXD_IPV4_NETWORK=""
-LXD_IPV4_DHCP_RANGE=""
-LXD_IPV4_DHCP_MAX=""
-LXD_IPV4_NAT="false"
-
-# IPv6
-LXD_IPV6_ADDR=""
-LXD_IPV6_MASK=""
-LXD_IPV6_NETWORK=""
-LXD_IPV6_NAT="false"
-LXD_IPV6_PROXY="true"
-
-# shellcheck disable=SC1090
-[ ! -f "${config}" ] || . "${config}"
-
-use_iptables_lock="-w"
-iptables -w -L -n > /dev/null 2>&1 || use_iptables_lock=""
-
-HAS_IPV6=false
-[ -e "/proc/sys/net/ipv6/conf/default/disable_ipv6" ] && \
-    [ "$(cat /proc/sys/net/ipv6/conf/default/disable_ipv6)" = "0" ] && HAS_IPV6=true
-
-_netmask2cidr ()
-{
-    # Assumes there's no "255." after a non-255 byte in the mask
-    x=${1##*255.}
-    set -- "0^^^128^192^224^240^248^252^254^" "$(( (${#1} - ${#x})*2 ))" "${x%%.*}"
-    x=${1%%${3}*}
-    echo $(( ${2} + (${#x}/4) ))
-}
-
-ifdown() {
-    ip addr flush dev "${1}"
-    ip link set dev "${1}" down
-}
-
-ifup() {
-    [ "${HAS_IPV6}" = "true" ] && [ "${LXD_IPV6_PROXY}" = "true" ] && ip addr add fe80::1/64 dev "${1}"
-    if [ -n "${LXD_IPV4_NETMASK}" ] && [ -n "${LXD_IPV4_ADDR}" ]; then
-        MASK=$(_netmask2cidr ${LXD_IPV4_NETMASK})
-        CIDR_ADDR="${LXD_IPV4_ADDR}/${MASK}"
-        ip addr add "${CIDR_ADDR}" dev "${1}"
-    fi
-    ip link set dev "${1}" up
-}
-
-start() {
-    [ "x${USE_LXD_BRIDGE}" = "xtrue" ] || { exit 0; }
-
-    [ ! -f "${varrun}/network_up" ] || { echo "lxd-bridge is already running"; exit 1; }
-
-    if [ -d /sys/class/net/${LXD_BRIDGE} ]; then
-        stop force 2>/dev/null || true
-    fi
-
-    FAILED=1
-
-    cleanup() {
-        set +e
-        if [ "${FAILED}" = "1" ]; then
-            echo "Failed to setup lxd-bridge." >&2
-            stop force
-        fi
-    }
-
-    trap cleanup EXIT HUP INT TERM
-    set -e
-
-    # set up the lxd network
-    [ ! -d "/sys/class/net/${LXD_BRIDGE}" ] && ip link add dev "${LXD_BRIDGE}" type bridge
-
-    if [ "${HAS_IPV6}" = "true" ]; then
-        echo 0 > "/proc/sys/net/ipv6/conf/${LXD_BRIDGE}/autoconf" || true
-        echo 0 > "/proc/sys/net/ipv6/conf/${LXD_BRIDGE}/accept_dad" || true
-    fi
-
-    # if we are run from systemd on a system with selinux enabled,
-    # the mkdir will create /run/lxd as init_var_run_t which dnsmasq
-    # can't write its pid into, so we restorecon it (to var_run_t)
-    if [ ! -d "${varrun}" ]; then
-        mkdir -p "${varrun}"
-        if which restorecon >/dev/null 2>&1; then
-            restorecon "${varrun}"
-        fi
-    fi
-
-    if [ ! -d "${varlib}" ]; then
-        mkdir -p "${varlib}"
-        if which restorecon >/dev/null 2>&1; then
-            restorecon "${varlib}"
-        fi
-    fi
-
-    ifup "${LXD_BRIDGE}" "${LXD_IPV4_ADDR}" "${LXD_IPV4_NETMASK}"
-
-    LXD_IPV4_ARG=""
-    if [ -n "${LXD_IPV4_ADDR}" ] && [ -n "${LXD_IPV4_NETMASK}" ] && [ -n "${LXD_IPV4_NETWORK}" ]; then
-        echo 1 > /proc/sys/net/ipv4/ip_forward
-        if [ "${LXD_IPV4_NAT}" = "true" ]; then
-            iptables "${use_iptables_lock}" -t nat -A POSTROUTING -s "${LXD_IPV4_NETWORK}" ! -d "${LXD_IPV4_NETWORK}" -j MASQUERADE -m comment --comment "managed by lxd-bridge"
-        fi
-        LXD_IPV4_ARG="--listen-address ${LXD_IPV4_ADDR} --dhcp-range ${LXD_IPV4_DHCP_RANGE} --dhcp-lease-max=${LXD_IPV4_DHCP_MAX}"
-    fi
-
-    LXD_IPV6_ARG=""
-    if [ "${HAS_IPV6}" = "true" ] && [ -n "${LXD_IPV6_ADDR}" ] && [ -n "${LXD_IPV6_MASK}" ] && [ -n "${LXD_IPV6_NETWORK}" ]; then
-        # IPv6 sysctls don't respect the "all" path...
-        for interface in /proc/sys/net/ipv6/conf/*; do
-            echo 2 > "${interface}/accept_ra"
-        done
-
-        for interface in /proc/sys/net/ipv6/conf/*; do
-            echo 1 > "${interface}/forwarding"
-        done
-
-        ip -6 addr add dev "${LXD_BRIDGE}" "${LXD_IPV6_ADDR}/${LXD_IPV6_MASK}"
-        if [ "${LXD_IPV6_NAT}" = "true" ]; then
-            ip6tables "${use_iptables_lock}" -t nat -A POSTROUTING -s "${LXD_IPV6_NETWORK}" ! -d "${LXD_IPV6_NETWORK}" -j MASQUERADE -m comment --comment "managed by lxd-bridge"
-        fi
-        LXD_IPV6_ARG="--dhcp-range=${LXD_IPV6_ADDR},ra-stateless,ra-names --listen-address ${LXD_IPV6_ADDR}"
-    fi
-
-    iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p udp --dport 67 -j ACCEPT -m comment --comment "managed by lxd-bridge"
-    iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p tcp --dport 67 -j ACCEPT -m comment --comment "managed by lxd-bridge"
-    iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p udp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
-    iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p tcp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
-    iptables "${use_iptables_lock}" -I FORWARD -i "${LXD_BRIDGE}" -j ACCEPT -m comment --comment "managed by lxd-bridge"
-    iptables "${use_iptables_lock}" -I FORWARD -o "${LXD_BRIDGE}" -j ACCEPT -m comment --comment "managed by lxd-bridge"
-    iptables "${use_iptables_lock}" -t mangle -A POSTROUTING -o "${LXD_BRIDGE}" -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill -m comment --comment "managed by lxd-bridge"
-
-    LXD_DOMAIN_ARG=""
-    if [ -n "${LXD_DOMAIN}" ]; then
-        LXD_DOMAIN_ARG="-s ${LXD_DOMAIN} -S /${LXD_DOMAIN}/"
-    fi
-
-    LXD_CONFILE_ARG=""
-    if [ -n "${LXD_CONFILE}" ]; then
-        LXD_CONFILE_ARG="--conf-file=${LXD_CONFILE}"
-    fi
-
-    # https://lists.linuxcontainers.org/pipermail/lxc-devel/2014-October/010561.html
-    for DNSMASQ_USER in lxd dnsmasq nobody
-    do
-        if getent passwd "${DNSMASQ_USER}" >/dev/null; then
-            break
-        fi
-    done
-
-    if [ -n "${LXD_IPV4_ADDR}" ] || [ -n "${LXD_IPV6_ADDR}" ]; then
-        # shellcheck disable=SC2086
-        dnsmasq ${LXD_CONFILE_ARG} ${LXD_DOMAIN_ARG} -u "${DNSMASQ_USER}" --strict-order --bind-interfaces --pid-file="${varrun}/dnsmasq.pid" --dhcp-no-override --except-interface=lo --interface="${LXD_BRIDGE}" --dhcp-leasefile="${varlib}/dnsmasq.${LXD_BRIDGE}.leases" --dhcp-authoritative ${LXD_IPV4_ARG} ${LXD_IPV6_ARG}
-    fi
-
-    if [ "${HAS_IPV6}" = "true" ] && [ "${LXD_IPV6_PROXY}" = "true" ]; then
-        PATH="${PATH}:$(dirname "${0}")" lxd-bridge-proxy --addr="[fe80::1%${LXD_BRIDGE}]:13128" &
-        PID=$!
-        echo "${PID}" > "${varrun}/proxy.pid"
-    fi
-
-    touch "${varrun}/network_up"
-    FAILED=0
-}
-
-stop() {
-    [ -f "${varrun}/network_up" ] || [ "${1}" = "force" ] || { echo "lxd-bridge isn't running"; exit 1; }
-
-    if [ -d /sys/class/net/${LXD_BRIDGE} ]; then
-        ifdown ${LXD_BRIDGE}
-        iptables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p udp --dport 67 -j ACCEPT -m comment --comment "managed by lxd-bridge"
-        iptables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p tcp --dport 67 -j ACCEPT -m comment --comment "managed by lxd-bridge"
-        iptables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p udp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
-        iptables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p tcp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
-        iptables ${use_iptables_lock} -D FORWARD -i ${LXD_BRIDGE} -j ACCEPT -m comment --comment "managed by lxd-bridge"
-        iptables ${use_iptables_lock} -D FORWARD -o ${LXD_BRIDGE} -j ACCEPT -m comment --comment "managed by lxd-bridge"
-        iptables ${use_iptables_lock} -t mangle -D POSTROUTING -o ${LXD_BRIDGE} -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill -m comment --comment "managed by lxd-bridge"
-
-        if [ -n "${LXD_IPV4_NETWORK}" ] && [ "${LXD_IPV4_NAT}" = "true" ]; then
-            iptables ${use_iptables_lock} -t nat -D POSTROUTING -s ${LXD_IPV4_NETWORK} ! -d ${LXD_IPV4_NETWORK} -j MASQUERADE -m comment --comment "managed by lxd-bridge"
-        fi
-
-        if [ "${HAS_IPV6}" = "true" ] && [ -n "${LXD_IPV6_NETWORK}" ] && [ "${LXD_IPV6_NAT}" = "true" ]; then
-            ip6tables ${use_iptables_lock} -t nat -D POSTROUTING -s ${LXD_IPV6_NETWORK} ! -d ${LXD_IPV6_NETWORK} -j MASQUERADE -m comment --comment "managed by lxd-bridge"
-        fi
-
-        if [ -e "${varrun}/dnsmasq.pid" ]; then
-            pid=$(cat "${varrun}/dnsmasq.pid" 2>/dev/null) && kill -9 "${pid}"
-            rm -f "${varrun}/dnsmasq.pid"
-        fi
-
-        if [ -e "${varrun}/proxy.pid" ]; then
-            pid=$(cat "${varrun}/proxy.pid" 2>/dev/null) && kill -9 "${pid}"
-            rm -f "${varrun}/proxy.pid"
-        fi
-
-        # if ${LXD_BRIDGE} has attached interfaces, don't destroy the bridge
-        ls /sys/class/net/${LXD_BRIDGE}/brif/* > /dev/null 2>&1 || ip link delete "${LXD_BRIDGE}"
-    fi
-
-    rm -f "${varrun}/network_up"
-}
-
-# See how we were called.
-case "${1}" in
-    start)
-        start
-    ;;
-
-    stop)
-        stop
-    ;;
-
-    restart|reload|force-reload)
-        ${0} stop
-        ${0} start
-    ;;
-
-    *)
-        echo "Usage: ${0} {start|stop|restart|reload|force-reload}"
-        exit 2
-esac
-
-exit $?
diff --git a/lxd-bridge/lxd-bridge-proxy/main.go b/lxd-bridge/lxd-bridge-proxy/main.go
deleted file mode 100644
index d680439..0000000
--- a/lxd-bridge/lxd-bridge-proxy/main.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package main
-
-import (
-	"flag"
-	"log"
-	"net/http"
-	"net/http/httputil"
-)
-
-func main() {
-	addr := flag.String("addr", "[fe80::1%lxdbr0]:13128", "proxy listen address")
-	flag.Parse()
-
-	log.Fatal(http.ListenAndServe(*addr, &httputil.ReverseProxy{Director: func(req *http.Request) {}}))
-}
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 174f5fd..e974a31 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -14,7 +14,7 @@ test_static_analysis() {
     pyflakes3 test/deps/import-busybox scripts/lxd-setup-lvm-storage
 
     # Shell static analysis
-    shellcheck lxd-bridge/lxd-bridge test/main.sh test/suites/* test/backends/*
+    shellcheck test/main.sh test/suites/* test/backends/*
 
     # Go static analysis
     ## Functions starting by empty line
@@ -38,7 +38,7 @@ test_static_analysis() {
 
     ## deadcode
     if which deadcode >/dev/null 2>&1; then
-      for path in . lxc/ lxd/ shared/ shared/i18n shared/termios fuidshift/ lxd-bridge/lxd-bridge-proxy/; do
+      for path in . lxc/ lxd/ shared/ shared/i18n shared/termios fuidshift/; do
         OUT=$(deadcode ${path} 2>&1 | grep -v lxd/migrate.pb.go || true)
         if [ -n "${OUT}" ]; then
           echo "${OUT}" >&2

From 6b991a18dfb4242f8485141e05731cbc3a8783c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Sep 2016 18:46:30 -0400
Subject: [PATCH 23/46] profiles: Drop lxdbr0 from default profile
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/configuration.md | 3 +--
 lxd/db_profiles.go   | 9 +--------
 lxd/db_update.go     | 7 +------
 test/main.sh         | 3 +++
 4 files changed, 6 insertions(+), 16 deletions(-)

diff --git a/doc/configuration.md b/doc/configuration.md
index cd5c7e6..7537c89 100644
--- a/doc/configuration.md
+++ b/doc/configuration.md
@@ -285,8 +285,7 @@ In any case, resource-specific configuration always overrides that
 coming from the profiles.
 
 
-If not present, LXD will create a "default" profile which comes with a
-network interface connected to LXD's default bridge (lxdbr0).
+If not present, LXD will create a "default" profile.
 
 The "default" profile is set for any new container created which doesn't
 specify a different profiles list.
diff --git a/lxd/db_profiles.go b/lxd/db_profiles.go
index 6e440ab..4cbfc9d 100644
--- a/lxd/db_profiles.go
+++ b/lxd/db_profiles.go
@@ -104,14 +104,7 @@ func dbProfileCreateDefault(db *sql.DB) error {
 		return nil
 	}
 
-	// TODO: We should scan for bridges and use the best available as default.
-	devices := shared.Devices{
-		"eth0": shared.Device{
-			"name":    "eth0",
-			"type":    "nic",
-			"nictype": "bridged",
-			"parent":  "lxdbr0"}}
-	id, err := dbProfileCreate(db, "default", "Default LXD profile", map[string]string{}, devices)
+	id, err := dbProfileCreate(db, "default", "Default LXD profile", map[string]string{}, shared.Devices{})
 	if err != nil {
 		return err
 	}
diff --git a/lxd/db_update.go b/lxd/db_update.go
index b6c1ca8..35f7032 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -873,12 +873,7 @@ CREATE TABLE IF NOT EXISTS config (
 
 func dbUpdateFromV3(currentVersion int, version int, d *Daemon) error {
 	// Attempt to create a default profile (but don't fail if already there)
-	stmt := `INSERT INTO profiles (name) VALUES ("default");
-INSERT INTO profiles_devices (profile_id, name, type) SELECT id, "eth0", "nic" FROM profiles WHERE profiles.name="default";
-INSERT INTO profiles_devices_config (profile_device_id, key, value) SELECT profiles_devices.id, "nictype", "bridged" FROM profiles_devices LEFT JOIN profiles ON profiles.id=profiles_devices.profile_id WHERE profiles.name == "default";
-INSERT INTO profiles_devices_config (profile_device_id, key, value) SELECT profiles_devices.id, 'name', "eth0" FROM profiles_devices LEFT JOIN profiles ON profiles.id=profiles_devices.profile_id WHERE profiles.name == "default";
-INSERT INTO profiles_devices_config (profile_device_id, key, value) SELECT profiles_devices.id, "parent", "lxdbr0" FROM profiles_devices LEFT JOIN profiles ON profiles.id=profiles_devices.profile_id WHERE profiles.name == "default";`
-	d.db.Exec(stmt)
+	d.db.Exec("INSERT INTO profiles (name) VALUES (\"default\");")
 
 	return nil
 }
diff --git a/test/main.sh b/test/main.sh
index 470d162..0a516fb 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -92,6 +92,9 @@ spawn_lxd() {
     set -x
   fi
 
+  echo "==> Setting up networking"
+  LXD_DIR="${lxddir}" lxc network attach-profile lxdbr0 default eth0
+
   echo "==> Configuring storage backend"
   "$LXD_BACKEND"_configure "${lxddir}"
 }

From a611882d979a934ba944d9182c2d8ef575b4526d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Sep 2016 23:31:30 -0400
Subject: [PATCH 24/46] lxc: Add warning when no network
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/init.go   | 20 ++++++++++++++++++++
 lxc/launch.go |  2 ++
 po/lxd.pot    | 20 ++++++++++++++++----
 3 files changed, 38 insertions(+), 4 deletions(-)

diff --git a/lxc/init.go b/lxc/init.go
index 2c1ce41..4d41783 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -210,6 +210,9 @@ func (c *initCmd) run(config *lxd.Config, args []string) error {
 			fmt.Printf(i18n.G("Container name is: %s")+"\n", fields[len(fields)-1])
 		}
 	}
+
+	c.checkNetwork(d, name)
+
 	return nil
 }
 
@@ -277,3 +280,20 @@ func (c *initCmd) guessImage(config *lxd.Config, d *lxd.Client, remote string, i
 	fmt.Fprintf(os.Stderr, i18n.G("The local image '%s' couldn't be found, trying '%s:' instead.")+"\n", image, image)
 	return image, "default"
 }
+
+func (c *initCmd) checkNetwork(d *lxd.Client, name string) {
+	ct, err := d.ContainerInfo(name)
+	if err != nil {
+		return
+	}
+
+	for _, d := range ct.ExpandedDevices {
+		if d["type"] == "nic" {
+			return
+		}
+	}
+
+	fmt.Fprintf(os.Stderr, "\n"+i18n.G("The container you are starting doesn’t have any network attached to it.")+"\n")
+	fmt.Fprintf(os.Stderr, "  "+i18n.G("To create a new network, use: lxc network create")+"\n")
+	fmt.Fprintf(os.Stderr, "  "+i18n.G("To assign a network to a container, use: lxc network assign")+"\n\n")
+}
diff --git a/lxc/launch.go b/lxc/launch.go
index f5f3d3f..df603b4 100644
--- a/lxc/launch.go
+++ b/lxc/launch.go
@@ -121,6 +121,8 @@ func (c *launchCmd) run(config *lxd.Config, args []string) error {
 		return err
 	}
 
+	c.init.checkNetwork(d, name)
+
 	fmt.Printf(i18n.G("Starting %s")+"\n", name)
 	resp, err = d.Action(name, shared.Start, -1, false, false)
 	if err != nil {
diff --git a/po/lxd.pot b/po/lxd.pot
index 69bcd14..d8ce62e 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -1055,7 +1055,7 @@ msgstr  ""
 msgid   "Resources:"
 msgstr  ""
 
-#: lxc/init.go:247
+#: lxc/init.go:250
 #, c-format
 msgid   "Retrieving image: %s"
 msgstr  ""
@@ -1138,7 +1138,7 @@ msgstr  ""
 msgid   "Source:"
 msgstr  ""
 
-#: lxc/launch.go:124
+#: lxc/launch.go:126
 #, c-format
 msgid   "Starting %s"
 msgstr  ""
@@ -1180,11 +1180,15 @@ msgstr  ""
 msgid   "The container is currently running. Use --force to have it stopped and restarted."
 msgstr  ""
 
+#: lxc/init.go:296
+msgid   "The container you are starting doesn’t have any network attached to it."
+msgstr  ""
+
 #: lxc/config.go:675 lxc/config.go:687 lxc/config.go:720 lxc/config.go:738 lxc/config.go:776 lxc/config.go:794
 msgid   "The device doesn't exist"
 msgstr  ""
 
-#: lxc/init.go:277
+#: lxc/init.go:280
 #, c-format
 msgid   "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr  ""
@@ -1213,6 +1217,14 @@ msgstr  ""
 msgid   "Timestamps:"
 msgstr  ""
 
+#: lxc/init.go:298
+msgid   "To assign a network to a container, use: lxc network assign"
+msgstr  ""
+
+#: lxc/init.go:297
+msgid   "To create a new network, use: lxc network create"
+msgstr  ""
+
 #: lxc/main.go:137
 msgid   "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr  ""
@@ -1222,7 +1234,7 @@ msgstr  ""
 msgid   "Transferring image: %d%%"
 msgstr  ""
 
-#: lxc/action.go:99 lxc/launch.go:132
+#: lxc/action.go:99 lxc/launch.go:134
 #, c-format
 msgid   "Try `lxc info --show-log %s` for more info"
 msgstr  ""

From 757ad57dc3f287a6f4802dd16a8956a371e70427 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 16 Sep 2016 00:01:59 -0400
Subject: [PATCH 25/46] lxc: Add --network option to init/launch
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/init.go   | 23 ++++++++++++++++++++---
 lxc/launch.go | 22 +++++++++++++++++++---
 po/lxd.pot    | 42 +++++++++++++++++++++++-------------------
 3 files changed, 62 insertions(+), 25 deletions(-)

diff --git a/lxc/init.go b/lxc/init.go
index 4d41783..80aafbf 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -63,6 +63,7 @@ type initCmd struct {
 	profArgs profileList
 	confArgs configList
 	ephem    bool
+	network  string
 }
 
 func (c *initCmd) showByDefault() bool {
@@ -73,7 +74,7 @@ func (c *initCmd) usage() string {
 	return i18n.G(
 		`Initialize a container from a particular image.
 
-lxc init [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]
+lxc init [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...] [--network|-n <network>]
 
 Initializes a container using the specified image and name.
 
@@ -137,6 +138,8 @@ func (c *initCmd) flags() {
 	gnuflag.Var(&c.profArgs, "p", i18n.G("Profile to apply to the new container"))
 	gnuflag.BoolVar(&c.ephem, "ephemeral", false, i18n.G("Ephemeral container"))
 	gnuflag.BoolVar(&c.ephem, "e", false, i18n.G("Ephemeral container"))
+	gnuflag.StringVar(&c.network, "network", "", i18n.G("Network name"))
+	gnuflag.StringVar(&c.network, "n", "", i18n.G("Network name"))
 }
 
 func (c *initCmd) run(config *lxd.Config, args []string) error {
@@ -179,10 +182,24 @@ func (c *initCmd) run(config *lxd.Config, args []string) error {
 
 	iremote, image = c.guessImage(config, d, remote, iremote, image)
 
+	devicesMap := map[string]shared.Device{}
+	if c.network != "" {
+		network, err := d.NetworkGet(c.network)
+		if err != nil {
+			return err
+		}
+
+		if network.Type == "bridge" {
+			devicesMap[c.network] = shared.Device{"type": "nic", "nictype": "bridge", "parent": c.network}
+		} else {
+			devicesMap[c.network] = shared.Device{"type": "nic", "nictype": "macvlan", "parent": c.network}
+		}
+	}
+
 	if !initRequestedEmptyProfiles && len(profiles) == 0 {
-		resp, err = d.Init(name, iremote, image, nil, configMap, nil, c.ephem)
+		resp, err = d.Init(name, iremote, image, nil, configMap, devicesMap, c.ephem)
 	} else {
-		resp, err = d.Init(name, iremote, image, &profiles, configMap, nil, c.ephem)
+		resp, err = d.Init(name, iremote, image, &profiles, configMap, devicesMap, c.ephem)
 	}
 	if err != nil {
 		return err
diff --git a/lxc/launch.go b/lxc/launch.go
index df603b4..425011e 100644
--- a/lxc/launch.go
+++ b/lxc/launch.go
@@ -22,7 +22,7 @@ func (c *launchCmd) usage() string {
 	return i18n.G(
 		`Launch a container from a particular image.
 
-lxc launch [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]
+lxc launch [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...] [--network|-n <network>]
 
 Launches a container using the specified image and name.
 
@@ -43,6 +43,8 @@ func (c *launchCmd) flags() {
 	gnuflag.Var(&c.init.profArgs, "p", i18n.G("Profile to apply to the new container"))
 	gnuflag.BoolVar(&c.init.ephem, "ephemeral", false, i18n.G("Ephemeral container"))
 	gnuflag.BoolVar(&c.init.ephem, "e", false, i18n.G("Ephemeral container"))
+	gnuflag.StringVar(&c.init.network, "network", "", i18n.G("Network name"))
+	gnuflag.StringVar(&c.init.network, "n", "", i18n.G("Network name"))
 }
 
 func (c *launchCmd) run(config *lxd.Config, args []string) error {
@@ -77,10 +79,24 @@ func (c *launchCmd) run(config *lxd.Config, args []string) error {
 
 	iremote, image = c.init.guessImage(config, d, remote, iremote, image)
 
+	devicesMap := map[string]shared.Device{}
+	if c.init.network != "" {
+		network, err := d.NetworkGet(c.init.network)
+		if err != nil {
+			return err
+		}
+
+		if network.Type == "bridge" {
+			devicesMap[c.init.network] = shared.Device{"type": "nic", "nictype": "bridge", "parent": c.init.network}
+		} else {
+			devicesMap[c.init.network] = shared.Device{"type": "nic", "nictype": "macvlan", "parent": c.init.network}
+		}
+	}
+
 	if !initRequestedEmptyProfiles && len(profiles) == 0 {
-		resp, err = d.Init(name, iremote, image, nil, configMap, nil, c.init.ephem)
+		resp, err = d.Init(name, iremote, image, nil, configMap, devicesMap, c.init.ephem)
 	} else {
-		resp, err = d.Init(name, iremote, image, &profiles, configMap, nil, c.init.ephem)
+		resp, err = d.Init(name, iremote, image, &profiles, configMap, devicesMap, c.init.ephem)
 	}
 
 	if err != nil {
diff --git a/po/lxd.pot b/po/lxd.pot
index d8ce62e..1cddc57 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -210,7 +210,7 @@ msgstr  ""
 msgid   "Columns"
 msgstr  ""
 
-#: lxc/copy.go:31 lxc/copy.go:32 lxc/init.go:134 lxc/init.go:135 lxc/launch.go:40 lxc/launch.go:41
+#: lxc/copy.go:31 lxc/copy.go:32 lxc/init.go:135 lxc/init.go:136 lxc/launch.go:40 lxc/launch.go:41
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
@@ -227,7 +227,7 @@ msgstr  ""
 msgid   "Container name is mandatory"
 msgstr  ""
 
-#: lxc/copy.go:140 lxc/copy.go:228 lxc/init.go:210
+#: lxc/copy.go:140 lxc/copy.go:228 lxc/init.go:227
 #, c-format
 msgid   "Container name is: %s"
 msgstr  ""
@@ -281,12 +281,12 @@ msgstr  ""
 msgid   "Created: %s"
 msgstr  ""
 
-#: lxc/init.go:177 lxc/launch.go:118
+#: lxc/init.go:180 lxc/launch.go:134
 #, c-format
 msgid   "Creating %s"
 msgstr  ""
 
-#: lxc/init.go:175
+#: lxc/init.go:178
 msgid   "Creating the container"
 msgstr  ""
 
@@ -336,7 +336,7 @@ msgstr  ""
 msgid   "Environment:"
 msgstr  ""
 
-#: lxc/copy.go:35 lxc/copy.go:36 lxc/init.go:138 lxc/init.go:139 lxc/launch.go:44 lxc/launch.go:45
+#: lxc/copy.go:35 lxc/copy.go:36 lxc/init.go:139 lxc/init.go:140 lxc/launch.go:44 lxc/launch.go:45
 msgid   "Ephemeral container"
 msgstr  ""
 
@@ -438,10 +438,10 @@ msgstr  ""
 msgid   "Importing the image: %s"
 msgstr  ""
 
-#: lxc/init.go:73
+#: lxc/init.go:74
 msgid   "Initialize a container from a particular image.\n"
         "\n"
-        "lxc init [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
+        "lxc init [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...] [--network|-n <network>]\n"
         "\n"
         "Initializes a container using the specified image and name.\n"
         "\n"
@@ -494,7 +494,7 @@ msgstr  ""
 #: lxc/launch.go:22
 msgid   "Launch a container from a particular image.\n"
         "\n"
-        "lxc launch [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
+        "lxc launch [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...] [--network|-n <network>]\n"
         "\n"
         "Launches a container using the specified image and name.\n"
         "\n"
@@ -853,6 +853,10 @@ msgstr  ""
 msgid   "Network %s deleted"
 msgstr  ""
 
+#: lxc/init.go:141 lxc/init.go:142 lxc/launch.go:46 lxc/launch.go:47
+msgid   "Network name"
+msgstr  ""
+
 #: lxc/image.go:168 lxc/publish.go:34
 msgid   "New alias to define at target"
 msgstr  ""
@@ -996,7 +1000,7 @@ msgstr  ""
 msgid   "Profile %s removed from %s"
 msgstr  ""
 
-#: lxc/copy.go:33 lxc/copy.go:34 lxc/init.go:136 lxc/init.go:137 lxc/launch.go:42 lxc/launch.go:43
+#: lxc/copy.go:33 lxc/copy.go:34 lxc/init.go:137 lxc/init.go:138 lxc/launch.go:42 lxc/launch.go:43
 msgid   "Profile to apply to the new container"
 msgstr  ""
 
@@ -1055,7 +1059,7 @@ msgstr  ""
 msgid   "Resources:"
 msgstr  ""
 
-#: lxc/init.go:250
+#: lxc/init.go:267
 #, c-format
 msgid   "Retrieving image: %s"
 msgstr  ""
@@ -1138,7 +1142,7 @@ msgstr  ""
 msgid   "Source:"
 msgstr  ""
 
-#: lxc/launch.go:126
+#: lxc/launch.go:142
 #, c-format
 msgid   "Starting %s"
 msgstr  ""
@@ -1180,7 +1184,7 @@ msgstr  ""
 msgid   "The container is currently running. Use --force to have it stopped and restarted."
 msgstr  ""
 
-#: lxc/init.go:296
+#: lxc/init.go:313
 msgid   "The container you are starting doesn’t have any network attached to it."
 msgstr  ""
 
@@ -1188,7 +1192,7 @@ msgstr  ""
 msgid   "The device doesn't exist"
 msgstr  ""
 
-#: lxc/init.go:280
+#: lxc/init.go:297
 #, c-format
 msgid   "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr  ""
@@ -1217,11 +1221,11 @@ msgstr  ""
 msgid   "Timestamps:"
 msgstr  ""
 
-#: lxc/init.go:298
+#: lxc/init.go:315
 msgid   "To assign a network to a container, use: lxc network assign"
 msgstr  ""
 
-#: lxc/init.go:297
+#: lxc/init.go:314
 msgid   "To create a new network, use: lxc network create"
 msgstr  ""
 
@@ -1234,7 +1238,7 @@ msgstr  ""
 msgid   "Transferring image: %d%%"
 msgstr  ""
 
-#: lxc/action.go:99 lxc/launch.go:134
+#: lxc/action.go:99 lxc/launch.go:150
 #, c-format
 msgid   "Try `lxc info --show-log %s` for more info"
 msgstr  ""
@@ -1301,7 +1305,7 @@ msgstr  ""
 msgid   "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr  ""
 
-#: lxc/launch.go:111
+#: lxc/launch.go:127
 msgid   "bad number of things scanned from image, container or snapshot"
 msgstr  ""
 
@@ -1329,7 +1333,7 @@ msgstr  ""
 msgid   "default"
 msgstr  ""
 
-#: lxc/copy.go:131 lxc/copy.go:136 lxc/copy.go:219 lxc/copy.go:224 lxc/init.go:200 lxc/init.go:205 lxc/launch.go:95 lxc/launch.go:100
+#: lxc/copy.go:131 lxc/copy.go:136 lxc/copy.go:219 lxc/copy.go:224 lxc/init.go:217 lxc/init.go:222 lxc/launch.go:111 lxc/launch.go:116
 msgid   "didn't get any affected image, container or snapshot from server"
 msgstr  ""
 
@@ -1351,7 +1355,7 @@ msgstr  ""
 msgid   "error: unknown command: %s"
 msgstr  ""
 
-#: lxc/launch.go:115
+#: lxc/launch.go:131
 msgid   "got bad version"
 msgstr  ""
 

From 5c74d98daa0bf8b3e24180f9f2766e0695fa2c90 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 16 Sep 2016 00:13:27 -0400
Subject: [PATCH 26/46] init: Add network creation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/lxd/main.go b/lxd/main.go
index 6673b99..d762dcd 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -599,6 +599,9 @@ func cmdInit() error {
 	var networkPort int64     // Port
 	var trustPassword string  // Trust password
 	var imagesAutoUpdate bool // controls whether we set images.auto_update_interval to 0
+	var bridgeName string     // Bridge name
+	var bridgeIPv4 string     // IPv4 address
+	var bridgeIPv6 string     // IPv6 address
 
 	// Detect userns
 	defaultPrivileged = -1
@@ -894,6 +897,23 @@ they otherwise would.
 		if !askBool("Would you like stale cached images to be updated automatically? (yes/no) [default=yes]? ", "yes") {
 			imagesAutoUpdate = false
 		}
+
+		if askBool("Would you like to create a new network bridge (yes/no) [default=yes]? ", "yes") {
+			bridgeName = askString("What should the new bridge be called [default=lxdbr0]? ", "lxdbr0", networkValidName)
+			bridgeIPv4 = askString("What IPv4 subnet should be used (CIDR notation, “auto” or “none”) [default=auto]? ", "auto", func(value string) error {
+				if shared.StringInSlice(value, []string{"auto", "none"}) {
+					return nil
+				}
+				return networkValidAddressV4(value)
+			})
+
+			bridgeIPv6 = askString("What IPv4 subnet should be used (CIDR notation, “auto” or “none”) [default=auto]? ", "auto", func(value string) error {
+				if shared.StringInSlice(value, []string{"auto", "none"}) {
+					return nil
+				}
+				return networkValidAddressV6(value)
+			})
+		}
 	}
 
 	if !shared.StringInSlice(storageBackend, []string{"dir", "zfs"}) {
@@ -999,6 +1019,17 @@ they otherwise would.
 		}
 	}
 
+	if bridgeName != "" {
+		bridgeConfig := map[string]string{}
+		bridgeConfig["ipv4.address"] = bridgeIPv4
+		bridgeConfig["ipv6.address"] = bridgeIPv6
+
+		err = c.NetworkCreate(bridgeName, bridgeConfig)
+		if err != nil {
+			return err
+		}
+	}
+
 	fmt.Printf("LXD has been successfully configured.\n")
 	return nil
 }

From 62cb7797b8f698e97cc72e586e0634348df388d9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 16 Sep 2016 19:30:12 -0400
Subject: [PATCH 27/46] network: Implement initial network struct
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go | 257 +++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 235 insertions(+), 22 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index cf064a3..ead05a5 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -166,6 +166,18 @@ func networksPost(d *Daemon, r *http.Request) Response {
 			fmt.Errorf("Error inserting %s into database: %s", req.Name, err))
 	}
 
+	// Start the network
+	n, err := networkLoadByName(d, req.Name)
+	if err != nil {
+		return InternalError(err)
+	}
+
+	err = n.Start()
+	if err != nil {
+		n.Delete()
+		return InternalError(err)
+	}
+
 	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/networks/%s", shared.APIVersion, req.Name))
 }
 
@@ -247,18 +259,13 @@ func networkDelete(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 
 	// Get the existing network
-	_, dbInfo, _ := dbNetworkGet(d.db, name)
-	if dbInfo == nil {
+	n, err := networkLoadByName(d, name)
+	if err != nil {
 		return NotFound
 	}
 
-	// Sanity checks
-	if len(dbInfo.UsedBy) != 0 {
-		return BadRequest(fmt.Errorf("Network is currently in use)"))
-	}
-
-	// Remove the network
-	err := dbNetworkDelete(d.db, name)
+	// Attempt to delete the network
+	err = n.Delete()
 	if err != nil {
 		return SmartError(err)
 	}
@@ -277,16 +284,12 @@ func networkPost(d *Daemon, r *http.Request) Response {
 	}
 
 	// Get the existing network
-	_, dbInfo, _ := dbNetworkGet(d.db, name)
-	if dbInfo == nil {
+	n, err := networkLoadByName(d, name)
+	if err != nil {
 		return NotFound
 	}
 
 	// Sanity checks
-	if len(dbInfo.UsedBy) != 0 {
-		return BadRequest(fmt.Errorf("Network is currently in use)"))
-	}
-
 	if req.Name == "" {
 		return BadRequest(fmt.Errorf("No name provided"))
 	}
@@ -306,8 +309,8 @@ func networkPost(d *Daemon, r *http.Request) Response {
 		return Conflict
 	}
 
-	// Rename the database entry
-	err = dbNetworkRename(d.db, name, req.Name)
+	// Rename it
+	err = n.Rename(req.Name)
 	if err != nil {
 		return SmartError(err)
 	}
@@ -384,24 +387,234 @@ func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, newCon
 		return BadRequest(err)
 	}
 
+	// When switching to a fan bridge, auto-detect the underlay
 	if newConfig["bridge.mode"] == "fan" {
 		if newConfig["fan.underlay_subnet"] == "" {
 			newConfig["fan.underlay_subnet"] = "auto"
 		}
 	}
 
-	// Replace "auto" by actual values
-	err = networkFillAuto(newConfig)
+	// Load the network
+	n, err := networkLoadByName(d, name)
 	if err != nil {
-		return InternalError(err)
+		return NotFound
 	}
 
-	err = dbNetworkUpdate(d.db, name, newConfig)
+	err = n.Update(shared.NetworkConfig{Config: newConfig})
 	if err != nil {
-		return InternalError(err)
+		return SmartError(err)
 	}
 
 	return EmptySyncResponse
 }
 
 var networkCmd = Command{name: "networks/{name}", get: networkGet, delete: networkDelete, post: networkPost, put: networkPut, patch: networkPatch}
+
+// The network structs and functions
+func networkLoadByName(d *Daemon, name string) (*network, error) {
+	id, dbInfo, err := dbNetworkGet(d.db, name)
+	if err != nil {
+		return nil, err
+	}
+
+	n := network{d: d, id: id, name: name, config: dbInfo.Config}
+
+	return &n, nil
+}
+
+type network struct {
+	// Properties
+	d    *Daemon
+	id   int64
+	name string
+
+	// config
+	config map[string]string
+}
+
+func (n *network) Config() map[string]string {
+	return n.config
+}
+
+func (n *network) IsRunning() bool {
+	return shared.PathExists(fmt.Sprintf("/sys/class/net/%s", n.name))
+}
+
+func (n *network) IsUsed() bool {
+	// Look for containers using the interface
+	cts, err := dbContainersList(n.d.db, cTypeRegular)
+	if err != nil {
+		return true
+	}
+
+	for _, ct := range cts {
+		c, err := containerLoadByName(n.d, ct)
+		if err != nil {
+			return true
+		}
+
+		if networkIsInUse(c, n.name) {
+			return true
+		}
+	}
+
+	return false
+}
+
+func (n *network) Delete() error {
+	// Sanity checks
+	if n.IsUsed() {
+		return fmt.Errorf("The network is currently in use")
+	}
+
+	// Bring the network down
+	if n.IsRunning() {
+		err := n.Stop()
+		if err != nil {
+			return err
+		}
+	}
+
+	// Remove the network from the database
+	err := dbNetworkDelete(n.d.db, n.name)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (n *network) Rename(name string) error {
+	// Sanity checks
+	if n.IsUsed() {
+		return fmt.Errorf("The network is currently in use")
+	}
+
+	// Bring the network down
+	if n.IsRunning() {
+		err := n.Stop()
+		if err != nil {
+			return err
+		}
+	}
+
+	// Rename the database entry
+	err := dbNetworkRename(n.d.db, n.name, name)
+	if err != nil {
+		return err
+	}
+
+	// Bring the network up
+	err = n.Start()
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (n *network) Start() error {
+	if n.IsRunning() {
+		return fmt.Errorf("The network is already running")
+	}
+
+	return nil
+}
+
+func (n *network) Stop() error {
+	if !n.IsRunning() {
+		return fmt.Errorf("The network is already stopped")
+	}
+
+	return nil
+}
+
+func (n *network) Update(newNetwork shared.NetworkConfig) error {
+	err := networkFillAuto(newNetwork.Config)
+	if err != nil {
+		return err
+	}
+	newConfig := newNetwork.Config
+
+	// Backup the current state
+	oldConfig := map[string]string{}
+	err = shared.DeepCopy(&n.config, &oldConfig)
+	if err != nil {
+		return err
+	}
+
+	// Define a function which reverts everything.  Defer this function
+	// so that it doesn't need to be explicitly called in every failing
+	// return path.  Track whether or not we want to undo the changes
+	// using a closure.
+	undoChanges := true
+	defer func() {
+		if undoChanges {
+			n.config = oldConfig
+		}
+	}()
+
+	// Diff the configurations
+	changedConfig := []string{}
+	userOnly := true
+	for key, _ := range oldConfig {
+		if oldConfig[key] != newConfig[key] {
+			if !strings.HasPrefix(key, "user.") {
+				userOnly = false
+			}
+
+			if !shared.StringInSlice(key, changedConfig) {
+				changedConfig = append(changedConfig, key)
+			}
+		}
+	}
+
+	for key, _ := range newConfig {
+		if oldConfig[key] != newConfig[key] {
+			if !strings.HasPrefix(key, "user.") {
+				userOnly = false
+			}
+
+			if !shared.StringInSlice(key, changedConfig) {
+				changedConfig = append(changedConfig, key)
+			}
+		}
+	}
+
+	// Skip on no change
+	if len(changedConfig) == 0 {
+		return nil
+	}
+
+	// Update the network
+	if !userOnly {
+		if shared.StringInSlice("bridge.driver", changedConfig) && n.IsRunning() {
+			err = n.Stop()
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	// Apply the new configuration
+	n.config = newConfig
+
+	// Update the database
+	err = dbNetworkUpdate(n.d.db, n.name, n.config)
+	if err != nil {
+		return err
+	}
+
+	// Restart the network
+	if !userOnly {
+		err = n.Start()
+		if err != nil {
+			return err
+		}
+	}
+
+	// Success, update the closure to mark that the changes should be kept.
+	undoChanges = false
+
+	return nil
+}

From 31ad70075a2570756aaca0ee209d1bbd79efdb6f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 17 Sep 2016 02:56:35 -0400
Subject: [PATCH 28/46] network: Bring networks up on startup
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go   |  6 ++++++
 lxd/networks.go | 23 +++++++++++++++++++++++
 2 files changed, 29 insertions(+)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 9c6c4a5..984d2e0 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -766,6 +766,12 @@ func (d *Daemon) Init() error {
 		if err != nil {
 			return err
 		}
+
+		/* Setup the networks */
+		err = networkStartup(d)
+		if err != nil {
+			return err
+		}
 	}
 
 	/* Log expiry */
diff --git a/lxd/networks.go b/lxd/networks.go
index ead05a5..4c12c16 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -422,6 +422,29 @@ func networkLoadByName(d *Daemon, name string) (*network, error) {
 	return &n, nil
 }
 
+func networkStartup(d *Daemon) error {
+	// Get a list of managed networks
+	networks, err := dbNetworks(d.db)
+	if err != nil {
+		return err
+	}
+
+	// Bring them all up
+	for _, name := range networks {
+		n, err := networkLoadByName(d, name)
+		if err != nil {
+			return err
+		}
+
+		err = n.Start()
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 type network struct {
 	// Properties
 	d    *Daemon

From 9d0974d6204363f36261abc948af51d312d4120e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 17 Sep 2016 17:20:58 -0400
Subject: [PATCH 29/46] network: Move the rest of the functions to utils
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go       | 43 -------------------------------------------
 lxd/networks_utils.go | 42 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 42 insertions(+), 43 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index 4c12c16..620ed3b 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -15,49 +15,6 @@ import (
 	"github.com/lxc/lxd/shared"
 )
 
-// Helper functions
-func networkGetInterfaces(d *Daemon) ([]string, error) {
-	networks, err := dbNetworks(d.db)
-	if err != nil {
-		return nil, err
-	}
-
-	ifaces, err := net.Interfaces()
-	if err != nil {
-		return nil, err
-	}
-
-	for _, iface := range ifaces {
-		if !shared.StringInSlice(iface.Name, networks) {
-			networks = append(networks, iface.Name)
-		}
-	}
-
-	return networks, nil
-}
-
-func networkIsInUse(c container, name string) bool {
-	for _, d := range c.ExpandedDevices() {
-		if d["type"] != "nic" {
-			continue
-		}
-
-		if !shared.StringInSlice(d["nictype"], []string{"bridged", "macvlan"}) {
-			continue
-		}
-
-		if d["parent"] == "" {
-			continue
-		}
-
-		if d["parent"] == name {
-			return true
-		}
-	}
-
-	return false
-}
-
 // API endpoints
 func networksGet(d *Daemon, r *http.Request) Response {
 	recursionStr := r.FormValue("recursion")
diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index cbb188e..ba00009 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -18,6 +18,48 @@ import (
 	"time"
 )
 
+func networkGetInterfaces(d *Daemon) ([]string, error) {
+	networks, err := dbNetworks(d.db)
+	if err != nil {
+		return nil, err
+	}
+
+	ifaces, err := net.Interfaces()
+	if err != nil {
+		return nil, err
+	}
+
+	for _, iface := range ifaces {
+		if !shared.StringInSlice(iface.Name, networks) {
+			networks = append(networks, iface.Name)
+		}
+	}
+
+	return networks, nil
+}
+
+func networkIsInUse(c container, name string) bool {
+	for _, d := range c.ExpandedDevices() {
+		if d["type"] != "nic" {
+			continue
+		}
+
+		if !shared.StringInSlice(d["nictype"], []string{"bridged", "macvlan"}) {
+			continue
+		}
+
+		if d["parent"] == "" {
+			continue
+		}
+
+		if d["parent"] == name {
+			return true
+		}
+	}
+
+	return false
+}
+
 func networkGetIP(subnet *net.IPNet, host int64) net.IP {
 	// Convert IP to a big int
 	bigIP := big.NewInt(0)

From 59a892e9001bafe8e78649f63adf947437603888 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 17 Sep 2016 17:28:07 -0400
Subject: [PATCH 30/46] network: Move interface attach to separate function
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go  | 16 ++++------------
 lxd/networks_utils.go | 18 ++++++++++++++++++
 2 files changed, 22 insertions(+), 12 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 8de5b8c..dc1b027 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -4241,18 +4241,10 @@ func (c *containerLXC) createNetworkDevice(name string, m shared.Device) (string
 		}
 
 		if m["nictype"] == "bridged" {
-			if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", m["parent"])) {
-				err = exec.Command("ip", "link", "set", n1, "master", m["parent"]).Run()
-				if err != nil {
-					deviceRemoveInterface(n2)
-					return "", fmt.Errorf("Failed to add interface to bridge: %s", err)
-				}
-			} else {
-				err = exec.Command("ovs-vsctl", "add-port", m["parent"], n1).Run()
-				if err != nil {
-					deviceRemoveInterface(n2)
-					return "", fmt.Errorf("Failed to add interface to bridge: %s", err)
-				}
+			err = networkAttachInterface(m["parent"], n1)
+			if err != nil {
+				deviceRemoveInterface(n2)
+				return "", fmt.Errorf("Failed to add interface to bridge: %s", err)
 			}
 		}
 
diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index ba00009..b54d4a2 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -16,8 +16,26 @@ import (
 	"strings"
 	"sync"
 	"time"
+
+	"github.com/lxc/lxd/shared"
 )
 
+func networkAttachInterface(netName string, devName string) error {
+	if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", netName)) {
+		err := exec.Command("ip", "link", "set", devName, "master", netName).Run()
+		if err != nil {
+			return fmt.Errorf("Failed to add interface to bridge: %s", err)
+		}
+	} else {
+		err := exec.Command("ovs-vsctl", "add-port", netName, devName).Run()
+		if err != nil {
+			return fmt.Errorf("Failed to add interface to bridge: %s", err)
+		}
+	}
+
+	return nil
+}
+
 func networkGetInterfaces(d *Daemon) ([]string, error) {
 	networks, err := dbNetworks(d.db)
 	if err != nil {

From 061c5f33bc8be686da6a69ed76e1f5f0e3a6b115 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 17 Sep 2016 02:23:14 -0400
Subject: [PATCH 31/46] network: Implement bridge create/destroy
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 62 insertions(+), 2 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index 620ed3b..d441fd3 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -9,6 +9,7 @@ import (
 	"os"
 	"os/exec"
 	"strconv"
+	"strings"
 
 	"github.com/gorilla/mux"
 
@@ -494,8 +495,54 @@ func (n *network) Rename(name string) error {
 }
 
 func (n *network) Start() error {
-	if n.IsRunning() {
-		return fmt.Errorf("The network is already running")
+	// Create the bridge interface
+	if !n.IsRunning() {
+		if n.config["bridge.driver"] == "openvswitch" {
+			err := shared.RunCommand("ovs-vsctl", "add-br", n.name)
+			if err != nil {
+				return err
+			}
+		} else {
+			err := shared.RunCommand("ip", "link", "add", n.name, "type", "bridge")
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	// Set the MTU
+	if n.config["bridge.mtu"] != "" {
+		err := shared.RunCommand("ip", "link", "set", n.name, "mtu", n.config["bridge.mtu"])
+		if err != nil {
+			return err
+		}
+	}
+
+	// Bring it up
+	err := shared.RunCommand("ip", "link", "set", n.name, "up")
+	if err != nil {
+		return err
+	}
+
+	// Add any listed existing external interface
+	if n.config["bridge.external_interfaces"] != "" {
+		for _, entry := range strings.Split(n.config["bridge.external_interfaces"], ",") {
+			entry = strings.TrimSpace(entry)
+			iface, err := net.InterfaceByName(entry)
+			if err != nil {
+				continue
+			}
+
+			addrs, err := iface.Addrs()
+			if err == nil && len(addrs) != 0 {
+				return fmt.Errorf("Only unconfigured network interfaces can be bridged")
+			}
+
+			err = networkAttachInterface(n.name, entry)
+			if err != nil {
+				return err
+			}
+		}
 	}
 
 	return nil
@@ -506,6 +553,19 @@ func (n *network) Stop() error {
 		return fmt.Errorf("The network is already stopped")
 	}
 
+	// Destroy the bridge interface
+	if n.config["bridge.driver"] == "openvswitch" {
+		err := shared.RunCommand("ovs-vsctl", "del-br", n.name)
+		if err != nil {
+			return err
+		}
+	} else {
+		err := shared.RunCommand("ip", "link", "del", n.name)
+		if err != nil {
+			return err
+		}
+	}
+
 	return nil
 }
 

From b5626d7091bc3e8b1fd9e62a708f57dc67dc4e93 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 17 Sep 2016 18:03:45 -0400
Subject: [PATCH 32/46] network: Add interface auto-add
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db_networks.go    | 42 ++++++++++++++++++++++++++++++++++++++++++
 lxd/devices.go        |  1 +
 lxd/networks_utils.go | 10 ++++++++++
 3 files changed, 53 insertions(+)

diff --git a/lxd/db_networks.go b/lxd/db_networks.go
index 05a2f5b..6ee23a3 100644
--- a/lxd/db_networks.go
+++ b/lxd/db_networks.go
@@ -3,6 +3,7 @@ package main
 import (
 	"database/sql"
 	"fmt"
+	"strings"
 
 	_ "github.com/mattn/go-sqlite3"
 
@@ -51,6 +52,47 @@ func dbNetworkGet(db *sql.DB, network string) (int64, *shared.NetworkConfig, err
 	}, nil
 }
 
+func dbNetworkGetInterface(db *sql.DB, devName string) (int64, *shared.NetworkConfig, error) {
+	id := int64(-1)
+	name := ""
+	value := ""
+
+	q := "SELECT networks.id, networks.name, networks_config.value FROM networks LEFT JOIN networks_config ON networks.id=networks_config.network_id WHERE networks_config.key=\"bridge.external_interfaces\""
+	arg1 := []interface{}{}
+	arg2 := []interface{}{id, name, value}
+	result, err := dbQueryScan(db, q, arg1, arg2)
+	if err != nil {
+		return -1, nil, err
+	}
+
+	for _, r := range result {
+		for _, entry := range strings.Split(r[2].(string), ",") {
+			entry = strings.TrimSpace(entry)
+
+			if entry == devName {
+				id = r[0].(int64)
+				name = r[1].(string)
+			}
+		}
+	}
+
+	if id == -1 {
+		return -1, nil, fmt.Errorf("No network found for interface: %s", devName)
+	}
+
+	config, err := dbNetworkConfigGet(db, id)
+	if err != nil {
+		return -1, nil, err
+	}
+
+	return id, &shared.NetworkConfig{
+		Name:    name,
+		Managed: true,
+		Type:    "bridge",
+		Config:  config,
+	}, nil
+}
+
 func dbNetworkConfigGet(db *sql.DB, id int64) (map[string]string, error) {
 	var key, value string
 	query := `
diff --git a/lxd/devices.go b/lxd/devices.go
index 652c6b3..05594be 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -544,6 +544,7 @@ func deviceEventListener(d *Daemon) {
 
 			shared.LogDebugf("Scheduler: network: %s has been added: updating network priorities", e[0])
 			deviceNetworkPriority(d, e[0])
+			networkAutoAttach(d, e[0])
 		case e := <-chUSB:
 			deviceUSBEvent(d, e)
 		case e := <-deviceSchedRebalance:
diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index b54d4a2..6e4676e 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -20,6 +20,16 @@ import (
 	"github.com/lxc/lxd/shared"
 )
 
+func networkAutoAttach(d *Daemon, devName string) error {
+	_, dbInfo, err := dbNetworkGetInterface(d.db, devName)
+	if err != nil {
+		// No match found, move on
+		return nil
+	}
+
+	return networkAttachInterface(dbInfo.Name, devName)
+}
+
 func networkAttachInterface(netName string, devName string) error {
 	if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", netName)) {
 		err := exec.Command("ip", "link", "set", devName, "master", netName).Run()

From 9a7f746c9af989883cb272243f9d3d1435d0aac8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 17 Sep 2016 20:38:25 -0400
Subject: [PATCH 33/46] network: Add IPv4/IPv6 configuration
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go       | 35 +++++++++++++++++++++++++++++++++++
 lxd/networks_utils.go | 15 +++++++++++++++
 2 files changed, 50 insertions(+)

diff --git a/lxd/networks.go b/lxd/networks.go
index d441fd3..27cb235 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -516,6 +516,11 @@ func (n *network) Start() error {
 		if err != nil {
 			return err
 		}
+	} else {
+		err := shared.RunCommand("ip", "link", "set", n.name, "mtu", "1500")
+		if err != nil {
+			return err
+		}
 	}
 
 	// Bring it up
@@ -545,6 +550,36 @@ func (n *network) Start() error {
 		}
 	}
 
+	// Flush all IPv4 addresses
+	err = shared.RunCommand("ip", "-4", "addr", "flush", "dev", n.name, "scope", "global")
+	if err != nil {
+		return err
+	}
+
+	// Configure IPv4
+	if !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) {
+		// Add the address
+		err = shared.RunCommand("ip", "-4", "addr", "add", "dev", n.name, n.config["ipv4.address"])
+		if err != nil {
+			return err
+		}
+	}
+
+	// Flush all IPv6 addresses
+	err = shared.RunCommand("ip", "-6", "addr", "flush", "dev", n.name, "scope", "global")
+	if err != nil {
+		return err
+	}
+
+	// Configure IPv6
+	if !shared.StringInSlice(n.config["ipv6.address"], []string{"", "none"}) {
+		// Add the address
+		err = shared.RunCommand("ip", "-6", "addr", "add", "dev", n.name, n.config["ipv6.address"])
+		if err != nil {
+			return err
+		}
+	}
+
 	return nil
 }
 
diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index 6e4676e..4573598 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -20,6 +20,21 @@ import (
 	"github.com/lxc/lxd/shared"
 )
 
+func networkAddressOnInterface(nic *net.Interface, address string) bool {
+	addrs, err := nic.Addrs()
+	if err != nil {
+		return false
+	}
+
+	for _, addr := range addrs {
+		if addr.String() == address {
+			return true
+		}
+	}
+
+	return false
+}
+
 func networkAutoAttach(d *Daemon, devName string) error {
 	_, dbInfo, err := dbNetworkGetInterface(d.db, devName)
 	if err != nil {

From 8f0a18d44790fcbf328d6bec1e3812c1502c28ae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 17 Sep 2016 22:52:41 -0400
Subject: [PATCH 34/46] network: Add iptables management
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go          | 169 +++++++++++++++++++++++++++++++++++++++++++++++
 lxd/networks_iptables.go | 110 ++++++++++++++++++++++++++++++
 2 files changed, 279 insertions(+)
 create mode 100644 lxd/networks_iptables.go

diff --git a/lxd/networks.go b/lxd/networks.go
index 27cb235..8e3fae5 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -550,6 +550,22 @@ func (n *network) Start() error {
 		}
 	}
 
+	// Remove any existing IPv4 iptables rules
+	err = networkIptablesClear("ipv4", n.name, "")
+	if err != nil {
+		return err
+	}
+
+	err = networkIptablesClear("ipv4", n.name, "mangle")
+	if err != nil {
+		return err
+	}
+
+	err = networkIptablesClear("ipv4", n.name, "nat")
+	if err != nil {
+		return err
+	}
+
 	// Flush all IPv4 addresses
 	err = shared.RunCommand("ip", "-4", "addr", "flush", "dev", n.name, "scope", "global")
 	if err != nil {
@@ -558,11 +574,80 @@ func (n *network) Start() error {
 
 	// Configure IPv4
 	if !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) {
+		// Parse the subnet
+		_, subnet, err := net.ParseCIDR(n.config["ipv4.address"])
+		if err != nil {
+			return err
+		}
+
+		// Setup basic iptables overrides
+		err = networkIptablesPrepend("ipv4", n.name, "", "INPUT", "-i", n.name, "-p", "udp", "--dport", "67", "-j", "ACCEPT")
+		if err != nil {
+			return err
+		}
+
+		err = networkIptablesPrepend("ipv4", n.name, "", "INPUT", "-i", n.name, "-p", "tcp", "--dport", "67", "-j", "ACCEPT")
+		if err != nil {
+			return err
+		}
+
+		err = networkIptablesPrepend("ipv4", n.name, "", "INPUT", "-i", n.name, "-p", "udp", "--dport", "53", "-j", "ACCEPT")
+		if err != nil {
+			return err
+		}
+
+		err = networkIptablesPrepend("ipv4", n.name, "", "INPUT", "-i", n.name, "-p", "tcp", "--dport", "53", "-j", "ACCEPT")
+		if err != nil {
+			return err
+		}
+
+		// Allow forwarding
+		if n.config["ipv4.routing"] == "" || shared.IsTrue(n.config["ipv4.routing"]) {
+			err = networkIptablesPrepend("ipv4", n.name, "", "FORWARD", "-i", n.name, "-j", "ACCEPT")
+			if err != nil {
+				return err
+			}
+
+			err = networkIptablesPrepend("ipv4", n.name, "", "FORWARD", "-o", n.name, "-j", "ACCEPT")
+			if err != nil {
+				return err
+			}
+		} else {
+			err = networkIptablesPrepend("ipv4", n.name, "", "FORWARD", "-i", n.name, "-j", "REJECT")
+			if err != nil {
+				return err
+			}
+
+			err = networkIptablesPrepend("ipv4", n.name, "", "FORWARD", "-o", n.name, "-j", "REJECT")
+			if err != nil {
+				return err
+			}
+		}
+
 		// Add the address
 		err = shared.RunCommand("ip", "-4", "addr", "add", "dev", n.name, n.config["ipv4.address"])
 		if err != nil {
 			return err
 		}
+
+		// Configure NAT
+		if shared.IsTrue(n.config["ipv4.nat"]) {
+			err = networkIptablesPrepend("ipv4", n.name, "nat", "POSTROUTING", "-s", subnet.String(), "!", "-d", subnet.String(), "-j", "MASQUERADE")
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	// Remove any existing IPv6 iptables rules
+	err = networkIptablesClear("ipv6", n.name, "")
+	if err != nil {
+		return err
+	}
+
+	err = networkIptablesClear("ipv6", n.name, "nat")
+	if err != nil {
+		return err
 	}
 
 	// Flush all IPv6 addresses
@@ -573,11 +658,69 @@ func (n *network) Start() error {
 
 	// Configure IPv6
 	if !shared.StringInSlice(n.config["ipv6.address"], []string{"", "none"}) {
+		// Parse the subnet
+		_, subnet, err := net.ParseCIDR(n.config["ipv6.address"])
+		if err != nil {
+			return err
+		}
+
+		// Setup basic iptables overrides
+		err = networkIptablesPrepend("ipv6", n.name, "", "INPUT", "-i", n.name, "-p", "udp", "--dport", "546", "-j", "ACCEPT")
+		if err != nil {
+			return err
+		}
+
+		err = networkIptablesPrepend("ipv6", n.name, "", "INPUT", "-i", n.name, "-p", "tcp", "--dport", "546", "-j", "ACCEPT")
+		if err != nil {
+			return err
+		}
+
+		err = networkIptablesPrepend("ipv6", n.name, "", "INPUT", "-i", n.name, "-p", "udp", "--dport", "53", "-j", "ACCEPT")
+		if err != nil {
+			return err
+		}
+
+		err = networkIptablesPrepend("ipv6", n.name, "", "INPUT", "-i", n.name, "-p", "tcp", "--dport", "53", "-j", "ACCEPT")
+		if err != nil {
+			return err
+		}
+
+		// Allow forwarding
+		if n.config["ipv6.routing"] == "" || shared.IsTrue(n.config["ipv6.routing"]) {
+			err = networkIptablesPrepend("ipv6", n.name, "", "FORWARD", "-i", n.name, "-j", "ACCEPT")
+			if err != nil {
+				return err
+			}
+
+			err = networkIptablesPrepend("ipv6", n.name, "", "FORWARD", "-o", n.name, "-j", "ACCEPT")
+			if err != nil {
+				return err
+			}
+		} else {
+			err = networkIptablesPrepend("ipv6", n.name, "", "FORWARD", "-i", n.name, "-j", "REJECT")
+			if err != nil {
+				return err
+			}
+
+			err = networkIptablesPrepend("ipv6", n.name, "", "FORWARD", "-o", n.name, "-j", "REJECT")
+			if err != nil {
+				return err
+			}
+		}
+
 		// Add the address
 		err = shared.RunCommand("ip", "-6", "addr", "add", "dev", n.name, n.config["ipv6.address"])
 		if err != nil {
 			return err
 		}
+
+		// Configure NAT
+		if shared.IsTrue(n.config["ipv6.nat"]) {
+			err = networkIptablesPrepend("ipv6", n.name, "nat", "POSTROUTING", "-s", subnet.String(), "!", "-d", subnet.String(), "-j", "MASQUERADE")
+			if err != nil {
+				return err
+			}
+		}
 	}
 
 	return nil
@@ -601,6 +744,32 @@ func (n *network) Stop() error {
 		}
 	}
 
+	// Cleanup iptables
+	err := networkIptablesClear("ipv4", n.name, "")
+	if err != nil {
+		return err
+	}
+
+	err = networkIptablesClear("ipv4", n.name, "mangle")
+	if err != nil {
+		return err
+	}
+
+	err = networkIptablesClear("ipv4", n.name, "nat")
+	if err != nil {
+		return err
+	}
+
+	err = networkIptablesClear("ipv6", n.name, "")
+	if err != nil {
+		return err
+	}
+
+	err = networkIptablesClear("ipv6", n.name, "nat")
+	if err != nil {
+		return err
+	}
+
 	return nil
 }
 
diff --git a/lxd/networks_iptables.go b/lxd/networks_iptables.go
new file mode 100644
index 0000000..23ea22e
--- /dev/null
+++ b/lxd/networks_iptables.go
@@ -0,0 +1,110 @@
+package main
+
+import (
+	"fmt"
+	"os/exec"
+	"strings"
+
+	"github.com/lxc/lxd/shared"
+)
+
+func networkIptablesPrepend(protocol string, netName string, table string, chain string, rule ...string) error {
+	cmd := "iptables"
+	if protocol == "ipv6" {
+		cmd = "ip6tables"
+	}
+
+	baseArgs := []string{"-w"}
+	if table != "" {
+		baseArgs = append(baseArgs, []string{"-t", table}...)
+	}
+
+	// Check for an existing entry
+	args := append(baseArgs, []string{"-C", chain}...)
+	args = append(args, rule...)
+	args = append(args, "-m", "comment", "--comment", fmt.Sprintf("generated for LXD network %s", netName))
+	if shared.RunCommand(cmd, args...) == nil {
+		return nil
+	}
+
+	// Add the rule
+	args = append(baseArgs, []string{"-I", chain}...)
+	args = append(args, rule...)
+	args = append(args, "-m", "comment", "--comment", fmt.Sprintf("generated for LXD network %s", netName))
+
+	err := shared.RunCommand(cmd, args...)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func networkIptablesDelete(protocol string, netName string, table string, chain string, rule ...string) error {
+	cmd := "iptables"
+	if protocol == "ipv6" {
+		cmd = "ip6tables"
+	}
+
+	baseArgs := []string{"-w"}
+	if table != "" {
+		baseArgs = append(baseArgs, []string{"-t", table}...)
+	}
+
+	// Check for an existing entry
+	args := append(baseArgs, []string{"-C", chain}...)
+	args = append(args, rule...)
+	args = append(args, "-m", "comment", "--comment", fmt.Sprintf("generated for LXD network %s", netName))
+	if shared.RunCommand(cmd, args...) != nil {
+		return nil
+	}
+
+	// Add the rule
+	args = append(baseArgs, []string{"-D", chain}...)
+	args = append(args, rule...)
+	args = append(args, "-m", "comment", "--comment", fmt.Sprintf("generated for LXD network %s", netName))
+
+	err := shared.RunCommand(cmd, args...)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func networkIptablesClear(protocol string, netName string, table string) error {
+	cmd := "iptables"
+	if protocol == "ipv6" {
+		cmd = "ip6tables"
+	}
+
+	baseArgs := []string{"-w"}
+	if table != "" {
+		baseArgs = append(baseArgs, []string{"-t", table}...)
+	}
+
+	// List the rules
+	args := append(baseArgs, "-S")
+	output, err := exec.Command(cmd, args...).Output()
+	if err != nil {
+		return fmt.Errorf("Failed to list %s rules for %s (table %s)", protocol, netName, table)
+	}
+
+	for _, line := range strings.Split(string(output), "\n") {
+		if !strings.Contains(line, fmt.Sprintf("generated for LXD network %s", netName)) {
+			continue
+		}
+
+		// Remove the entry
+		fields := strings.Fields(line)
+		fields[0] = "-D"
+
+		args = append(baseArgs, fields...)
+		err = shared.RunCommand("sh", "-c", fmt.Sprintf("%s %s", cmd, strings.Join(args, " ")))
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}

From ff636fd9c1dda5993d5c6aac6ba49b1345531fd9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 19 Sep 2016 02:24:25 -0400
Subject: [PATCH 35/46] network: Setup sysctls
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go | 36 ++++++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/lxd/networks.go b/lxd/networks.go
index 8e3fae5..c4b2136 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -510,6 +510,19 @@ func (n *network) Start() error {
 		}
 	}
 
+	// IPv6 bridge configuration
+	if !shared.StringInSlice(n.config["ipv6.address"], []string{"", "none"}) {
+		err := ioutil.WriteFile(fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/autoconf", n.name), []byte("0"), 0)
+		if err != nil {
+			return err
+		}
+
+		err = ioutil.WriteFile(fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/accept_dad", n.name), []byte("0"), 0)
+		if err != nil {
+			return err
+		}
+	}
+
 	// Set the MTU
 	if n.config["bridge.mtu"] != "" {
 		err := shared.RunCommand("ip", "link", "set", n.name, "mtu", n.config["bridge.mtu"])
@@ -603,6 +616,11 @@ func (n *network) Start() error {
 
 		// Allow forwarding
 		if n.config["ipv4.routing"] == "" || shared.IsTrue(n.config["ipv4.routing"]) {
+			err = ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte("1"), 0)
+			if err != nil {
+				return err
+			}
+
 			err = networkIptablesPrepend("ipv4", n.name, "", "FORWARD", "-i", n.name, "-j", "ACCEPT")
 			if err != nil {
 				return err
@@ -687,6 +705,24 @@ func (n *network) Start() error {
 
 		// Allow forwarding
 		if n.config["ipv6.routing"] == "" || shared.IsTrue(n.config["ipv6.routing"]) {
+			// Get a list of proc entries
+			entries, err := ioutil.ReadDir("/proc/sys/net/ipv6/conf/")
+			if err != nil {
+				return err
+			}
+
+			for _, entry := range entries {
+				err := ioutil.WriteFile(fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/accept_ra", entry.Name()), []byte("2"), 0000)
+				if err != nil {
+					return err
+				}
+
+				err = ioutil.WriteFile(fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/forwarding", entry.Name()), []byte("1"), 0000)
+				if err != nil {
+					return err
+				}
+			}
+
 			err = networkIptablesPrepend("ipv6", n.name, "", "FORWARD", "-i", n.name, "-j", "ACCEPT")
 			if err != nil {
 				return err

From 29d87c20721095ee3071351bcf97c8351c21b4fd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 19 Sep 2016 19:26:44 -0400
Subject: [PATCH 36/46] network: Implement FAN support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go        | 140 +++++++++++++++++++++++++++++++++++++++++++++----
 lxd/networks_config.go |  15 +++++-
 lxd/networks_utils.go  |  82 +++++++++++++++++++++++++++--
 3 files changed, 222 insertions(+), 15 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index c4b2136..6145fd2 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"encoding/binary"
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
@@ -529,6 +530,18 @@ func (n *network) Start() error {
 		if err != nil {
 			return err
 		}
+	} else if n.config["bridge.mode"] == "fan" {
+		if n.config["fan.type"] == "ipip" {
+			err := shared.RunCommand("ip", "link", "set", n.name, "mtu", "1480")
+			if err != nil {
+				return err
+			}
+		} else {
+			err := shared.RunCommand("ip", "link", "set", n.name, "mtu", "1450")
+			if err != nil {
+				return err
+			}
+		}
 	} else {
 		err := shared.RunCommand("ip", "link", "set", n.name, "mtu", "1500")
 		if err != nil {
@@ -585,14 +598,8 @@ func (n *network) Start() error {
 		return err
 	}
 
-	// Configure IPv4
-	if !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) {
-		// Parse the subnet
-		_, subnet, err := net.ParseCIDR(n.config["ipv4.address"])
-		if err != nil {
-			return err
-		}
-
+	// Configure IPv4 firewall (includes fan)
+	if n.config["bridge.mode"] == "fan" || !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) {
 		// Setup basic iptables overrides
 		err = networkIptablesPrepend("ipv4", n.name, "", "INPUT", "-i", n.name, "-p", "udp", "--dport", "67", "-j", "ACCEPT")
 		if err != nil {
@@ -615,7 +622,7 @@ func (n *network) Start() error {
 		}
 
 		// Allow forwarding
-		if n.config["ipv4.routing"] == "" || shared.IsTrue(n.config["ipv4.routing"]) {
+		if n.config["bridge.mode"] == "fan" || n.config["ipv4.routing"] == "" || shared.IsTrue(n.config["ipv4.routing"]) {
 			err = ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte("1"), 0)
 			if err != nil {
 				return err
@@ -641,6 +648,15 @@ func (n *network) Start() error {
 				return err
 			}
 		}
+	}
+
+	// Configure IPv4
+	if !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) {
+		// Parse the subnet
+		_, subnet, err := net.ParseCIDR(n.config["ipv4.address"])
+		if err != nil {
+			return err
+		}
 
 		// Add the address
 		err = shared.RunCommand("ip", "-4", "addr", "add", "dev", n.name, n.config["ipv4.address"])
@@ -759,6 +775,103 @@ func (n *network) Start() error {
 		}
 	}
 
+	// Cleanup an existing FAN tunnel
+	tunName := fmt.Sprintf("%s-fan", n.name)
+	if shared.PathExists(fmt.Sprintf("/sys/class/net/%s", tunName)) {
+		err = shared.RunCommand("ip", "link", "del", tunName)
+		if err != nil {
+			return err
+		}
+	}
+
+	// Configure the fan
+	if n.config["bridge.mode"] == "fan" {
+		// Parse the underlay
+		underlay := n.config["fan.underlay_subnet"]
+		_, underlaySubnet, err := net.ParseCIDR(underlay)
+		if err != nil {
+			return nil
+		}
+
+		// Parse the overlay
+		overlay := n.config["fan.overlay_subnet"]
+		if overlay == "" {
+			overlay = "240.0.0.0/8"
+		}
+
+		_, overlaySubnet, err := net.ParseCIDR(overlay)
+		if err != nil {
+			return err
+		}
+
+		// Get the address
+		fanAddress, devName, devAddr, err := networkFanAddress(underlaySubnet, overlaySubnet)
+		if err != nil {
+			return err
+		}
+
+		if n.config["fan.type"] == "ipip" {
+			fanAddress = strings.Replace(fanAddress, "/8", "/24", 1)
+		}
+
+		// Add the address
+		err = shared.RunCommand("ip", "-4", "addr", "add", "dev", n.name, fanAddress)
+		if err != nil {
+			return err
+		}
+
+		// Setup the tunnel
+		if n.config["fan.type"] == "ipip" {
+			err = shared.RunCommand("ip", "-4", "route", "flush", "dev", "tunl0")
+			if err != nil {
+				return err
+			}
+
+			err = shared.RunCommand("ip", "link", "set", "tunl0", "up")
+			if err != nil {
+				return err
+			}
+
+			// Fails if the map is already set
+			shared.RunCommand("ip", "link", "change", "tunl0", "type", "ipip", "fan-map", fmt.Sprintf("%s:%s", overlay, underlay))
+
+			addr := strings.Split(fanAddress, "/")
+
+			err = shared.RunCommand("ip", "route", "add", overlay, "dev", "tunl0", "src", addr[0])
+			if err != nil {
+				return err
+			}
+		} else {
+			vxlanID := fmt.Sprintf("%d", binary.BigEndian.Uint32(overlaySubnet.IP.To4())>>8)
+
+			err = shared.RunCommand("ip", "link", "add", tunName, "type", "vxlan", "id", vxlanID, "dev", devName, "dstport", "0", "local", devAddr, "fan-map", fmt.Sprintf("%s:%s", overlay, underlay))
+			if err != nil {
+				return err
+			}
+
+			err = networkAttachInterface(n.name, tunName)
+			if err != nil {
+				return err
+			}
+
+			err = shared.RunCommand("ip", "link", "set", tunName, "up")
+			if err != nil {
+				return err
+			}
+
+			err = shared.RunCommand("ip", "link", "set", n.name, "up")
+			if err != nil {
+				return err
+			}
+		}
+
+		// Configure NAT
+		err = networkIptablesPrepend("ipv4", n.name, "nat", "POSTROUTING", "-s", underlaySubnet.String(), "!", "-d", underlaySubnet.String(), "-j", "MASQUERADE")
+		if err != nil {
+			return err
+		}
+	}
+
 	return nil
 }
 
@@ -806,6 +919,15 @@ func (n *network) Stop() error {
 		return err
 	}
 
+	// Cleanup an existing fan tunnel
+	tunName := fmt.Sprintf("%s-fan", n.name)
+	if shared.PathExists(fmt.Sprintf("/sys/class/net/%s", tunName)) {
+		err = shared.RunCommand("ip", "link", "del", tunName)
+		if err != nil {
+			return err
+		}
+	}
+
 	return nil
 }
 
diff --git a/lxd/networks_config.go b/lxd/networks_config.go
index e8fa12a..633a374 100644
--- a/lxd/networks_config.go
+++ b/lxd/networks_config.go
@@ -39,6 +39,9 @@ var networkConfigKeys = map[string]func(value string) error{
 
 		return networkValidNetworkV4(value)
 	},
+	"fan.type": func(value string) error {
+		return shared.IsOneOf(value, []string{"vxlan", "ipip"})
+	},
 
 	"tunnel.TARGET.protocol": func(value string) error {
 		return shared.IsOneOf(value, []string{"gre", "vxlan"})
@@ -143,8 +146,16 @@ func networkValidateConfig(config map[string]string) error {
 				return fmt.Errorf("The minimum MTU for an IPv4 network is 68")
 			}
 
-			if config["bridge.mode"] == "fan" && mtu > 1450 {
-				return fmt.Errorf("Maximum MTU for a FAN bridge is 1450")
+			if config["bridge.mode"] == "fan" {
+				if config["fan.type"] == "ipip" {
+					if mtu > 1480 {
+						return fmt.Errorf("Maximum MTU for an IPIP FAN bridge is 1480")
+					}
+				} else {
+					if mtu > 1450 {
+						return fmt.Errorf("Maximum MTU for a VXLAN FAN bridge is 1450")
+					}
+				}
 			}
 		}
 	}
diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index 4573598..16c510b 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -47,14 +47,17 @@ func networkAutoAttach(d *Daemon, devName string) error {
 
 func networkAttachInterface(netName string, devName string) error {
 	if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", netName)) {
-		err := exec.Command("ip", "link", "set", devName, "master", netName).Run()
+		err := shared.RunCommand("ip", "link", "set", devName, "master", netName)
 		if err != nil {
-			return fmt.Errorf("Failed to add interface to bridge: %s", err)
+			return err
 		}
 	} else {
-		err := exec.Command("ovs-vsctl", "add-port", netName, devName).Run()
+		err := shared.RunCommand("ovs-vsctl", "port-to-br", devName)
 		if err != nil {
-			return fmt.Errorf("Failed to add interface to bridge: %s", err)
+			err := shared.RunCommand("ovs-vsctl", "add-port", netName, devName)
+			if err != nil {
+				return err
+			}
 		}
 	}
 
@@ -526,3 +529,74 @@ func networkValidNetworkV4(value string) error {
 
 	return nil
 }
+
+func networkAddressForSubnet(subnet *net.IPNet) (net.IP, string, error) {
+	ifaces, err := net.Interfaces()
+	if err != nil {
+		return net.IP{}, "", err
+	}
+
+	for _, iface := range ifaces {
+		addrs, err := iface.Addrs()
+		if err != nil {
+			continue
+		}
+
+		for _, addr := range addrs {
+			ip, _, err := net.ParseCIDR(addr.String())
+			if err != nil {
+				continue
+			}
+
+			if subnet.Contains(ip) {
+				return ip, iface.Name, nil
+			}
+		}
+	}
+
+	return net.IP{}, "", fmt.Errorf("No address found in subnet")
+}
+
+func networkFanAddress(underlay *net.IPNet, overlay *net.IPNet) (string, string, string, error) {
+	// Sanity checks
+	underlaySize, _ := underlay.Mask.Size()
+	if underlaySize != 16 && underlaySize != 24 {
+		return "", "", "", fmt.Errorf("Only /16 or /24 underlays are supported at this time")
+	}
+
+	overlaySize, _ := overlay.Mask.Size()
+	if overlaySize != 8 && overlaySize != 16 {
+		return "", "", "", fmt.Errorf("Only /8 or /16 overlays are supported at this time")
+	}
+
+	if overlaySize+(32-underlaySize)+8 > 32 {
+		return "", "", "", fmt.Errorf("Underlay or overlay networks too large to accomodate the FAN")
+	}
+
+	// Get the IP
+	ip, dev, err := networkAddressForSubnet(underlay)
+	if err != nil {
+		return "", "", "", err
+	}
+	ipStr := ip.String()
+
+	// Force into IPv4 format
+	ipBytes := ip.To4()
+	if ipBytes == nil {
+		return "", "", "", fmt.Errorf("Invalid IPv4: %s", ip)
+	}
+
+	// Compute the IP
+	ipBytes[0] = overlay.IP[0]
+	if overlaySize == 16 {
+		ipBytes[1] = overlay.IP[1]
+	} else if underlaySize == 24 {
+		ipBytes[1] = 0
+	} else if underlaySize == 16 {
+		ipBytes[1] = ipBytes[2]
+	}
+	ipBytes[2] = ipBytes[3]
+	ipBytes[3] = 1
+
+	return fmt.Sprintf("%s/%d", ipBytes.String(), overlaySize), dev, ipStr, err
+}

From efa775d66bd676ceb67a9a81cdc7548f256aa866 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 19 Sep 2016 20:07:44 -0400
Subject: [PATCH 37/46] network: Don't prevent daemon startup on failure
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index 6145fd2..33048fb 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -13,6 +13,7 @@ import (
 	"strings"
 
 	"github.com/gorilla/mux"
+	log "gopkg.in/inconshreveable/log15.v2"
 
 	"github.com/lxc/lxd/shared"
 )
@@ -397,7 +398,7 @@ func networkStartup(d *Daemon) error {
 
 		err = n.Start()
 		if err != nil {
-			return err
+			shared.LogError("Failed to bring up network", log.Ctx{"err": err, "name": name})
 		}
 	}
 

From 2a72d5ddd841529e2bf1b352ce887b56dec75e86 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 19 Sep 2016 20:09:39 -0400
Subject: [PATCH 38/46] network: Simplify MTU handling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go | 28 ++++++++++------------------
 1 file changed, 10 insertions(+), 18 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index 33048fb..eb86db0 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -526,32 +526,24 @@ func (n *network) Start() error {
 	}
 
 	// Set the MTU
+	mtu := "1500"
 	if n.config["bridge.mtu"] != "" {
-		err := shared.RunCommand("ip", "link", "set", n.name, "mtu", n.config["bridge.mtu"])
-		if err != nil {
-			return err
-		}
+		mtu = n.config["bridge.mtu"]
 	} else if n.config["bridge.mode"] == "fan" {
 		if n.config["fan.type"] == "ipip" {
-			err := shared.RunCommand("ip", "link", "set", n.name, "mtu", "1480")
-			if err != nil {
-				return err
-			}
+			mtu = "1480"
 		} else {
-			err := shared.RunCommand("ip", "link", "set", n.name, "mtu", "1450")
-			if err != nil {
-				return err
-			}
-		}
-	} else {
-		err := shared.RunCommand("ip", "link", "set", n.name, "mtu", "1500")
-		if err != nil {
-			return err
+			mtu = "1450"
 		}
 	}
 
+	err := shared.RunCommand("ip", "link", "set", n.name, "mtu", mtu)
+	if err != nil {
+		return err
+	}
+
 	// Bring it up
-	err := shared.RunCommand("ip", "link", "set", n.name, "up")
+	err = shared.RunCommand("ip", "link", "set", n.name, "up")
 	if err != nil {
 		return err
 	}

From 29a508822040fdaf47b68ca7276b965d8c2b8f54 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 20 Sep 2016 00:39:27 -0400
Subject: [PATCH 39/46] network: Check length of fan interface name
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go        | 4 ++--
 lxd/networks_config.go | 6 +++++-
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index eb86db0..f832c93 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -84,7 +84,7 @@ func networksPost(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("The network already exists"))
 	}
 
-	err = networkValidateConfig(req.Config)
+	err = networkValidateConfig(req.Name, req.Config)
 	if err != nil {
 		return BadRequest(err)
 	}
@@ -342,7 +342,7 @@ func networkPatch(d *Daemon, r *http.Request) Response {
 
 func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, newConfig map[string]string) Response {
 	// Validate the configuration
-	err := networkValidateConfig(newConfig)
+	err := networkValidateConfig(name, newConfig)
 	if err != nil {
 		return BadRequest(err)
 	}
diff --git a/lxd/networks_config.go b/lxd/networks_config.go
index 633a374..b97e4d9 100644
--- a/lxd/networks_config.go
+++ b/lxd/networks_config.go
@@ -84,9 +84,13 @@ var networkConfigKeys = map[string]func(value string) error{
 	"raw.dnsmasq": shared.IsAny,
 }
 
-func networkValidateConfig(config map[string]string) error {
+func networkValidateConfig(name string, config map[string]string) error {
 	bridgeMode := config["bridge.mode"]
 
+	if bridgeMode == "fan" && len(name) > 11 {
+		return fmt.Errorf("Network name too long to use with the FAN (must be 11 characters or less)")
+	}
+
 	for k, v := range config {
 		key := k
 

From fd3ae8b1783ee6fc23a463ee0fc42f051b50bfb8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 20 Sep 2016 01:03:22 -0400
Subject: [PATCH 40/46] network: Cleanup all existing sub-devices
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go | 23 ++++++++++++++++-------
 1 file changed, 16 insertions(+), 7 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index f832c93..7804af9 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -512,6 +512,12 @@ func (n *network) Start() error {
 		}
 	}
 
+	// Get a list of interfaces
+	ifaces, err := net.Interfaces()
+	if err != nil {
+		return err
+	}
+
 	// IPv6 bridge configuration
 	if !shared.StringInSlice(n.config["ipv6.address"], []string{"", "none"}) {
 		err := ioutil.WriteFile(fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/autoconf", n.name), []byte("0"), 0)
@@ -537,7 +543,7 @@ func (n *network) Start() error {
 		}
 	}
 
-	err := shared.RunCommand("ip", "link", "set", n.name, "mtu", mtu)
+	err = shared.RunCommand("ip", "link", "set", n.name, "mtu", mtu)
 	if err != nil {
 		return err
 	}
@@ -768,17 +774,20 @@ func (n *network) Start() error {
 		}
 	}
 
-	// Cleanup an existing FAN tunnel
-	tunName := fmt.Sprintf("%s-fan", n.name)
-	if shared.PathExists(fmt.Sprintf("/sys/class/net/%s", tunName)) {
-		err = shared.RunCommand("ip", "link", "del", tunName)
-		if err != nil {
-			return err
+	// Cleanup any existing tunnel device
+	for _, iface := range ifaces {
+		if strings.HasPrefix(iface.Name, fmt.Sprintf("%s-", n.name)) {
+			err = shared.RunCommand("ip", "link", "del", iface.Name)
+			if err != nil {
+				return err
+			}
 		}
 	}
 
 	// Configure the fan
 	if n.config["bridge.mode"] == "fan" {
+		tunName := fmt.Sprintf("%s-fan", n.name)
+
 		// Parse the underlay
 		underlay := n.config["fan.underlay_subnet"]
 		_, underlaySubnet, err := net.ParseCIDR(underlay)

From 11b087db0522905efb9e2150ce4039bab1ed3967 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 20 Sep 2016 01:58:17 -0400
Subject: [PATCH 41/46] network: Rework IP validation functions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go            |  4 ++--
 lxd/networks_config.go |  8 ++++----
 lxd/networks_utils.go  | 31 +++++++++++++++----------------
 3 files changed, 21 insertions(+), 22 deletions(-)

diff --git a/lxd/main.go b/lxd/main.go
index d762dcd..e0aab3f 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -904,14 +904,14 @@ they otherwise would.
 				if shared.StringInSlice(value, []string{"auto", "none"}) {
 					return nil
 				}
-				return networkValidAddressV4(value)
+				return networkValidAddressCIDRV4(value)
 			})
 
 			bridgeIPv6 = askString("What IPv4 subnet should be used (CIDR notation, “auto” or “none”) [default=auto]? ", "auto", func(value string) error {
 				if shared.StringInSlice(value, []string{"auto", "none"}) {
 					return nil
 				}
-				return networkValidAddressV6(value)
+				return networkValidAddressCIDRV6(value)
 			})
 		}
 	}
diff --git a/lxd/networks_config.go b/lxd/networks_config.go
index b97e4d9..4ca2bd6 100644
--- a/lxd/networks_config.go
+++ b/lxd/networks_config.go
@@ -46,8 +46,8 @@ var networkConfigKeys = map[string]func(value string) error{
 	"tunnel.TARGET.protocol": func(value string) error {
 		return shared.IsOneOf(value, []string{"gre", "vxlan"})
 	},
-	"tunnel.TARGET.local":  networkValidAddress,
-	"tunnel.TARGET.remote": networkValidAddress,
+	"tunnel.TARGET.local":  networkValidAddressV4,
+	"tunnel.TARGET.remote": networkValidAddressV4,
 	"tunnel.TARGET.port":   networkValidPort,
 	"tunnel.TARGET.id":     shared.IsInt64,
 
@@ -56,7 +56,7 @@ var networkConfigKeys = map[string]func(value string) error{
 			return nil
 		}
 
-		return networkValidAddressV4(value)
+		return networkValidAddressCIDRV4(value)
 	},
 	"ipv4.nat":         shared.IsBool,
 	"ipv4.dhcp":        shared.IsBool,
@@ -68,7 +68,7 @@ var networkConfigKeys = map[string]func(value string) error{
 			return nil
 		}
 
-		return networkValidAddressV6(value)
+		return networkValidAddressCIDRV6(value)
 	},
 	"ipv6.nat":           shared.IsBool,
 	"ipv6.dhcp":          shared.IsBool,
diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index 16c510b..27f633c 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -432,21 +432,7 @@ func networkValidPort(value string) error {
 	return nil
 }
 
-func networkValidAddress(value string) error {
-	err := networkValidAddressV4(value)
-	if err == nil {
-		return nil
-	}
-
-	err = networkValidAddressV6(value)
-	if err == nil {
-		return nil
-	}
-
-	return fmt.Errorf("Not a valid address: %s", value)
-}
-
-func networkValidAddressV6(value string) error {
+func networkValidAddressCIDRV6(value string) error {
 	if value == "" {
 		return nil
 	}
@@ -488,7 +474,7 @@ func networkValidNetworkV6(value string) error {
 	return nil
 }
 
-func networkValidAddressV4(value string) error {
+func networkValidAddressCIDRV4(value string) error {
 	if value == "" {
 		return nil
 	}
@@ -509,6 +495,19 @@ func networkValidAddressV4(value string) error {
 	return nil
 }
 
+func networkValidAddressV4(value string) error {
+	if value == "" {
+		return nil
+	}
+
+	ip := net.ParseIP(value)
+	if ip == nil {
+		return fmt.Errorf("Not an IPv4 address: %s", value)
+	}
+
+	return nil
+}
+
 func networkValidNetworkV4(value string) error {
 	if value == "" {
 		return nil

From 1b8d61b525a37a6b2c1aba8f9b57bf2a47192a2e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 20 Sep 2016 01:58:35 -0400
Subject: [PATCH 42/46] network: Add tunnel support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go        | 105 ++++++++++++++++++++++++++++++++++++++++++++++---
 lxd/networks_config.go |  12 +++++-
 lxd/networks_utils.go  |  35 ++++++++++++-----
 3 files changed, 136 insertions(+), 16 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index 7804af9..c7fc9f6 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -518,6 +518,9 @@ func (n *network) Start() error {
 		return err
 	}
 
+	// Get a list of tunnels
+	tunnels := networkGetTunnels(n.config)
+
 	// IPv6 bridge configuration
 	if !shared.StringInSlice(n.config["ipv6.address"], []string{"", "none"}) {
 		err := ioutil.WriteFile(fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/autoconf", n.name), []byte("0"), 0)
@@ -535,6 +538,8 @@ func (n *network) Start() error {
 	mtu := "1500"
 	if n.config["bridge.mtu"] != "" {
 		mtu = n.config["bridge.mtu"]
+	} else if len(tunnels) > 0 {
+		mtu = "1400"
 	} else if n.config["bridge.mode"] == "fan" {
 		if n.config["fan.type"] == "ipip" {
 			mtu = "1480"
@@ -874,6 +879,87 @@ func (n *network) Start() error {
 		}
 	}
 
+	// Configure tunnels
+	for _, tunnel := range tunnels {
+		getConfig := func(key string) string {
+			return n.config[fmt.Sprintf("tunnel.%s.%s", tunnel, key)]
+		}
+
+		tunProtocol := getConfig("protocol")
+		tunLocal := getConfig("local")
+		tunRemote := getConfig("remote")
+		tunName := fmt.Sprintf("%s-%s", n.name, tunnel)
+
+		// Configure the tunnel
+		cmd := []string{"ip", "link", "add", tunName}
+		if tunProtocol == "gre" {
+			// Skip partial configs
+			if tunProtocol == "" || tunLocal == "" || tunRemote == "" {
+				continue
+			}
+
+			cmd = append(cmd, []string{"type", "gretap", "local", tunLocal, "remote", tunRemote}...)
+		} else if tunProtocol == "vxlan" {
+			tunGroup := getConfig("group")
+
+			// Skip partial configs
+			if tunProtocol == "" {
+				continue
+			}
+
+			cmd = append(cmd, []string{"type", "vxlan"}...)
+
+			if tunLocal != "" && tunRemote != "" {
+				cmd = append(cmd, []string{"local", tunLocal, "remote", tunRemote}...)
+			} else {
+				if tunGroup == "" {
+					tunGroup = "239.0.0.1"
+				}
+
+				_, devName, err := networkDefaultGatewaySubnetV4()
+				if err != nil {
+					return err
+				}
+
+				cmd = append(cmd, []string{"group", tunGroup, "dev", devName}...)
+			}
+
+			tunPort := getConfig("port")
+			if tunPort == "" {
+				tunPort = "0"
+			}
+			cmd = append(cmd, []string{"dstport", tunPort}...)
+
+			tunId := getConfig("id")
+			if tunId == "" {
+				tunId = "1"
+			}
+			cmd = append(cmd, []string{"id", tunId}...)
+		}
+
+		// Create the interface
+		err = shared.RunCommand(cmd[0], cmd[1:]...)
+		if err != nil {
+			return err
+		}
+
+		// Bridge it and bring up
+		err = networkAttachInterface(n.name, tunName)
+		if err != nil {
+			return err
+		}
+
+		err = shared.RunCommand("ip", "link", "set", tunName, "up")
+		if err != nil {
+			return err
+		}
+
+		err = shared.RunCommand("ip", "link", "set", n.name, "up")
+		if err != nil {
+			return err
+		}
+	}
+
 	return nil
 }
 
@@ -921,12 +1007,19 @@ func (n *network) Stop() error {
 		return err
 	}
 
-	// Cleanup an existing fan tunnel
-	tunName := fmt.Sprintf("%s-fan", n.name)
-	if shared.PathExists(fmt.Sprintf("/sys/class/net/%s", tunName)) {
-		err = shared.RunCommand("ip", "link", "del", tunName)
-		if err != nil {
-			return err
+	// Get a list of interfaces
+	ifaces, err := net.Interfaces()
+	if err != nil {
+		return err
+	}
+
+	// Cleanup any existing tunnel device
+	for _, iface := range ifaces {
+		if strings.HasPrefix(iface.Name, fmt.Sprintf("%s-", n.name)) {
+			err = shared.RunCommand("ip", "link", "del", iface.Name)
+			if err != nil {
+				return err
+			}
 		}
 	}
 
diff --git a/lxd/networks_config.go b/lxd/networks_config.go
index 4ca2bd6..86dd48d 100644
--- a/lxd/networks_config.go
+++ b/lxd/networks_config.go
@@ -49,6 +49,7 @@ var networkConfigKeys = map[string]func(value string) error{
 	"tunnel.TARGET.local":  networkValidAddressV4,
 	"tunnel.TARGET.remote": networkValidAddressV4,
 	"tunnel.TARGET.port":   networkValidPort,
+	"tunnel.TARGET.group":  networkValidAddressV4,
 	"tunnel.TARGET.id":     shared.IsInt64,
 
 	"ipv4.address": func(value string) error {
@@ -106,6 +107,10 @@ func networkValidateConfig(name string, config map[string]string) error {
 				return fmt.Errorf("Invalid network configuration key: %s", k)
 			}
 
+			if len(name)+len(fields[1]) > 14 {
+				return fmt.Errorf("Network name too long for tunnel interface: %s-%s", name, fields[1])
+			}
+
 			key = fmt.Sprintf("tunnel.TARGET.%s", fields[2])
 		}
 
@@ -161,6 +166,11 @@ func networkValidateConfig(name string, config map[string]string) error {
 					}
 				}
 			}
+
+			tunnels := networkGetTunnels(config)
+			if len(tunnels) > 0 && mtu > 1400 {
+				return fmt.Errorf("Maximum MTU when using tunnels is 1400")
+			}
 		}
 	}
 
@@ -177,7 +187,7 @@ func networkFillAuto(config map[string]string) error {
 	}
 
 	if config["fan.underlay_subnet"] == "auto" {
-		subnet, err := networkDefaultGatewaySubnetV4()
+		subnet, _, err := networkDefaultGatewaySubnetV4()
 		if err != nil {
 			return err
 		}
diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index 27f633c..e2cf1c7 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -141,6 +141,23 @@ func networkGetIP(subnet *net.IPNet, host int64) net.IP {
 	return newIp
 }
 
+func networkGetTunnels(config map[string]string) []string {
+	tunnels := []string{}
+
+	for k, _ := range config {
+		if !strings.HasPrefix(k, "tunnel.") {
+			continue
+		}
+
+		fields := strings.Split(k, ".")
+		if !shared.StringInSlice(fields[1], tunnels) {
+			tunnels = append(tunnels, fields[1])
+		}
+	}
+
+	return tunnels
+}
+
 func networkPingSubnet(subnet *net.IPNet) bool {
 	var fail bool
 	var failLock sync.Mutex
@@ -332,10 +349,10 @@ func networkRandomSubnetV6() string {
 	return cidr
 }
 
-func networkDefaultGatewaySubnetV4() (*net.IPNet, error) {
+func networkDefaultGatewaySubnetV4() (*net.IPNet, string, error) {
 	file, err := os.Open("/proc/net/route")
 	if err != nil {
-		return nil, err
+		return nil, "", err
 	}
 	defer file.Close()
 
@@ -357,17 +374,17 @@ func networkDefaultGatewaySubnetV4() (*net.IPNet, error) {
 	}
 
 	if ifaceName == "" {
-		return nil, fmt.Errorf("No default gateway for IPv4")
+		return nil, "", fmt.Errorf("No default gateway for IPv4")
 	}
 
 	iface, err := net.InterfaceByName(ifaceName)
 	if err != nil {
-		return nil, err
+		return nil, "", err
 	}
 
 	addrs, err := iface.Addrs()
 	if err != nil {
-		return nil, err
+		return nil, "", err
 	}
 
 	var subnet *net.IPNet
@@ -375,7 +392,7 @@ func networkDefaultGatewaySubnetV4() (*net.IPNet, error) {
 	for _, addr := range addrs {
 		addrIP, addrNet, err := net.ParseCIDR(addr.String())
 		if err != nil {
-			return nil, err
+			return nil, "", err
 		}
 
 		if addrIP.To4() == nil {
@@ -383,17 +400,17 @@ func networkDefaultGatewaySubnetV4() (*net.IPNet, error) {
 		}
 
 		if subnet != nil {
-			return nil, fmt.Errorf("More than one IPv4 subnet on default interface")
+			return nil, "", fmt.Errorf("More than one IPv4 subnet on default interface")
 		}
 
 		subnet = addrNet
 	}
 
 	if subnet == nil {
-		return nil, fmt.Errorf("No IPv4 subnet on default interface")
+		return nil, "", fmt.Errorf("No IPv4 subnet on default interface")
 	}
 
-	return subnet, nil
+	return subnet, ifaceName, nil
 }
 
 func networkValidName(value string) error {

From 35622b358bc7c3970844b4626833034ef614becc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 21 Sep 2016 17:42:14 -0400
Subject: [PATCH 43/46] network: Better handle mtu
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go | 51 ++++++++++++++++++++++++++++++++-------------------
 1 file changed, 32 insertions(+), 19 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index c7fc9f6..4563218 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -512,12 +512,6 @@ func (n *network) Start() error {
 		}
 	}
 
-	// Get a list of interfaces
-	ifaces, err := net.Interfaces()
-	if err != nil {
-		return err
-	}
-
 	// Get a list of tunnels
 	tunnels := networkGetTunnels(n.config)
 
@@ -534,8 +528,24 @@ func (n *network) Start() error {
 		}
 	}
 
+	// Get a list of interfaces
+	ifaces, err := net.Interfaces()
+	if err != nil {
+		return err
+	}
+
+	// Cleanup any existing tunnel device
+	for _, iface := range ifaces {
+		if strings.HasPrefix(iface.Name, fmt.Sprintf("%s-", n.name)) {
+			err = shared.RunCommand("ip", "link", "del", iface.Name)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
 	// Set the MTU
-	mtu := "1500"
+	mtu := ""
 	if n.config["bridge.mtu"] != "" {
 		mtu = n.config["bridge.mtu"]
 	} else if len(tunnels) > 0 {
@@ -548,6 +558,19 @@ func (n *network) Start() error {
 		}
 	}
 
+	// Attempt to add a dummy device to the bridge to force the MTU
+	if mtu != "" && n.config["bridge.driver"] != "openvswitch" {
+		err = shared.RunCommand("ip", "link", "add", fmt.Sprintf("%s-mtu", n.name), "mtu", mtu, "type", "dummy")
+		if err == nil {
+			networkAttachInterface(n.name, fmt.Sprintf("%s-mtu", n.name))
+		}
+	}
+
+	// Now, set a default MTU
+	if mtu == "" {
+		mtu = "1500"
+	}
+
 	err = shared.RunCommand("ip", "link", "set", n.name, "mtu", mtu)
 	if err != nil {
 		return err
@@ -779,16 +802,6 @@ func (n *network) Start() error {
 		}
 	}
 
-	// Cleanup any existing tunnel device
-	for _, iface := range ifaces {
-		if strings.HasPrefix(iface.Name, fmt.Sprintf("%s-", n.name)) {
-			err = shared.RunCommand("ip", "link", "del", iface.Name)
-			if err != nil {
-				return err
-			}
-		}
-	}
-
 	// Configure the fan
 	if n.config["bridge.mode"] == "fan" {
 		tunName := fmt.Sprintf("%s-fan", n.name)
@@ -861,7 +874,7 @@ func (n *network) Start() error {
 				return err
 			}
 
-			err = shared.RunCommand("ip", "link", "set", tunName, "up")
+			err = shared.RunCommand("ip", "link", "set", tunName, "mtu", mtu, "up")
 			if err != nil {
 				return err
 			}
@@ -949,7 +962,7 @@ func (n *network) Start() error {
 			return err
 		}
 
-		err = shared.RunCommand("ip", "link", "set", tunName, "up")
+		err = shared.RunCommand("ip", "link", "set", tunName, "mtu", mtu, "up")
 		if err != nil {
 			return err
 		}

From b90132d9a788588f5f12c973b9df9bbe66cf8029 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 21 Sep 2016 01:22:33 -0400
Subject: [PATCH 44/46] network: Add dnsmasq configuration
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go       | 126 +++++++++++++++++++++++++++++++++++++++++++++++++-
 lxd/networks_utils.go |  68 +++++++++++++++++++++++++++
 2 files changed, 192 insertions(+), 2 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index 4563218..e71c4c9 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -230,6 +230,11 @@ func networkDelete(d *Daemon, r *http.Request) Response {
 		return SmartError(err)
 	}
 
+	// Cleanup storage
+	if shared.PathExists(shared.VarPath("networks", n.name)) {
+		os.RemoveAll(shared.VarPath("networks", n.name))
+	}
+
 	return EmptySyncResponse
 }
 
@@ -481,6 +486,18 @@ func (n *network) Rename(name string) error {
 		}
 	}
 
+	// Rename directory
+	if shared.PathExists(shared.VarPath("networks", name)) {
+		os.RemoveAll(shared.VarPath("networks", name))
+	}
+
+	if shared.PathExists(shared.VarPath("networks", n.name)) {
+		err := os.Rename(shared.VarPath("networks", n.name), shared.VarPath("networks", name))
+		if err != nil {
+			return err
+		}
+	}
+
 	// Rename the database entry
 	err := dbNetworkRename(n.d.db, n.name, name)
 	if err != nil {
@@ -497,6 +514,14 @@ func (n *network) Rename(name string) error {
 }
 
 func (n *network) Start() error {
+	// Create directory
+	if !shared.PathExists(shared.VarPath("networks", n.name)) {
+		err := os.MkdirAll(shared.VarPath("networks", n.name), 0700)
+		if err != nil {
+			return err
+		}
+	}
+
 	// Create the bridge interface
 	if !n.IsRunning() {
 		if n.config["bridge.driver"] == "openvswitch" {
@@ -677,14 +702,37 @@ func (n *network) Start() error {
 		}
 	}
 
+	// Start building the dnsmasq command line
+	dnsmasqCmd := []string{"dnsmasq", "-u", "root", "--strict-order", "--bind-interfaces",
+		fmt.Sprintf("--pid-file=%s", shared.VarPath("networks", n.name, "dnsmasq.pid")),
+		"--except-interface=lo",
+		fmt.Sprintf("--interface=%s", n.name)}
+
 	// Configure IPv4
 	if !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) {
 		// Parse the subnet
-		_, subnet, err := net.ParseCIDR(n.config["ipv4.address"])
+		ip, subnet, err := net.ParseCIDR(n.config["ipv4.address"])
 		if err != nil {
 			return err
 		}
 
+		// Update the dnsmasq config
+		dnsmasqCmd = append(dnsmasqCmd, fmt.Sprintf("--listen-address=%s", ip.String()))
+		if n.config["ipv4.dhcp"] == "" || shared.IsTrue(n.config["ipv4.dhcp"]) {
+			if !shared.StringInSlice("--dhcp-no-override", dnsmasqCmd) {
+				dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-no-override", "--dhcp-authoritative", fmt.Sprintf("--dhcp-leasefile=%s", shared.VarPath("networks", n.name, "dnsmasq.leases")), fmt.Sprintf("--dhcp-hostsfile=%s", shared.VarPath("networks", n.name, "dnsmasq.hosts"))}...)
+			}
+
+			if n.config["ipv4.dhcp.ranges"] != "" {
+				for _, dhcpRange := range strings.Split(n.config["ipv4.dhcp.ranges"], ",") {
+					dhcpRange = strings.TrimSpace(dhcpRange)
+					dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", strings.Replace(dhcpRange, "-", ",", -1)}...)
+				}
+			} else {
+				dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", fmt.Sprintf("%s,%s", networkGetIP(subnet, 2).String(), networkGetIP(subnet, -2).String())}...)
+			}
+		}
+
 		// Add the address
 		err = shared.RunCommand("ip", "-4", "addr", "add", "dev", n.name, n.config["ipv4.address"])
 		if err != nil {
@@ -720,11 +768,37 @@ func (n *network) Start() error {
 	// Configure IPv6
 	if !shared.StringInSlice(n.config["ipv6.address"], []string{"", "none"}) {
 		// Parse the subnet
-		_, subnet, err := net.ParseCIDR(n.config["ipv6.address"])
+		ip, subnet, err := net.ParseCIDR(n.config["ipv6.address"])
 		if err != nil {
 			return err
 		}
 
+		// Update the dnsmasq config
+		dnsmasqCmd = append(dnsmasqCmd, fmt.Sprintf("--listen-address=%s", ip.String()))
+		if n.config["ipv6.dhcp"] == "" || shared.IsTrue(n.config["ipv6.dhcp"]) {
+			if !shared.StringInSlice("--dhcp-no-override", dnsmasqCmd) {
+				dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-no-override", "--dhcp-authoritative", fmt.Sprintf("--dhcp-leasefile=%s", shared.VarPath("networks", n.name, "dnsmasq.leases")), fmt.Sprintf("--dhcp-hostsfile=%s", shared.VarPath("networks", n.name, "dnsmasq.hosts"))}...)
+			}
+
+			flags := ""
+			if n.config["ipv6.dhcp"] == "" || shared.IsTrue(n.config["ipv6.dhcp"]) {
+				if !shared.IsTrue(n.config["ipv6.dhcp.stateful"]) {
+					flags = ",ra-stateless,ra-names"
+				}
+			} else {
+				flags = ",ra-only"
+			}
+
+			if n.config["ipv6.dhcp.ranges"] != "" {
+				for _, dhcpRange := range strings.Split(n.config["ipv6.dhcp.ranges"], ",") {
+					dhcpRange = strings.TrimSpace(dhcpRange)
+					dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", fmt.Sprintf("%s%s", strings.Replace(dhcpRange, "-", ",", -1), flags)}...)
+				}
+			} else {
+				dnsmasqCmd = append(dnsmasqCmd, []string{"--dhcp-range", fmt.Sprintf("%s%s", ip.String(), flags)}...)
+			}
+		}
+
 		// Setup basic iptables overrides
 		err = networkIptablesPrepend("ipv6", n.name, "", "INPUT", "-i", n.name, "-p", "udp", "--dport", "546", "-j", "ACCEPT")
 		if err != nil {
@@ -973,6 +1047,48 @@ func (n *network) Start() error {
 		}
 	}
 
+	// Kill any existing dnsmasq daemon for this network
+	err = networkKillDnsmasq(n.name, false)
+	if err != nil {
+		return err
+	}
+
+	// Configure dnsmasq
+	if !shared.StringInSlice(n.config["ipv4.address"], []string{"", "none"}) || !shared.StringInSlice(n.config["ipv6.address"], []string{"", "none"}) {
+		dnsDomain := n.config["dns.domain"]
+		if dnsDomain == "" {
+			dnsDomain = "lxd"
+		}
+
+		// Setup the dnsmasq domain
+		if n.config["dns.mode"] != "none" {
+			dnsmasqCmd = append(dnsmasqCmd, []string{"-s", dnsDomain, "-S", fmt.Sprintf("/%s/", dnsDomain)}...)
+		}
+
+		// Create raw config file
+		if n.config["raw.dnsmasq"] != "" {
+			err = ioutil.WriteFile(shared.VarPath("networks", n.name, "dnsmasq.raw"), []byte(fmt.Sprintf("%s\n", n.config["raw.dnsmasq"])), 0)
+			if err != nil {
+				return err
+			}
+			dnsmasqCmd = append(dnsmasqCmd, fmt.Sprintf("--conf-file=%s", shared.VarPath("networks", n.name, "dnsmasq.raw")))
+		}
+
+		// Create DHCP hosts file
+		if !shared.PathExists(shared.VarPath("networks", n.name, "dnsmasq.hosts")) {
+			err = ioutil.WriteFile(shared.VarPath("networks", n.name, "dnsmasq.hosts"), []byte(""), 0)
+			if err != nil {
+				return err
+			}
+		}
+
+		// Start dnsmasq
+		err := shared.RunCommand(dnsmasqCmd[0], dnsmasqCmd[1:]...)
+		if err != nil {
+			return err
+		}
+	}
+
 	return nil
 }
 
@@ -1020,6 +1136,12 @@ func (n *network) Stop() error {
 		return err
 	}
 
+	// Kill any existing dnsmasq daemon for this network
+	err = networkKillDnsmasq(n.name, false)
+	if err != nil {
+		return err
+	}
+
 	// Get a list of interfaces
 	ifaces, err := net.Interfaces()
 	if err != nil {
diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index e2cf1c7..b2fb062 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -5,12 +5,14 @@ import (
 	"encoding/binary"
 	"encoding/hex"
 	"fmt"
+	"io/ioutil"
 	"math"
 	"math/big"
 	"math/rand"
 	"net"
 	"os"
 	"os/exec"
+	"path/filepath"
 	"regexp"
 	"strconv"
 	"strings"
@@ -616,3 +618,69 @@ func networkFanAddress(underlay *net.IPNet, overlay *net.IPNet) (string, string,
 
 	return fmt.Sprintf("%s/%d", ipBytes.String(), overlaySize), dev, ipStr, err
 }
+
+func networkKillDnsmasq(name string, reload bool) error {
+	// Check if we have a running dnsmasq at all
+	pidPath := shared.VarPath("networks", name, "dnsmasq.pid")
+	if !shared.PathExists(pidPath) {
+		if reload {
+			return fmt.Errorf("dnsmasq isn't running")
+		}
+
+		return nil
+	}
+
+	// Grab the PID
+	content, err := ioutil.ReadFile(pidPath)
+	if err != nil {
+		return err
+	}
+	pid := strings.TrimSpace(string(content))
+
+	// Check if the process still exists
+	if !shared.PathExists(fmt.Sprintf("/proc/%s", pid)) {
+		os.Remove(pidPath)
+
+		if reload {
+			return fmt.Errorf("dnsmasq isn't running")
+		}
+
+		return nil
+	}
+
+	// Check if it's dnsmasq
+	cmdPath, err := os.Readlink(fmt.Sprintf("/proc/%s/exe", pid))
+	if err != nil {
+		return err
+	}
+
+	// Deal with deleted paths
+	cmdName := filepath.Base(strings.Split(cmdPath, " ")[0])
+	if cmdName != "dnsmasq" {
+		if reload {
+			return fmt.Errorf("dnsmasq isn't running")
+		}
+
+		os.Remove(pidPath)
+		return nil
+	}
+
+	// Actually kill the process
+	if reload {
+		err = shared.RunCommand("kill", "-HUP", pid)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+
+	err = shared.RunCommand("kill", "-9", pid)
+	if err != nil {
+		return err
+	}
+
+	// Cleanup
+	os.Remove(pidPath)
+	return nil
+}

From c6d09ce0dd4ddbbfd401c86c9f9110a12983c671 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 21 Sep 2016 22:11:13 -0400
Subject: [PATCH 45/46] network: Add static ipv4/ipv6 DHCP leases
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container.go      |   4 ++
 lxd/container_lxc.go  |  20 +++++++++
 lxd/networks.go       |   6 +++
 lxd/networks_utils.go | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++
 shared/devices.go     |   2 +-
 5 files changed, 142 insertions(+), 1 deletion(-)

diff --git a/lxd/container.go b/lxd/container.go
index 87c78dd..2d61b51 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -102,6 +102,10 @@ func containerValidDeviceConfigKey(t, k string) bool {
 			return true
 		case "parent":
 			return true
+		case "ipv4.address":
+			return true
+		case "ipv6.address":
+			return true
 		default:
 			return false
 		}
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index dc1b027..269c40c 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -232,6 +232,9 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 		return nil, err
 	}
 
+	// Update lease files
+	networkUpdateStatic(d)
+
 	return c, nil
 }
 
@@ -2101,6 +2104,9 @@ func (c *containerLXC) Delete() error {
 		return err
 	}
 
+	// Update lease files
+	networkUpdateStatic(c.daemon)
+
 	shared.LogInfo("Deleted container", ctxMap)
 
 	return nil
@@ -2756,6 +2762,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 			if m["type"] == "disk" {
 				updateDiskLimit = true
 			} else if m["type"] == "nic" {
+				// Refresh tc limits
 				err = c.setNetworkLimits(k, m)
 				if err != nil {
 					return err
@@ -2834,6 +2841,19 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		return err
 	}
 
+	// Update network leases
+	needsUpdate := false
+	for _, m := range updateDevices {
+		if m["type"] == "nic" && m["nictype"] == "bridged" {
+			needsUpdate = true
+			break
+		}
+	}
+
+	if needsUpdate {
+		networkUpdateStatic(c.daemon)
+	}
+
 	// Invalidate the go-lxc cache
 	c.c = nil
 
diff --git a/lxd/networks.go b/lxd/networks.go
index e71c4c9..9dddbef 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -1087,6 +1087,12 @@ func (n *network) Start() error {
 		if err != nil {
 			return err
 		}
+
+		// Update the static leases
+		err = networkUpdateStatic(n.d)
+		if err != nil {
+			return err
+		}
 	}
 
 	return nil
diff --git a/lxd/networks_utils.go b/lxd/networks_utils.go
index b2fb062..f79980e 100644
--- a/lxd/networks_utils.go
+++ b/lxd/networks_utils.go
@@ -684,3 +684,114 @@ func networkKillDnsmasq(name string, reload bool) error {
 	os.Remove(pidPath)
 	return nil
 }
+
+func networkUpdateStatic(d *Daemon) error {
+	// Get all the containers
+	containers, err := dbContainersList(d.db, cTypeRegular)
+	if err != nil {
+		return err
+	}
+
+	// Get all the networks
+	networks, err := dbNetworks(d.db)
+	if err != nil {
+		return err
+	}
+
+	// Build a list of dhcp host entries
+	entries := map[string][][]string{}
+	for _, name := range containers {
+		// Load the container
+		c, err := containerLoadByName(d, name)
+		if err != nil {
+			continue
+		}
+
+		// Go through all its devices (including profiles
+		for k, d := range c.ExpandedDevices() {
+			// Skip uninteresting entries
+			if d["type"] != "nic" || d["nictype"] != "bridged" || !shared.StringInSlice(d["parent"], networks) {
+				continue
+			}
+
+			// Fill in the hwaddr from volatile
+			d, err = c.(*containerLXC).fillNetworkDevice(k, d)
+			if err != nil {
+				continue
+			}
+
+			// Add the new host entries
+			_, ok := entries[d["parent"]]
+			if !ok {
+				entries[d["parent"]] = [][]string{}
+			}
+
+			entries[d["parent"]] = append(entries[d["parent"]], []string{d["hwaddr"], name, d["ipv4.address"], d["ipv6.address"]})
+		}
+	}
+
+	// Update the host files
+	for _, network := range networks {
+		entries, _ := entries[network]
+
+		// Skip networks we don't manage (or don't have DHCP enabled)
+		if !shared.PathExists(shared.VarPath("networks", network, "dnsmasq.hosts")) {
+			continue
+		}
+
+		n, err := networkLoadByName(d, network)
+		if err != nil {
+			return err
+		}
+		config := n.Config()
+
+		// Update the file
+		if entries == nil {
+			err := ioutil.WriteFile(shared.VarPath("networks", network, "dnsmasq.hosts"), []byte(""), 0)
+			if err != nil {
+				return err
+			}
+		} else {
+			lines := []string{}
+			for _, entry := range entries {
+				hwaddr := entry[0]
+				name := entry[1]
+				ipv4Address := entry[2]
+				ipv6Address := entry[3]
+
+				line := hwaddr
+
+				if ipv4Address != "" {
+					line += fmt.Sprintf(",id:*,%s", ipv4Address)
+				}
+
+				if ipv6Address != "" {
+					line += fmt.Sprintf(",[%s]", ipv6Address)
+				}
+
+				if config["dns.mode"] == "" || config["dns.mode"] == "managed" {
+					line += fmt.Sprintf(",%s", name)
+				}
+
+				if line == hwaddr {
+					continue
+				}
+
+				lines = append(lines, line)
+			}
+
+			err := ioutil.WriteFile(shared.VarPath("networks", network, "dnsmasq.hosts"), []byte(strings.Join(lines, "\n")+"\n"), 0)
+			if err != nil {
+				return err
+			}
+		}
+
+		// Signal dnsmasq
+		err = networkKillDnsmasq(network, true)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/shared/devices.go b/shared/devices.go
index e2b22db..fe43c9a 100644
--- a/shared/devices.go
+++ b/shared/devices.go
@@ -78,7 +78,7 @@ func (old Devices) Update(newlist Devices) (map[string]Device, map[string]Device
 			continue
 		}
 
-		for _, k := range []string{"limits.max", "limits.read", "limits.write", "limits.egress", "limits.ingress"} {
+		for _, k := range []string{"limits.max", "limits.read", "limits.write", "limits.egress", "limits.ingress", "ipv4.address", "ipv6.address"} {
 			delete(oldDevice, k)
 			delete(newDevice, k)
 		}

From d349f872a7c4dd43d278469d42b1f55be43b0015 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 21 Sep 2016 22:14:05 -0400
Subject: [PATCH 46/46] network: Rename "d" to "daemon" for consistency
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index 9dddbef..1563809 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -382,7 +382,7 @@ func networkLoadByName(d *Daemon, name string) (*network, error) {
 		return nil, err
 	}
 
-	n := network{d: d, id: id, name: name, config: dbInfo.Config}
+	n := network{daemon: d, id: id, name: name, config: dbInfo.Config}
 
 	return &n, nil
 }
@@ -412,9 +412,9 @@ func networkStartup(d *Daemon) error {
 
 type network struct {
 	// Properties
-	d    *Daemon
-	id   int64
-	name string
+	daemon *Daemon
+	id     int64
+	name   string
 
 	// config
 	config map[string]string
@@ -430,13 +430,13 @@ func (n *network) IsRunning() bool {
 
 func (n *network) IsUsed() bool {
 	// Look for containers using the interface
-	cts, err := dbContainersList(n.d.db, cTypeRegular)
+	cts, err := dbContainersList(n.daemon.db, cTypeRegular)
 	if err != nil {
 		return true
 	}
 
 	for _, ct := range cts {
-		c, err := containerLoadByName(n.d, ct)
+		c, err := containerLoadByName(n.daemon, ct)
 		if err != nil {
 			return true
 		}
@@ -464,7 +464,7 @@ func (n *network) Delete() error {
 	}
 
 	// Remove the network from the database
-	err := dbNetworkDelete(n.d.db, n.name)
+	err := dbNetworkDelete(n.daemon.db, n.name)
 	if err != nil {
 		return err
 	}
@@ -499,7 +499,7 @@ func (n *network) Rename(name string) error {
 	}
 
 	// Rename the database entry
-	err := dbNetworkRename(n.d.db, n.name, name)
+	err := dbNetworkRename(n.daemon.db, n.name, name)
 	if err != nil {
 		return err
 	}
@@ -1089,7 +1089,7 @@ func (n *network) Start() error {
 		}
 
 		// Update the static leases
-		err = networkUpdateStatic(n.d)
+		err = networkUpdateStatic(n.daemon)
 		if err != nil {
 			return err
 		}
@@ -1238,7 +1238,7 @@ func (n *network) Update(newNetwork shared.NetworkConfig) error {
 	n.config = newConfig
 
 	// Update the database
-	err = dbNetworkUpdate(n.d.db, n.name, n.config)
+	err = dbNetworkUpdate(n.daemon.db, n.name, n.config)
 	if err != nil {
 		return err
 	}


More information about the lxc-devel mailing list