stanza.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. // Copyright 2015 CoreOS, Inc.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package network
  15. import (
  16. "fmt"
  17. "net"
  18. "strconv"
  19. "strings"
  20. )
  21. type stanza interface{}
  22. type stanzaAuto struct {
  23. interfaces []string
  24. }
  25. type stanzaInterface struct {
  26. name string
  27. kind interfaceKind
  28. auto bool
  29. configMethod configMethod
  30. options map[string][]string
  31. }
  32. type interfaceKind int
  33. const (
  34. interfaceBond = interfaceKind(iota)
  35. interfacePhysical
  36. interfaceVLAN
  37. )
  38. type route struct {
  39. destination net.IPNet
  40. gateway net.IP
  41. }
  42. type configMethod interface{}
  43. type configMethodStatic struct {
  44. addresses []net.IPNet
  45. nameservers []net.IP
  46. domains []string
  47. routes []route
  48. hwaddress net.HardwareAddr
  49. }
  50. type configMethodLoopback struct{}
  51. type configMethodManual struct{}
  52. type configMethodDHCP struct {
  53. hwaddress net.HardwareAddr
  54. }
  55. func parseStanzas(lines []string) (stanzas []stanza, err error) {
  56. rawStanzas, err := splitStanzas(lines)
  57. if err != nil {
  58. return nil, err
  59. }
  60. stanzas = make([]stanza, 0, len(rawStanzas))
  61. for _, rawStanza := range rawStanzas {
  62. if stanza, err := parseStanza(rawStanza); err == nil {
  63. stanzas = append(stanzas, stanza)
  64. } else {
  65. return nil, err
  66. }
  67. }
  68. autos := make([]string, 0)
  69. interfaceMap := make(map[string]*stanzaInterface)
  70. for _, stanza := range stanzas {
  71. switch c := stanza.(type) {
  72. case *stanzaAuto:
  73. autos = append(autos, c.interfaces...)
  74. case *stanzaInterface:
  75. interfaceMap[c.name] = c
  76. }
  77. }
  78. // Apply the auto attribute
  79. for _, auto := range autos {
  80. if iface, ok := interfaceMap[auto]; ok {
  81. iface.auto = true
  82. }
  83. }
  84. return stanzas, nil
  85. }
  86. func splitStanzas(lines []string) ([][]string, error) {
  87. var curStanza []string
  88. stanzas := make([][]string, 0)
  89. for _, line := range lines {
  90. if isStanzaStart(line) {
  91. if curStanza != nil {
  92. stanzas = append(stanzas, curStanza)
  93. }
  94. curStanza = []string{line}
  95. } else if curStanza != nil {
  96. curStanza = append(curStanza, line)
  97. } else {
  98. return nil, fmt.Errorf("missing stanza start %q", line)
  99. }
  100. }
  101. if curStanza != nil {
  102. stanzas = append(stanzas, curStanza)
  103. }
  104. return stanzas, nil
  105. }
  106. func isStanzaStart(line string) bool {
  107. switch strings.Split(line, " ")[0] {
  108. case "auto":
  109. fallthrough
  110. case "iface":
  111. fallthrough
  112. case "mapping":
  113. return true
  114. }
  115. if strings.HasPrefix(line, "allow-") {
  116. return true
  117. }
  118. return false
  119. }
  120. func parseStanza(rawStanza []string) (stanza, error) {
  121. if len(rawStanza) == 0 {
  122. panic("empty stanza")
  123. }
  124. tokens := strings.Fields(rawStanza[0])
  125. if len(tokens) < 2 {
  126. return nil, fmt.Errorf("malformed stanza start %q", rawStanza[0])
  127. }
  128. kind := tokens[0]
  129. attributes := tokens[1:]
  130. switch kind {
  131. case "auto":
  132. return parseAutoStanza(attributes, rawStanza[1:])
  133. case "iface":
  134. return parseInterfaceStanza(attributes, rawStanza[1:])
  135. default:
  136. return nil, fmt.Errorf("unknown stanza %q", kind)
  137. }
  138. }
  139. func parseAutoStanza(attributes []string, options []string) (*stanzaAuto, error) {
  140. return &stanzaAuto{interfaces: attributes}, nil
  141. }
  142. func parseInterfaceStanza(attributes []string, options []string) (*stanzaInterface, error) {
  143. if len(attributes) != 3 {
  144. return nil, fmt.Errorf("incorrect number of attributes")
  145. }
  146. iface := attributes[0]
  147. confMethod := attributes[2]
  148. optionMap := make(map[string][]string, 0)
  149. for _, option := range options {
  150. if strings.HasPrefix(option, "post-up") {
  151. tokens := strings.SplitAfterN(option, " ", 2)
  152. if len(tokens) != 2 {
  153. continue
  154. }
  155. if v, ok := optionMap["post-up"]; ok {
  156. optionMap["post-up"] = append(v, tokens[1])
  157. } else {
  158. optionMap["post-up"] = []string{tokens[1]}
  159. }
  160. } else if strings.HasPrefix(option, "pre-down") {
  161. tokens := strings.SplitAfterN(option, " ", 2)
  162. if len(tokens) != 2 {
  163. continue
  164. }
  165. if v, ok := optionMap["pre-down"]; ok {
  166. optionMap["pre-down"] = append(v, tokens[1])
  167. } else {
  168. optionMap["pre-down"] = []string{tokens[1]}
  169. }
  170. } else {
  171. tokens := strings.Fields(option)
  172. optionMap[tokens[0]] = tokens[1:]
  173. }
  174. }
  175. var conf configMethod
  176. switch confMethod {
  177. case "static":
  178. config := configMethodStatic{
  179. addresses: make([]net.IPNet, 1),
  180. routes: make([]route, 0),
  181. nameservers: make([]net.IP, 0),
  182. }
  183. if addresses, ok := optionMap["address"]; ok {
  184. if len(addresses) == 1 {
  185. config.addresses[0].IP = net.ParseIP(addresses[0])
  186. }
  187. }
  188. if netmasks, ok := optionMap["netmask"]; ok {
  189. if len(netmasks) == 1 {
  190. config.addresses[0].Mask = net.IPMask(net.ParseIP(netmasks[0]).To4())
  191. }
  192. }
  193. if config.addresses[0].IP == nil || config.addresses[0].Mask == nil {
  194. return nil, fmt.Errorf("malformed static network config for %q", iface)
  195. }
  196. if gateways, ok := optionMap["gateway"]; ok {
  197. if len(gateways) == 1 {
  198. config.routes = append(config.routes, route{
  199. destination: net.IPNet{
  200. IP: net.IPv4(0, 0, 0, 0),
  201. Mask: net.IPv4Mask(0, 0, 0, 0),
  202. },
  203. gateway: net.ParseIP(gateways[0]),
  204. })
  205. }
  206. }
  207. if hwaddress, err := parseHwaddress(optionMap, iface); err == nil {
  208. config.hwaddress = hwaddress
  209. } else {
  210. return nil, err
  211. }
  212. for _, nameserver := range optionMap["dns-nameservers"] {
  213. config.nameservers = append(config.nameservers, net.ParseIP(nameserver))
  214. }
  215. for _, postup := range optionMap["post-up"] {
  216. if strings.HasPrefix(postup, "route add") {
  217. route := route{}
  218. fields := strings.Fields(postup)
  219. for i, field := range fields[:len(fields)-1] {
  220. switch field {
  221. case "-net":
  222. if _, dst, err := net.ParseCIDR(fields[i+1]); err == nil {
  223. route.destination = *dst
  224. } else {
  225. route.destination.IP = net.ParseIP(fields[i+1])
  226. }
  227. case "netmask":
  228. route.destination.Mask = net.IPMask(net.ParseIP(fields[i+1]).To4())
  229. case "gw":
  230. route.gateway = net.ParseIP(fields[i+1])
  231. }
  232. }
  233. if route.destination.IP != nil && route.destination.Mask != nil && route.gateway != nil {
  234. config.routes = append(config.routes, route)
  235. }
  236. }
  237. }
  238. conf = config
  239. case "loopback":
  240. conf = configMethodLoopback{}
  241. case "manual":
  242. conf = configMethodManual{}
  243. case "dhcp":
  244. config := configMethodDHCP{}
  245. if hwaddress, err := parseHwaddress(optionMap, iface); err == nil {
  246. config.hwaddress = hwaddress
  247. } else {
  248. return nil, err
  249. }
  250. conf = config
  251. default:
  252. return nil, fmt.Errorf("invalid config method %q", confMethod)
  253. }
  254. if _, ok := optionMap["vlan_raw_device"]; ok {
  255. return parseVLANStanza(iface, conf, attributes, optionMap)
  256. }
  257. if strings.Contains(iface, ".") {
  258. return parseVLANStanza(iface, conf, attributes, optionMap)
  259. }
  260. if _, ok := optionMap["bond-slaves"]; ok {
  261. return parseBondStanza(iface, conf, attributes, optionMap)
  262. }
  263. return parsePhysicalStanza(iface, conf, attributes, optionMap)
  264. }
  265. func parseHwaddress(options map[string][]string, iface string) (net.HardwareAddr, error) {
  266. if hwaddress, ok := options["hwaddress"]; ok && len(hwaddress) == 2 {
  267. switch hwaddress[0] {
  268. case "ether":
  269. if address, err := net.ParseMAC(hwaddress[1]); err == nil {
  270. return address, nil
  271. }
  272. return nil, fmt.Errorf("malformed hwaddress option for %q", iface)
  273. }
  274. }
  275. return nil, nil
  276. }
  277. func parseBondStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) {
  278. return &stanzaInterface{name: iface, kind: interfaceBond, configMethod: conf, options: options}, nil
  279. }
  280. func parsePhysicalStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) {
  281. return &stanzaInterface{name: iface, kind: interfacePhysical, configMethod: conf, options: options}, nil
  282. }
  283. func parseVLANStanza(iface string, conf configMethod, attributes []string, options map[string][]string) (*stanzaInterface, error) {
  284. var id string
  285. if strings.Contains(iface, ".") {
  286. tokens := strings.Split(iface, ".")
  287. id = tokens[len(tokens)-1]
  288. } else if strings.HasPrefix(iface, "vlan") {
  289. id = strings.TrimPrefix(iface, "vlan")
  290. } else {
  291. return nil, fmt.Errorf("malformed vlan name %q", iface)
  292. }
  293. if _, err := strconv.Atoi(id); err != nil {
  294. return nil, fmt.Errorf("malformed vlan name %q", iface)
  295. }
  296. options["id"] = []string{id}
  297. options["raw_device"] = options["vlan_raw_device"]
  298. return &stanzaInterface{name: iface, kind: interfaceVLAN, configMethod: conf, options: options}, nil
  299. }