-
Notifications
You must be signed in to change notification settings - Fork 187
/
default_network.go
222 lines (201 loc) · 6.29 KB
/
default_network.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
package defaultnet
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"regexp"
"text/template"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// TODO: A smarter implementation would make sure cni-podman0 was unused before
// making the default, and adjust if necessary
const networkTemplate = `{
"cniVersion": "0.4.0",
"name": "{{{{.Name}}}}",
"plugins": [
{
"type": "bridge",
"bridge": "cni-podman0",
"isGateway": true,
"ipMasq": true,
"hairpinMode": true,
"ipam": {
"type": "host-local",
"routes": [{ "dst": "0.0.0.0/0" }],
"ranges": [
[
{
"subnet": "{{{{.Subnet}}}}",
"gateway": "{{{{.Gateway}}}}"
}
]
]
}
},
{{{{- if (eq .Machine true) }}}}
{
"type": "podman-machine",
"capabilities": {
"portMappings": true
}
},
{{{{- end}}}}
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
},
{
"type": "firewall"
},
{
"type": "tuning"
}
]
}
`
var (
// Borrowed from Podman, modified to remove dashes and periods.
nameRegex = regexp.MustCompile("^[a-zA-Z0-9][a-zA-Z0-9_]*$")
)
// Used to pass info into the template engine
type networkInfo struct {
Name string
Subnet string
Gateway string
Machine bool
}
// The most trivial definition of a CNI network possible for our use here.
// We need the name, and nothing else.
type network struct {
Name string `json:"name"`
}
// Create makes the CNI default network, if necessary.
// Accepts the name and subnet of the network to create (a standard template
// will be used, with these values plugged in), the configuration directory
// where CNI configs are stored (to verify if a named configuration already
// exists), an exists directory (where a sentinel file will be stored, to ensure
// the network is only made once), and an isMachine bool (to determine whether
// the machine block will be added to the config).
// Create first checks if a default network has already been created via the
// presence of a sentinel file. If it does exist, it returns immediately without
// error.
// It next checks if a CNI network with the given name already exists. In that
// case, it creates the sentinel file and returns without error.
// If neither of these are true, the default network is created.
func Create(name, subnet, configDir, existsDir string, isMachine bool) error {
// TODO: Should probably regex name to make sure it's valid.
if name == "" || subnet == "" || configDir == "" || existsDir == "" {
return errors.Errorf("must provide values for all arguments to MakeDefaultNetwork")
}
if !nameRegex.MatchString(name) {
return errors.Errorf("invalid default network name %s - letters, numbers, and underscores only", name)
}
sentinelFile := filepath.Join(existsDir, "defaultCNINetExists")
// Check if sentinel file exists, return immediately if it does.
if _, err := os.Stat(sentinelFile); err == nil {
return nil
}
// Create the sentinel file if it doesn't exist, so subsequent checks
// don't need to go further.
file, err := os.Create(sentinelFile)
if err != nil {
return err
}
file.Close()
// We may need to make the config dir.
if err := os.MkdirAll(configDir, 0755); err != nil && !os.IsExist(err) {
return errors.Wrapf(err, "error creating CNI configuration directory")
}
// Check all networks in the CNI conflist.
files, err := ioutil.ReadDir(configDir)
if err != nil {
return errors.Wrapf(err, "error reading CNI configuration directory")
}
if len(files) > 0 {
configPaths := make([]string, 0, len(files))
for _, path := range files {
if !path.IsDir() && filepath.Ext(path.Name()) == ".conflist" {
configPaths = append(configPaths, filepath.Join(configDir, path.Name()))
}
}
for _, config := range configPaths {
configName, err := getConfigName(config)
if err != nil {
logrus.Errorf("Error reading CNI configuration file: %v", err)
continue
}
if configName == name {
return nil
}
}
}
// We need to make the config.
// Get subnet and gateway.
_, ipNet, err := net.ParseCIDR(subnet)
if err != nil {
return errors.Wrapf(err, "default network subnet %s is invalid", subnet)
}
ones, bits := ipNet.Mask.Size()
if ones == bits {
return errors.Wrapf(err, "default network subnet %s is to small", subnet)
}
gateway := make(net.IP, len(ipNet.IP))
// copy the subnet ip to the gateway so we can modify it
copy(gateway, ipNet.IP)
// the default gateway should be the first ip in the subnet
gateway[len(gateway)-1]++
netInfo := new(networkInfo)
netInfo.Name = name
netInfo.Gateway = gateway.String()
netInfo.Subnet = ipNet.String()
netInfo.Machine = isMachine
templ, err := template.New("network_template").Delims("{{{{", "}}}}").Parse(networkTemplate)
if err != nil {
return errors.Wrapf(err, "error compiling template for default network")
}
var output bytes.Buffer
if err := templ.Execute(&output, netInfo); err != nil {
return errors.Wrapf(err, "error executing template for default network")
}
// Next, we need to place the config on disk.
// Loop through possible indexes, with a limit of 100 attempts.
created := false
for i := 87; i < 187; i++ {
configFile, err := os.OpenFile(filepath.Join(configDir, fmt.Sprintf("%d-%s.conflist", i, name)), os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
if err != nil {
logrus.Infof("Attempt to create default CNI network config file failed: %v", err)
continue
}
defer configFile.Close()
created = true
// Success - file is open. Write our buffer to it.
if _, err := configFile.Write(output.Bytes()); err != nil {
return errors.Wrapf(err, "error writing default CNI config to file")
}
break
}
if !created {
return errors.Errorf("no available default network configuration file was found")
}
return nil
}
// Get the name of the configuration contained in a given conflist file. Accepts
// the full path of a .conflist CNI configuration.
func getConfigName(file string) (string, error) {
contents, err := ioutil.ReadFile(file)
if err != nil {
return "", err
}
config := new(network)
if err := json.Unmarshal(contents, config); err != nil {
return "", errors.Wrapf(err, "error decoding CNI configuration %s", filepath.Base(file))
}
return config.Name, nil
}