[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