2433 lines
66 KiB
Go
2433 lines
66 KiB
Go
package common
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"log"
|
|
"math"
|
|
"net"
|
|
"os"
|
|
"reflect"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
/** low-level parsing */
|
|
// strip the comments and extraneous newlines from a byte channel
|
|
func uncomment(in <-chan byte) chan byte {
|
|
out := make(chan byte)
|
|
|
|
go func(in <-chan byte, out chan byte) {
|
|
var endofline bool
|
|
|
|
for {
|
|
by, ok := <-in
|
|
if !ok {
|
|
break
|
|
}
|
|
|
|
// If we find a comment, then everything until the end of line
|
|
// needs to be culled. We keep track of that using the `endofline`
|
|
// flag.
|
|
if by == '#' {
|
|
endofline = true
|
|
|
|
} else if by == '\n' && endofline {
|
|
endofline = false
|
|
}
|
|
|
|
// If we're not in the processing of culling bytes, then write what
|
|
// we've read into our output chan.
|
|
if !endofline {
|
|
out <- by
|
|
}
|
|
}
|
|
close(out)
|
|
}(in, out)
|
|
return out
|
|
}
|
|
|
|
// convert a byte channel into a channel of pseudo-tokens
|
|
func tokenizeDhcpConfig(in chan byte) chan string {
|
|
var state string
|
|
var quote bool
|
|
|
|
out := make(chan string)
|
|
go func(out chan string) {
|
|
for {
|
|
by, ok := <-in
|
|
if !ok {
|
|
break
|
|
}
|
|
|
|
// If we're in a quote, then we continue until we're not in a quote
|
|
// before we start looing for tokens
|
|
if quote {
|
|
if by == '"' {
|
|
out <- state + string(by)
|
|
state, quote = "", false
|
|
continue
|
|
}
|
|
state += string(by)
|
|
continue
|
|
}
|
|
|
|
switch by {
|
|
case '"':
|
|
// Otherwise we're outside any quotes and can process bytes normaly
|
|
quote = true
|
|
state += string(by)
|
|
continue
|
|
|
|
case '\r':
|
|
fallthrough
|
|
case '\n':
|
|
fallthrough
|
|
case '\t':
|
|
fallthrough
|
|
case ' ':
|
|
// Whitespace is a separator, so we check to see if there's any state.
|
|
// If so, then write our state prior to resetting.
|
|
|
|
if len(state) == 0 {
|
|
continue
|
|
}
|
|
out <- state
|
|
state = ""
|
|
|
|
case '{':
|
|
fallthrough
|
|
case '}':
|
|
fallthrough
|
|
case ';':
|
|
// If we encounter a brace or a semicolon, then we need to emit our
|
|
// state and then the byte because it can be part of the token.
|
|
|
|
if len(state) > 0 {
|
|
out <- state
|
|
}
|
|
out <- string(by)
|
|
state = ""
|
|
|
|
default:
|
|
// Just a byte which needs to be aggregated into our state
|
|
state += string(by)
|
|
}
|
|
}
|
|
|
|
// If we still have any data left, then make sure to emit that
|
|
if len(state) > 0 {
|
|
out <- state
|
|
}
|
|
|
|
// Close our channel since we're responsible for it.
|
|
close(out)
|
|
}(out)
|
|
return out
|
|
}
|
|
|
|
/** mid-level parsing */
|
|
type tkParameter struct {
|
|
name string
|
|
operand []string
|
|
}
|
|
|
|
func (e *tkParameter) String() string {
|
|
var values []string
|
|
for _, val := range e.operand {
|
|
values = append(values, val)
|
|
}
|
|
return fmt.Sprintf("%s [%s]", e.name, strings.Join(values, ","))
|
|
}
|
|
|
|
type tkGroup struct {
|
|
parent *tkGroup
|
|
id tkParameter
|
|
|
|
groups []*tkGroup
|
|
params []tkParameter
|
|
}
|
|
|
|
func (e *tkGroup) String() string {
|
|
var id []string
|
|
|
|
id = append(id, e.id.name)
|
|
for _, val := range e.id.operand {
|
|
id = append(id, val)
|
|
}
|
|
|
|
var config []string
|
|
for _, val := range e.params {
|
|
config = append(config, val.String())
|
|
}
|
|
return fmt.Sprintf("%s {\n%s\n}", strings.Join(id, " "), strings.Join(config, "\n"))
|
|
}
|
|
|
|
// convert a channel of pseudo-tokens into an tkParameter struct
|
|
func parseTokenParameter(in chan string) tkParameter {
|
|
var result tkParameter
|
|
for {
|
|
token, ok := <-in
|
|
if !ok {
|
|
break
|
|
}
|
|
|
|
// If there's no name for this parameter yet, then the first token
|
|
// is our name. Snag it into our struct, and grab the next one.
|
|
if result.name == "" {
|
|
result.name = token
|
|
continue
|
|
}
|
|
|
|
// If encounter any braces or line-terminators, then we're done parsing.
|
|
// Anything else we find are just operands we need to keep track of.
|
|
if strings.ContainsAny("{};", token) {
|
|
break
|
|
} else {
|
|
result.operand = append(result.operand, token)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// convert a channel of pseudo-tokens into an tkGroup tree */
|
|
func parseDhcpConfig(in chan string) (tkGroup, error) {
|
|
var tokens []string
|
|
var result tkGroup
|
|
|
|
// This utility function takes a list of tokens and line-terminates them
|
|
// before sending them to parseTokenParameter().
|
|
toParameter := func(tokens []string) tkParameter {
|
|
out := make(chan string)
|
|
go func(out chan string) {
|
|
for _, v := range tokens {
|
|
out <- v
|
|
}
|
|
out <- ";"
|
|
close(out)
|
|
}(out)
|
|
return parseTokenParameter(out)
|
|
}
|
|
|
|
// Start building our tree using result as our root node
|
|
node := &result
|
|
for {
|
|
tk, ok := <-in
|
|
if !ok {
|
|
break
|
|
}
|
|
|
|
switch tk {
|
|
case "{":
|
|
// If our next token is an opening brace, then we need to collect our
|
|
// current aggregated tokens to parse, push our current node onto the
|
|
// tree, and then pivot into it. Then we can reset our tokens for the child.
|
|
|
|
grp := &tkGroup{parent: node}
|
|
grp.id = toParameter(tokens)
|
|
|
|
node.groups = append(node.groups, grp)
|
|
node = grp
|
|
|
|
tokens = []string{}
|
|
|
|
case "}":
|
|
// Otherwise if it's a closing brace, then we need to pop back up to
|
|
// the parent node and resume parsing. If we have any tokens, then
|
|
// that was because they were unterminated. Raise an error in that case.
|
|
|
|
if node.parent == nil {
|
|
return tkGroup{}, fmt.Errorf("Refusing to close the global declaration")
|
|
}
|
|
if len(tokens) > 0 {
|
|
return tkGroup{}, fmt.Errorf("List of tokens was left unterminated : %v", tokens)
|
|
}
|
|
node = node.parent
|
|
|
|
tokens = []string{}
|
|
|
|
case ";":
|
|
// If we encounter a line-terminator, then the list of tokens we've been
|
|
// aggregating are ready to be parsed. Afterwards, we can write them
|
|
// to our current tree node.
|
|
|
|
arg := toParameter(tokens)
|
|
node.params = append(node.params, arg)
|
|
tokens = []string{}
|
|
|
|
default:
|
|
// Anything else requires us to aggregate our token into our list, and
|
|
// try grabbing the next one.
|
|
|
|
tokens = append(tokens, tk)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func tokenizeNetworkMapConfig(in chan byte) chan string {
|
|
var state string
|
|
var quote bool
|
|
var lastnewline bool
|
|
|
|
// This logic is very similar to tokenizeDhcpConfig except she needs to handle
|
|
// braces, and we don't. This is the only major difference from us.
|
|
|
|
out := make(chan string)
|
|
go func(out chan string) {
|
|
for {
|
|
by, ok := <-in
|
|
if !ok {
|
|
break
|
|
}
|
|
|
|
// If we're currently inide a quote, then we need to continue until
|
|
// we encounter the closing quote. We'll keep collecting our state
|
|
// in the meantime.
|
|
if quote {
|
|
if by == '"' {
|
|
out <- state + string(by)
|
|
state, quote = "", false
|
|
continue
|
|
}
|
|
state += string(by)
|
|
continue
|
|
}
|
|
|
|
switch by {
|
|
case '"':
|
|
// If we encounter a quote, then we need to transition into our
|
|
// quote-parsing state that keeps collecting data until the closing
|
|
// quote is encountered.
|
|
|
|
quote = true
|
|
state += string(by)
|
|
continue
|
|
|
|
case '\r':
|
|
fallthrough
|
|
case '\t':
|
|
fallthrough
|
|
case ' ':
|
|
// Whitespace is considered a separator, so if we encounter this
|
|
// then we can write our current state, and then reset.
|
|
|
|
if len(state) == 0 {
|
|
continue
|
|
}
|
|
out <- state
|
|
state = ""
|
|
|
|
case '\n':
|
|
// Newlines are a somewhat special case because they separate each
|
|
// attribute/line-item, and they can repeat. We need to preserve
|
|
// this token, so we write our current state, then the newline.
|
|
// We also maintain a flag so that we can consolidate multiple
|
|
// newlines together.
|
|
|
|
if lastnewline {
|
|
continue
|
|
}
|
|
if len(state) > 0 {
|
|
out <- state
|
|
}
|
|
out <- string(by)
|
|
state = ""
|
|
lastnewline = true
|
|
continue
|
|
|
|
case '.':
|
|
fallthrough
|
|
case '=':
|
|
// These characters separate attributes or tokens from one another,
|
|
// so they result in writing the state, the character, and then reset.
|
|
|
|
if len(state) > 0 {
|
|
out <- state
|
|
}
|
|
out <- string(by)
|
|
state = ""
|
|
|
|
default:
|
|
// Any byte we couldn't parse just gets aggregated for the next pass.
|
|
state += string(by)
|
|
}
|
|
|
|
// If we made it here, then we can guarantee that the we didn't just
|
|
// process a newline. Clear this flag for the next one we find.
|
|
lastnewline = false
|
|
}
|
|
|
|
// If there's anything left in our state, then the last line was just not
|
|
// newline-terminated. This is a common occurrence, so write our current
|
|
// state before we finish.
|
|
if len(state) > 0 {
|
|
out <- state
|
|
}
|
|
close(out)
|
|
}(out)
|
|
return out
|
|
}
|
|
|
|
func parseNetworkMapConfig(in chan string) (NetworkMap, error) {
|
|
var state []string
|
|
unsorted := make(map[string]map[string]string)
|
|
|
|
// A network map has the following syntax "network.attribute = value". This
|
|
// closure is responsible for using the "network" as a key into the `unsorted`
|
|
// mapping, and then assigning the "value" into it keyed by the "attribute".
|
|
addResult := func(network string, attribute string, value string) error {
|
|
_, ok := unsorted[network]
|
|
if !ok {
|
|
unsorted[network] = make(map[string]string)
|
|
}
|
|
|
|
val, err := strconv.Unquote(value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
current := unsorted[network]
|
|
current[attribute] = val
|
|
return nil
|
|
}
|
|
|
|
// Loop through all of our tokens making sure to update our unsorted map.
|
|
for {
|
|
tk, ok := <-in
|
|
if !ok {
|
|
// If our token channel is closed, then check to see if we've
|
|
// collected 3 items in our state. If so, then we can add this
|
|
// final attribute/value before we leave.
|
|
if len(state) == 3 {
|
|
err := addResult(state[0], state[1], state[2])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
break
|
|
}
|
|
|
|
// This switch makes sure we encounter these tokens in the correct order.
|
|
switch tk {
|
|
case ".":
|
|
if len(state) != 1 {
|
|
return nil, fmt.Errorf("Missing network index")
|
|
}
|
|
|
|
case "=":
|
|
if len(state) != 2 {
|
|
return nil, fmt.Errorf("Assignment to empty attribute")
|
|
}
|
|
|
|
case "\n":
|
|
if len(state) == 0 {
|
|
continue
|
|
}
|
|
if len(state) != 3 {
|
|
return nil, fmt.Errorf("Invalid attribute assignment : %v", state)
|
|
}
|
|
err := addResult(state[0], state[1], state[2])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
state = make([]string, 0)
|
|
|
|
default:
|
|
state = append(state, tk)
|
|
}
|
|
}
|
|
|
|
// Go through our unsorted map, and collect all of the keys for "network".
|
|
result := make([]map[string]string, 0)
|
|
var keys []string
|
|
for k := range unsorted {
|
|
keys = append(keys, k)
|
|
}
|
|
|
|
// This way we can sort them.
|
|
sort.Strings(keys)
|
|
|
|
// And then collect all of them into a list to return to the caller.
|
|
for _, k := range keys {
|
|
result = append(result, unsorted[k])
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
/** higher-level parsing */
|
|
/// parameters
|
|
type pParameter interface {
|
|
repr() string
|
|
}
|
|
|
|
type pParameterInclude struct {
|
|
filename string
|
|
}
|
|
|
|
func (e pParameterInclude) repr() string { return fmt.Sprintf("include-file:filename=%s", e.filename) }
|
|
|
|
type pParameterOption struct {
|
|
name string
|
|
value string
|
|
}
|
|
|
|
func (e pParameterOption) repr() string { return fmt.Sprintf("option:%s=%s", e.name, e.value) }
|
|
|
|
// allow some-kind-of-something
|
|
type pParameterGrant struct {
|
|
verb string // allow,deny,ignore
|
|
attribute string
|
|
}
|
|
|
|
func (e pParameterGrant) repr() string { return fmt.Sprintf("grant:%s,%s", e.verb, e.attribute) }
|
|
|
|
type pParameterAddress4 []string
|
|
|
|
func (e pParameterAddress4) repr() string {
|
|
return fmt.Sprintf("fixed-address4:%s", strings.Join(e, ","))
|
|
}
|
|
|
|
type pParameterAddress6 []string
|
|
|
|
func (e pParameterAddress6) repr() string {
|
|
return fmt.Sprintf("fixed-address6:%s", strings.Join(e, ","))
|
|
}
|
|
|
|
// hardware address 00:00:00:00:00:00
|
|
type pParameterHardware struct {
|
|
class string
|
|
address []byte
|
|
}
|
|
|
|
func (e pParameterHardware) repr() string {
|
|
res := make([]string, 0)
|
|
|
|
for _, v := range e.address {
|
|
res = append(res, fmt.Sprintf("%02x", v))
|
|
}
|
|
|
|
return fmt.Sprintf("hardware-address:%s[%s]", e.class, strings.Join(res, ":"))
|
|
}
|
|
|
|
type pParameterBoolean struct {
|
|
parameter string
|
|
truancy bool
|
|
}
|
|
|
|
func (e pParameterBoolean) repr() string { return fmt.Sprintf("boolean:%s=%v", e.parameter, e.truancy) }
|
|
|
|
type pParameterClientMatch struct {
|
|
name string
|
|
data string
|
|
}
|
|
|
|
func (e pParameterClientMatch) repr() string {
|
|
return fmt.Sprintf("match-client:%s=%s", e.name, e.data)
|
|
}
|
|
|
|
// range 127.0.0.1 127.0.0.255
|
|
type pParameterRange4 struct {
|
|
min net.IP
|
|
max net.IP
|
|
}
|
|
|
|
func (e pParameterRange4) repr() string {
|
|
return fmt.Sprintf("range4:%s-%s", e.min.String(), e.max.String())
|
|
}
|
|
|
|
type pParameterRange6 struct {
|
|
min net.IP
|
|
max net.IP
|
|
}
|
|
|
|
func (e pParameterRange6) repr() string {
|
|
return fmt.Sprintf("range6:%s-%s", e.min.String(), e.max.String())
|
|
}
|
|
|
|
type pParameterPrefix6 struct {
|
|
min net.IP
|
|
max net.IP
|
|
bits int
|
|
}
|
|
|
|
func (e pParameterPrefix6) repr() string {
|
|
return fmt.Sprintf("prefix6:/%d:%s-%s", e.bits, e.min.String(), e.max.String())
|
|
}
|
|
|
|
// some-kind-of-parameter 1024
|
|
type pParameterOther struct {
|
|
parameter string
|
|
value string
|
|
}
|
|
|
|
func (e pParameterOther) repr() string { return fmt.Sprintf("parameter:%s=%s", e.parameter, e.value) }
|
|
|
|
type pParameterExpression struct {
|
|
parameter string
|
|
expression string
|
|
}
|
|
|
|
func (e pParameterExpression) repr() string {
|
|
return fmt.Sprintf("parameter-expression:%s=\"%s\"", e.parameter, e.expression)
|
|
}
|
|
|
|
type pDeclarationIdentifier interface {
|
|
repr() string
|
|
}
|
|
|
|
type pDeclaration struct {
|
|
id pDeclarationIdentifier
|
|
parent *pDeclaration
|
|
parameters []pParameter
|
|
declarations []pDeclaration
|
|
}
|
|
|
|
func (e *pDeclaration) short() string {
|
|
return e.id.repr()
|
|
}
|
|
|
|
func (e *pDeclaration) repr() string {
|
|
res := e.short()
|
|
|
|
var parameters []string
|
|
for _, v := range e.parameters {
|
|
parameters = append(parameters, v.repr())
|
|
}
|
|
|
|
var groups []string
|
|
for _, v := range e.declarations {
|
|
groups = append(groups, fmt.Sprintf("-> %s", v.short()))
|
|
}
|
|
|
|
if e.parent != nil {
|
|
res = fmt.Sprintf("%s parent:%s", res, e.parent.short())
|
|
}
|
|
|
|
return fmt.Sprintf("%s\n%s\n%s\n", res, strings.Join(parameters, "\n"), strings.Join(groups, "\n"))
|
|
}
|
|
|
|
type pDeclarationGlobal struct{}
|
|
|
|
func (e pDeclarationGlobal) repr() string { return fmt.Sprintf("{global}") }
|
|
|
|
type pDeclarationShared struct{ name string }
|
|
|
|
func (e pDeclarationShared) repr() string { return fmt.Sprintf("{shared-network %s}", e.name) }
|
|
|
|
type pDeclarationSubnet4 struct{ net.IPNet }
|
|
|
|
func (e pDeclarationSubnet4) repr() string { return fmt.Sprintf("{subnet4 %s}", e.String()) }
|
|
|
|
type pDeclarationSubnet6 struct{ net.IPNet }
|
|
|
|
func (e pDeclarationSubnet6) repr() string { return fmt.Sprintf("{subnet6 %s}", e.String()) }
|
|
|
|
type pDeclarationHost struct{ name string }
|
|
|
|
func (e pDeclarationHost) repr() string { return fmt.Sprintf("{host name:%s}", e.name) }
|
|
|
|
type pDeclarationPool struct{}
|
|
|
|
func (e pDeclarationPool) repr() string { return fmt.Sprintf("{pool}") }
|
|
|
|
type pDeclarationGroup struct{}
|
|
|
|
func (e pDeclarationGroup) repr() string { return fmt.Sprintf("{group}") }
|
|
|
|
type pDeclarationClass struct{ name string }
|
|
|
|
func (e pDeclarationClass) repr() string { return fmt.Sprintf("{class}") }
|
|
|
|
/** parsers */
|
|
func parseParameter(val tkParameter) (pParameter, error) {
|
|
switch val.name {
|
|
case "include":
|
|
if len(val.operand) != 2 {
|
|
return nil, fmt.Errorf("Invalid number of parameters for pParameterInclude : %v", val.operand)
|
|
}
|
|
|
|
name := val.operand[0]
|
|
return pParameterInclude{filename: name}, nil
|
|
|
|
case "option":
|
|
if len(val.operand) != 2 {
|
|
return nil, fmt.Errorf("Invalid number of parameters for pParameterOption : %v", val.operand)
|
|
}
|
|
|
|
name, value := val.operand[0], val.operand[1]
|
|
return pParameterOption{name: name, value: value}, nil
|
|
|
|
case "allow":
|
|
fallthrough
|
|
case "deny":
|
|
fallthrough
|
|
case "ignore":
|
|
if len(val.operand) < 1 {
|
|
return nil, fmt.Errorf("Invalid number of parameters for pParameterGrant : %v", val.operand)
|
|
}
|
|
|
|
attribute := strings.Join(val.operand, " ")
|
|
return pParameterGrant{verb: strings.ToLower(val.name), attribute: attribute}, nil
|
|
|
|
case "range":
|
|
if len(val.operand) < 1 {
|
|
return nil, fmt.Errorf("Invalid number of parameters for pParameterRange4 : %v", val.operand)
|
|
}
|
|
|
|
idxAddress := map[bool]int{true: 1, false: 0}[strings.ToLower(val.operand[0]) == "bootp"]
|
|
if len(val.operand) > 2+idxAddress {
|
|
return nil, fmt.Errorf("Invalid number of parameters for pParameterRange : %v", val.operand)
|
|
}
|
|
|
|
if idxAddress+1 > len(val.operand) {
|
|
res := net.ParseIP(val.operand[idxAddress])
|
|
return pParameterRange4{min: res, max: res}, nil
|
|
}
|
|
|
|
addr1 := net.ParseIP(val.operand[idxAddress])
|
|
addr2 := net.ParseIP(val.operand[idxAddress+1])
|
|
return pParameterRange4{min: addr1, max: addr2}, nil
|
|
|
|
case "range6":
|
|
if len(val.operand) == 1 {
|
|
address := val.operand[0]
|
|
if strings.Contains(address, "/") {
|
|
cidr := strings.SplitN(address, "/", 2)
|
|
if len(cidr) != 2 {
|
|
return nil, fmt.Errorf("Unknown ipv6 format : %v", address)
|
|
}
|
|
|
|
address := net.ParseIP(cidr[0])
|
|
|
|
bits, err := strconv.Atoi(cidr[1])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mask := net.CIDRMask(bits, net.IPv6len*8)
|
|
|
|
// figure out the network address
|
|
network := address.Mask(mask)
|
|
|
|
// make a broadcast address
|
|
broadcast := network
|
|
networkSize, totalSize := mask.Size()
|
|
hostSize := totalSize - networkSize
|
|
for i := networkSize / 8; i < totalSize/8; i++ {
|
|
broadcast[i] = byte(0xff)
|
|
}
|
|
|
|
octetIndex := network[networkSize/8]
|
|
bitsLeft := (uint32)(hostSize % 8)
|
|
broadcast[octetIndex] = network[octetIndex] | ((1 << bitsLeft) - 1)
|
|
|
|
// FIXME: check that the broadcast address was made correctly
|
|
return pParameterRange6{min: network, max: broadcast}, nil
|
|
}
|
|
res := net.ParseIP(address)
|
|
return pParameterRange6{min: res, max: res}, nil
|
|
}
|
|
|
|
if len(val.operand) == 2 {
|
|
addr := net.ParseIP(val.operand[0])
|
|
if strings.ToLower(val.operand[1]) == "temporary" {
|
|
return pParameterRange6{min: addr, max: addr}, nil
|
|
}
|
|
|
|
other := net.ParseIP(val.operand[1])
|
|
return pParameterRange6{min: addr, max: other}, nil
|
|
}
|
|
return nil, fmt.Errorf("Invalid number of parameters for pParameterRange6 : %v", val.operand)
|
|
|
|
case "prefix6":
|
|
if len(val.operand) != 3 {
|
|
return nil, fmt.Errorf("Invalid number of parameters for pParameterRange6 : %v", val.operand)
|
|
}
|
|
|
|
bits, err := strconv.Atoi(val.operand[2])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Invalid bits for pParameterPrefix6 : %v", val.operand[2])
|
|
}
|
|
|
|
minaddr := net.ParseIP(val.operand[0])
|
|
maxaddr := net.ParseIP(val.operand[1])
|
|
return pParameterPrefix6{min: minaddr, max: maxaddr, bits: bits}, nil
|
|
|
|
case "hardware":
|
|
if len(val.operand) != 2 {
|
|
return nil, fmt.Errorf("Invalid number of parameters for pParameterHardware : %v", val.operand)
|
|
}
|
|
|
|
class := val.operand[0]
|
|
octets := strings.Split(val.operand[1], ":")
|
|
address := make([]byte, 0)
|
|
for _, v := range octets {
|
|
b, err := strconv.ParseInt(v, 16, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
address = append(address, byte(b))
|
|
}
|
|
|
|
return pParameterHardware{class: class, address: address}, nil
|
|
|
|
case "fixed-address":
|
|
ip4addrs := make(pParameterAddress4, len(val.operand))
|
|
copy(ip4addrs, val.operand)
|
|
return ip4addrs, nil
|
|
|
|
case "fixed-address6":
|
|
ip6addrs := make(pParameterAddress6, len(val.operand))
|
|
copy(ip6addrs, val.operand)
|
|
return ip6addrs, nil
|
|
|
|
case "host-identifier":
|
|
if len(val.operand) != 3 {
|
|
return nil, fmt.Errorf("Invalid number of parameters for pParameterClientMatch : %v", val.operand)
|
|
}
|
|
|
|
if val.operand[0] != "option" {
|
|
return nil, fmt.Errorf("Invalid match parameter : %v", val.operand[0])
|
|
}
|
|
|
|
optionName := val.operand[1]
|
|
optionData := val.operand[2]
|
|
return pParameterClientMatch{name: optionName, data: optionData}, nil
|
|
|
|
default:
|
|
length := len(val.operand)
|
|
|
|
if length < 1 {
|
|
return pParameterBoolean{parameter: val.name, truancy: true}, nil
|
|
|
|
} else if length > 1 {
|
|
if val.operand[0] == "=" {
|
|
return pParameterExpression{parameter: val.name, expression: strings.Join(val.operand[1:], "")}, nil
|
|
}
|
|
}
|
|
|
|
if length != 1 {
|
|
return nil, fmt.Errorf("Invalid number of parameters for pParameterOther : %v", val.operand)
|
|
}
|
|
|
|
if strings.ToLower(val.name) == "not" {
|
|
return pParameterBoolean{parameter: val.operand[0], truancy: false}, nil
|
|
}
|
|
|
|
return pParameterOther{parameter: val.name, value: val.operand[0]}, nil
|
|
}
|
|
}
|
|
|
|
func parseTokenGroup(val tkGroup) (*pDeclaration, error) {
|
|
params := val.id.operand
|
|
|
|
switch val.id.name {
|
|
case "group":
|
|
return &pDeclaration{id: pDeclarationGroup{}}, nil
|
|
|
|
case "pool":
|
|
return &pDeclaration{id: pDeclarationPool{}}, nil
|
|
|
|
case "host":
|
|
if len(params) == 1 {
|
|
return &pDeclaration{id: pDeclarationHost{name: params[0]}}, nil
|
|
}
|
|
|
|
case "subnet":
|
|
if len(params) == 3 && strings.ToLower(params[1]) == "netmask" {
|
|
addr := make([]byte, 4)
|
|
for i, v := range strings.SplitN(params[2], ".", 4) {
|
|
res, err := strconv.ParseInt(v, 10, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
addr[i] = byte(res)
|
|
}
|
|
oc1, oc2, oc3, oc4 := addr[0], addr[1], addr[2], addr[3]
|
|
if subnet, mask := net.ParseIP(params[0]), net.IPv4Mask(oc1, oc2, oc3, oc4); subnet != nil && mask != nil {
|
|
return &pDeclaration{id: pDeclarationSubnet4{net.IPNet{IP: subnet, Mask: mask}}}, nil
|
|
}
|
|
}
|
|
|
|
case "subnet6":
|
|
if len(params) == 1 {
|
|
ip6 := strings.SplitN(params[0], "/", 2)
|
|
if len(ip6) == 2 && strings.Contains(ip6[0], ":") {
|
|
address := net.ParseIP(ip6[0])
|
|
prefix, err := strconv.Atoi(ip6[1])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &pDeclaration{id: pDeclarationSubnet6{net.IPNet{IP: address, Mask: net.CIDRMask(prefix, net.IPv6len*8)}}}, nil
|
|
}
|
|
}
|
|
|
|
case "shared-network":
|
|
if len(params) == 1 {
|
|
return &pDeclaration{id: pDeclarationShared{name: params[0]}}, nil
|
|
}
|
|
|
|
case "":
|
|
return &pDeclaration{id: pDeclarationGlobal{}}, nil
|
|
}
|
|
return nil, fmt.Errorf("Invalid pDeclaration : %v : %v", val.id.name, params)
|
|
}
|
|
|
|
func flattenDhcpConfig(root tkGroup) (*pDeclaration, error) {
|
|
result, err := parseTokenGroup(root)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, p := range root.params {
|
|
param, err := parseParameter(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result.parameters = append(result.parameters, param)
|
|
}
|
|
|
|
for _, p := range root.groups {
|
|
group, err := flattenDhcpConfig(*p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
group.parent = result
|
|
result.declarations = append(result.declarations, *group)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
/** reduce the tree into the things that we care about */
|
|
type grant uint
|
|
|
|
const (
|
|
ALLOW grant = iota
|
|
IGNORE grant = iota
|
|
DENY grant = iota
|
|
)
|
|
|
|
type configDeclaration struct {
|
|
id []pDeclarationIdentifier
|
|
composites []pDeclaration
|
|
|
|
address []pParameter
|
|
|
|
options map[string]string
|
|
grants map[string]grant
|
|
attributes map[string]bool
|
|
parameters map[string]string
|
|
expressions map[string]string
|
|
|
|
hostid []pParameterClientMatch
|
|
}
|
|
|
|
func createDeclaration(node pDeclaration) configDeclaration {
|
|
var hierarchy []pDeclaration
|
|
|
|
for n := &node; n != nil; n = n.parent {
|
|
hierarchy = append(hierarchy, *n)
|
|
}
|
|
|
|
var result configDeclaration
|
|
result.address = make([]pParameter, 0)
|
|
|
|
result.options = make(map[string]string)
|
|
result.grants = make(map[string]grant)
|
|
result.attributes = make(map[string]bool)
|
|
result.parameters = make(map[string]string)
|
|
result.expressions = make(map[string]string)
|
|
|
|
result.hostid = make([]pParameterClientMatch, 0)
|
|
|
|
// walk from globals to pDeclaration collecting all parameters
|
|
for i := len(hierarchy) - 1; i >= 0; i-- {
|
|
result.composites = append(result.composites, hierarchy[(len(hierarchy)-1)-i])
|
|
result.id = append(result.id, hierarchy[(len(hierarchy)-1)-i].id)
|
|
|
|
// update configDeclaration parameters
|
|
for _, p := range hierarchy[i].parameters {
|
|
switch p.(type) {
|
|
case pParameterOption:
|
|
result.options[p.(pParameterOption).name] = p.(pParameterOption).value
|
|
case pParameterGrant:
|
|
Grant := map[string]grant{"ignore": IGNORE, "allow": ALLOW, "deny": DENY}
|
|
result.grants[p.(pParameterGrant).attribute] = Grant[p.(pParameterGrant).verb]
|
|
case pParameterBoolean:
|
|
result.attributes[p.(pParameterBoolean).parameter] = p.(pParameterBoolean).truancy
|
|
case pParameterClientMatch:
|
|
result.hostid = append(result.hostid, p.(pParameterClientMatch))
|
|
case pParameterExpression:
|
|
result.expressions[p.(pParameterExpression).parameter] = p.(pParameterExpression).expression
|
|
case pParameterOther:
|
|
result.parameters[p.(pParameterOther).parameter] = p.(pParameterOther).value
|
|
default:
|
|
result.address = append(result.address, p)
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (e *configDeclaration) repr() string {
|
|
var result []string
|
|
|
|
res := make([]string, 0)
|
|
for _, v := range e.id {
|
|
res = append(res, v.repr())
|
|
}
|
|
result = append(result, strings.Join(res, ","))
|
|
|
|
if len(e.address) > 0 {
|
|
res := make([]string, 0)
|
|
for _, v := range e.address {
|
|
res = append(res, v.repr())
|
|
}
|
|
result = append(result, fmt.Sprintf("address : %v", strings.Join(res, ",")))
|
|
}
|
|
|
|
if len(e.options) > 0 {
|
|
result = append(result, fmt.Sprintf("options : %v", e.options))
|
|
}
|
|
if len(e.grants) > 0 {
|
|
result = append(result, fmt.Sprintf("grants : %v", e.grants))
|
|
}
|
|
if len(e.attributes) > 0 {
|
|
result = append(result, fmt.Sprintf("attributes : %v", e.attributes))
|
|
}
|
|
if len(e.parameters) > 0 {
|
|
result = append(result, fmt.Sprintf("parameters : %v", e.parameters))
|
|
}
|
|
if len(e.expressions) > 0 {
|
|
result = append(result, fmt.Sprintf("parameter-expressions : %v", e.expressions))
|
|
}
|
|
|
|
if len(e.hostid) > 0 {
|
|
res := make([]string, 0)
|
|
for _, v := range e.hostid {
|
|
res = append(res, v.repr())
|
|
}
|
|
result = append(result, fmt.Sprintf("hostid : %v", strings.Join(res, " ")))
|
|
}
|
|
|
|
return strings.Join(result, "\n") + "\n"
|
|
}
|
|
|
|
func (e *configDeclaration) IP4() (net.IP, error) {
|
|
var result []string
|
|
|
|
for _, entry := range e.address {
|
|
switch entry.(type) {
|
|
case pParameterAddress4:
|
|
for _, s := range entry.(pParameterAddress4) {
|
|
result = append(result, s)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(result) > 1 {
|
|
return nil, fmt.Errorf("More than one address4 returned : %v", result)
|
|
|
|
} else if len(result) == 0 {
|
|
return nil, fmt.Errorf("No IP4 addresses found")
|
|
}
|
|
|
|
// Try and parse it as an IP4. If so, then it's good to return it as-is.
|
|
if res := net.ParseIP(result[0]); res != nil {
|
|
return res, nil
|
|
}
|
|
|
|
// Otherwise make an attempt to resolve it to an address.
|
|
res, err := net.ResolveIPAddr("ip4", result[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return res.IP, nil
|
|
}
|
|
|
|
func (e *configDeclaration) IP6() (net.IP, error) {
|
|
var result []string
|
|
|
|
for _, entry := range e.address {
|
|
switch entry.(type) {
|
|
case pParameterAddress6:
|
|
for _, s := range entry.(pParameterAddress6) {
|
|
result = append(result, s)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(result) > 1 {
|
|
return nil, fmt.Errorf("More than one address6 returned : %v", result)
|
|
|
|
} else if len(result) == 0 {
|
|
return nil, fmt.Errorf("No IP6 addresses found")
|
|
}
|
|
|
|
// If we were able to parse it into an IP, then we can just return it.
|
|
if res := net.ParseIP(result[0]); res != nil {
|
|
return res, nil
|
|
}
|
|
|
|
// Otherwise, try to resolve it into an address.
|
|
res, err := net.ResolveIPAddr("ip6", result[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return res.IP, nil
|
|
}
|
|
|
|
func (e *configDeclaration) Hardware() (net.HardwareAddr, error) {
|
|
var result []pParameterHardware
|
|
|
|
for _, addr := range e.address {
|
|
switch addr.(type) {
|
|
case pParameterHardware:
|
|
result = append(result, addr.(pParameterHardware))
|
|
}
|
|
}
|
|
|
|
if len(result) > 0 {
|
|
return nil, fmt.Errorf("More than one hardware address returned : %v", result)
|
|
}
|
|
|
|
res := make(net.HardwareAddr, 0)
|
|
for _, by := range result[0].address {
|
|
res = append(res, by)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
/*** Dhcp Configuration */
|
|
type DhcpConfiguration []configDeclaration
|
|
|
|
func ReadDhcpConfiguration(fd *os.File) (DhcpConfiguration, error) {
|
|
fromfile := consumeFile(fd)
|
|
uncommented := uncomment(fromfile)
|
|
tokenized := tokenizeDhcpConfig(uncommented)
|
|
|
|
// Parse the tokenized DHCP configuration into a tree. We need it as a tree
|
|
// because DHCP declarations can inherit options from their parent
|
|
parsetree, err := parseDhcpConfig(tokenized)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Flatten the tree into a list of pDeclaration objects. This is responsible
|
|
// for actually propagating options from the parent pDeclaration into all of
|
|
// its children.
|
|
global, err := flattenDhcpConfig(parsetree)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// This closure is just to the goro that follows it in recursively walking
|
|
// through all of the declarations and writing them individually to a chan.
|
|
var walkDeclarations func(root pDeclaration, out chan *configDeclaration)
|
|
|
|
walkDeclarations = func(root pDeclaration, out chan *configDeclaration) {
|
|
res := createDeclaration(root)
|
|
out <- &res
|
|
for _, p := range root.declarations {
|
|
walkDeclarations(p, out)
|
|
}
|
|
}
|
|
|
|
// That way this goro can take each individual declaration and write it to
|
|
// a channel.
|
|
each := make(chan *configDeclaration)
|
|
go func(out chan *configDeclaration) {
|
|
walkDeclarations(*global, out)
|
|
out <- nil
|
|
}(each)
|
|
|
|
// For this loop to convert it into a itemized list.
|
|
var result DhcpConfiguration
|
|
for decl := <-each; decl != nil; decl = <-each {
|
|
result = append(result, *decl)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (e *DhcpConfiguration) Global() configDeclaration {
|
|
result := (*e)[0]
|
|
if len(result.id) != 1 {
|
|
panic(fmt.Errorf("Something that can't happen happened"))
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (e *DhcpConfiguration) SubnetByAddress(address net.IP) (configDeclaration, error) {
|
|
var result []configDeclaration
|
|
for _, entry := range *e {
|
|
switch entry.id[0].(type) {
|
|
case pDeclarationSubnet4:
|
|
id := entry.id[0].(pDeclarationSubnet4)
|
|
if id.Contains(address) {
|
|
result = append(result, entry)
|
|
}
|
|
case pDeclarationSubnet6:
|
|
id := entry.id[0].(pDeclarationSubnet6)
|
|
if id.Contains(address) {
|
|
result = append(result, entry)
|
|
}
|
|
}
|
|
}
|
|
if len(result) == 0 {
|
|
return configDeclaration{}, fmt.Errorf("No network declarations containing %s found", address.String())
|
|
}
|
|
if len(result) > 1 {
|
|
return configDeclaration{}, fmt.Errorf("More than 1 network declaration found : %v", result)
|
|
}
|
|
return result[0], nil
|
|
}
|
|
|
|
func (e *DhcpConfiguration) HostByName(host string) (configDeclaration, error) {
|
|
var result []configDeclaration
|
|
for _, entry := range *e {
|
|
switch entry.id[0].(type) {
|
|
case pDeclarationHost:
|
|
id := entry.id[0].(pDeclarationHost)
|
|
if strings.ToLower(id.name) == strings.ToLower(host) {
|
|
result = append(result, entry)
|
|
}
|
|
}
|
|
}
|
|
if len(result) == 0 {
|
|
return configDeclaration{}, fmt.Errorf("No host declarations containing %s found", host)
|
|
}
|
|
if len(result) > 1 {
|
|
return configDeclaration{}, fmt.Errorf("More than 1 host declaration found : %v", result)
|
|
}
|
|
return result[0], nil
|
|
}
|
|
|
|
/*** Network Map */
|
|
type NetworkMap []map[string]string
|
|
|
|
type NetworkNameMapper interface {
|
|
NameIntoDevices(string) ([]string, error)
|
|
DeviceIntoName(string) (string, error)
|
|
}
|
|
|
|
func ReadNetworkMap(fd *os.File) (NetworkMap, error) {
|
|
fromfile := consumeFile(fd)
|
|
uncommented := uncomment(fromfile)
|
|
tokenized := tokenizeNetworkMapConfig(uncommented)
|
|
|
|
// Now that we've tokenized the network map, we just need to parse it into
|
|
// a list of maps.
|
|
result, err := parseNetworkMapConfig(tokenized)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (e NetworkMap) NameIntoDevices(name string) ([]string, error) {
|
|
var devices []string
|
|
|
|
for _, val := range e {
|
|
if strings.ToLower(val["name"]) == strings.ToLower(name) {
|
|
devices = append(devices, val["device"])
|
|
}
|
|
}
|
|
|
|
if len(devices) > 0 {
|
|
return devices, nil
|
|
}
|
|
|
|
return make([]string, 0), fmt.Errorf("Network name not found : %v", name)
|
|
}
|
|
func (e NetworkMap) DeviceIntoName(device string) (string, error) {
|
|
for _, val := range e {
|
|
if strings.ToLower(val["device"]) == strings.ToLower(device) {
|
|
return val["name"], nil
|
|
}
|
|
}
|
|
|
|
return "", fmt.Errorf("Device name not found : %v", device)
|
|
}
|
|
func (e *NetworkMap) repr() string {
|
|
var result []string
|
|
|
|
for idx, val := range *e {
|
|
result = append(result, fmt.Sprintf("network%d.name = \"%s\"", idx, val["name"]))
|
|
result = append(result, fmt.Sprintf("network%d.device = \"%s\"", idx, val["device"]))
|
|
}
|
|
|
|
return strings.Join(result, "\n")
|
|
}
|
|
|
|
/*** parser for VMware Fusion's networking file */
|
|
func tokenizeNetworkingConfig(in chan byte) chan string {
|
|
var state string
|
|
var repeat_newline bool
|
|
|
|
out := make(chan string)
|
|
go func(out chan string) {
|
|
for {
|
|
by, ok := <-in
|
|
if !ok {
|
|
break
|
|
}
|
|
|
|
switch by {
|
|
case '\t':
|
|
fallthrough
|
|
case ' ':
|
|
// If we receive whitespace, then this is just a write to our
|
|
// state and then we reset.
|
|
if len(state) == 0 {
|
|
continue
|
|
}
|
|
out <- state
|
|
state = ""
|
|
|
|
case '\r':
|
|
// If windows has tampered with our newlines, then we normalize
|
|
// things by converting its value from 0x0d to 0x0a.
|
|
fallthrough
|
|
case '\n':
|
|
// Newlines can repeat, so this case is responsible for writing
|
|
// to the chan, and consolidating multiple newlines into a single.
|
|
if repeat_newline {
|
|
continue
|
|
}
|
|
if len(state) > 0 {
|
|
out <- state
|
|
}
|
|
out <- "\n"
|
|
state = ""
|
|
repeat_newline = true
|
|
continue
|
|
|
|
default:
|
|
// Anything other bytes just need to be aggregated into a string.
|
|
state += string(by)
|
|
}
|
|
repeat_newline = false
|
|
}
|
|
|
|
// If there's anything left in our state after the chan has been closed,
|
|
// then the input just wasn't terminated properly. It's still valid, so
|
|
// write we have to the channel.
|
|
if len(state) > 0 {
|
|
out <- state
|
|
}
|
|
close(out)
|
|
}(out)
|
|
return out
|
|
}
|
|
|
|
func splitNetworkingConfig(in chan string) chan []string {
|
|
out := make(chan []string)
|
|
|
|
// This goroutine is simple in that it takes a chan of tokens, and splits
|
|
// them across the newlines.
|
|
|
|
go func(out chan []string) {
|
|
row := make([]string, 0)
|
|
for {
|
|
tk, ok := <-in
|
|
if !ok {
|
|
break
|
|
}
|
|
|
|
if tk == "\n" {
|
|
// If we received a newline token, then we need to write our
|
|
// aggregated list of tokens and reset our "splitting" state.
|
|
|
|
if len(row) > 0 {
|
|
out <- row
|
|
}
|
|
|
|
row = make([]string, 0)
|
|
|
|
} else {
|
|
// Anything else just requires us to aggregate the token into
|
|
// our list.
|
|
row = append(row, tk)
|
|
}
|
|
}
|
|
|
|
if len(row) > 0 {
|
|
out <- row
|
|
}
|
|
close(out)
|
|
}(out)
|
|
return out
|
|
}
|
|
|
|
/// All token types in networking file.
|
|
// VERSION token
|
|
type networkingVERSION struct {
|
|
value string
|
|
}
|
|
|
|
func networkingReadVersion(row []string) (*networkingVERSION, error) {
|
|
if len(row) != 1 {
|
|
return nil, fmt.Errorf("Unexpected format for VERSION entry : %v", row)
|
|
}
|
|
res := &networkingVERSION{value: row[0]}
|
|
if !res.Valid() {
|
|
return nil, fmt.Errorf("Unexpected format for VERSION entry : %v", row)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func (s networkingVERSION) Repr() string {
|
|
if !s.Valid() {
|
|
return fmt.Sprintf("VERSION{INVALID=\"%v\"}", s.value)
|
|
}
|
|
return fmt.Sprintf("VERSION{%f}", s.Number())
|
|
}
|
|
|
|
func (s networkingVERSION) Valid() bool {
|
|
tokens := strings.SplitN(s.value, "=", 2)
|
|
if len(tokens) != 2 || tokens[0] != "VERSION" {
|
|
return false
|
|
}
|
|
|
|
tokens = strings.Split(tokens[1], ",")
|
|
if len(tokens) != 2 {
|
|
return false
|
|
}
|
|
|
|
for _, t := range tokens {
|
|
_, err := strconv.ParseUint(t, 10, 64)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (s networkingVERSION) Number() float64 {
|
|
var result float64
|
|
tokens := strings.SplitN(s.value, "=", 2)
|
|
tokens = strings.Split(tokens[1], ",")
|
|
|
|
integer, err := strconv.ParseUint(tokens[0], 10, 64)
|
|
if err != nil {
|
|
integer = 0
|
|
}
|
|
result = float64(integer)
|
|
|
|
mantissa, err := strconv.ParseUint(tokens[1], 10, 64)
|
|
if err != nil {
|
|
return result
|
|
}
|
|
denomination := math.Pow(10.0, float64(len(tokens[1])))
|
|
return result + (float64(mantissa) / denomination)
|
|
}
|
|
|
|
// VNET_X token
|
|
type networkingVNET struct {
|
|
value string
|
|
}
|
|
|
|
func (s networkingVNET) Valid() bool {
|
|
if strings.ToUpper(s.value) != s.value {
|
|
return false
|
|
}
|
|
tokens := strings.SplitN(s.value, "_", 3)
|
|
if len(tokens) != 3 || tokens[0] != "VNET" {
|
|
return false
|
|
}
|
|
_, err := strconv.ParseUint(tokens[1], 10, 64)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (s networkingVNET) Number() int {
|
|
tokens := strings.SplitN(s.value, "_", 3)
|
|
res, err := strconv.Atoi(tokens[1])
|
|
if err != nil {
|
|
return ^int(0)
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (s networkingVNET) Option() string {
|
|
tokens := strings.SplitN(s.value, "_", 3)
|
|
if len(tokens) == 3 {
|
|
return tokens[2]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (s networkingVNET) Repr() string {
|
|
if !s.Valid() {
|
|
tokens := strings.SplitN(s.value, "_", 3)
|
|
return fmt.Sprintf("VNET{INVALID=%v}", tokens)
|
|
}
|
|
return fmt.Sprintf("VNET{%d} %s", s.Number(), s.Option())
|
|
}
|
|
|
|
// Interface name
|
|
type networkingInterface struct {
|
|
name string
|
|
}
|
|
|
|
func (s networkingInterface) Interface() (*net.Interface, error) {
|
|
return net.InterfaceByName(s.name)
|
|
}
|
|
|
|
// networking command entry types
|
|
type networkingCommandEntry_answer struct {
|
|
vnet networkingVNET
|
|
value string
|
|
}
|
|
type networkingCommandEntry_remove_answer struct {
|
|
vnet networkingVNET
|
|
}
|
|
type networkingCommandEntry_add_nat_portfwd struct {
|
|
vnet int
|
|
protocol string
|
|
port int
|
|
target_host net.IP
|
|
target_port int
|
|
}
|
|
type networkingCommandEntry_remove_nat_portfwd struct {
|
|
vnet int
|
|
protocol string
|
|
port int
|
|
}
|
|
type networkingCommandEntry_add_dhcp_mac_to_ip struct {
|
|
vnet int
|
|
mac net.HardwareAddr
|
|
ip net.IP
|
|
}
|
|
type networkingCommandEntry_remove_dhcp_mac_to_ip struct {
|
|
vnet int
|
|
mac net.HardwareAddr
|
|
}
|
|
type networkingCommandEntry_add_bridge_mapping struct {
|
|
intf networkingInterface
|
|
vnet int
|
|
}
|
|
type networkingCommandEntry_remove_bridge_mapping struct {
|
|
intf networkingInterface
|
|
}
|
|
type networkingCommandEntry_add_nat_prefix struct {
|
|
vnet int
|
|
prefix int
|
|
}
|
|
type networkingCommandEntry_remove_nat_prefix struct {
|
|
vnet int
|
|
prefix int
|
|
}
|
|
|
|
type networkingCommandEntry struct {
|
|
entry interface{}
|
|
answer *networkingCommandEntry_answer
|
|
remove_answer *networkingCommandEntry_remove_answer
|
|
add_nat_portfwd *networkingCommandEntry_add_nat_portfwd
|
|
remove_nat_portfwd *networkingCommandEntry_remove_nat_portfwd
|
|
add_dhcp_mac_to_ip *networkingCommandEntry_add_dhcp_mac_to_ip
|
|
remove_dhcp_mac_to_ip *networkingCommandEntry_remove_dhcp_mac_to_ip
|
|
add_bridge_mapping *networkingCommandEntry_add_bridge_mapping
|
|
remove_bridge_mapping *networkingCommandEntry_remove_bridge_mapping
|
|
add_nat_prefix *networkingCommandEntry_add_nat_prefix
|
|
remove_nat_prefix *networkingCommandEntry_remove_nat_prefix
|
|
}
|
|
|
|
func (e networkingCommandEntry) Name() string {
|
|
switch e.entry.(type) {
|
|
case networkingCommandEntry_answer:
|
|
return "answer"
|
|
case networkingCommandEntry_remove_answer:
|
|
return "remove_answer"
|
|
case networkingCommandEntry_add_nat_portfwd:
|
|
return "add_nat_portfwd"
|
|
case networkingCommandEntry_remove_nat_portfwd:
|
|
return "remove_nat_portfwd"
|
|
case networkingCommandEntry_add_dhcp_mac_to_ip:
|
|
return "add_dhcp_mac_to_ip"
|
|
case networkingCommandEntry_remove_dhcp_mac_to_ip:
|
|
return "remove_dhcp_mac_to_ip"
|
|
case networkingCommandEntry_add_bridge_mapping:
|
|
return "add_bridge_mapping"
|
|
case networkingCommandEntry_remove_bridge_mapping:
|
|
return "remove_bridge_mapping"
|
|
case networkingCommandEntry_add_nat_prefix:
|
|
return "add_nat_prefix"
|
|
case networkingCommandEntry_remove_nat_prefix:
|
|
return "remove_nat_prefix"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (e networkingCommandEntry) Entry() reflect.Value {
|
|
this := reflect.ValueOf(e)
|
|
switch e.entry.(type) {
|
|
case networkingCommandEntry_answer:
|
|
return reflect.Indirect(this.FieldByName("answer"))
|
|
case networkingCommandEntry_remove_answer:
|
|
return reflect.Indirect(this.FieldByName("remove_answer"))
|
|
case networkingCommandEntry_add_nat_portfwd:
|
|
return reflect.Indirect(this.FieldByName("add_nat_portfwd"))
|
|
case networkingCommandEntry_remove_nat_portfwd:
|
|
return reflect.Indirect(this.FieldByName("remove_nat_portfwd"))
|
|
case networkingCommandEntry_add_dhcp_mac_to_ip:
|
|
return reflect.Indirect(this.FieldByName("add_dhcp_mac_to_ip"))
|
|
case networkingCommandEntry_remove_dhcp_mac_to_ip:
|
|
return reflect.Indirect(this.FieldByName("remove_dhcp_mac_to_ip"))
|
|
case networkingCommandEntry_add_bridge_mapping:
|
|
return reflect.Indirect(this.FieldByName("add_bridge_mapping"))
|
|
case networkingCommandEntry_remove_bridge_mapping:
|
|
return reflect.Indirect(this.FieldByName("remove_bridge_mapping"))
|
|
case networkingCommandEntry_add_nat_prefix:
|
|
return reflect.Indirect(this.FieldByName("add_nat_prefix"))
|
|
case networkingCommandEntry_remove_nat_prefix:
|
|
return reflect.Indirect(this.FieldByName("remove_nat_prefix"))
|
|
}
|
|
return reflect.Value{}
|
|
}
|
|
|
|
func (e networkingCommandEntry) Repr() string {
|
|
result := make(map[string]interface{})
|
|
|
|
entryN, entry := e.Name(), e.Entry()
|
|
entryT := entry.Type()
|
|
for i := 0; i < entry.NumField(); i++ {
|
|
fld, fldT := entry.Field(i), entryT.Field(i)
|
|
result[fldT.Name] = fld
|
|
}
|
|
return fmt.Sprintf("%s -> %v", entryN, result)
|
|
}
|
|
|
|
// networking command entry parsers
|
|
func parseNetworkingCommand_answer(row []string) (*networkingCommandEntry, error) {
|
|
if len(row) != 2 {
|
|
return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 2, len(row))
|
|
}
|
|
|
|
vnet := networkingVNET{value: row[0]}
|
|
if !vnet.Valid() {
|
|
return nil, fmt.Errorf("Invalid format for VNET.")
|
|
}
|
|
|
|
result := networkingCommandEntry_answer{vnet: vnet, value: row[1]}
|
|
return &networkingCommandEntry{entry: result, answer: &result}, nil
|
|
}
|
|
func parseNetworkingCommand_remove_answer(row []string) (*networkingCommandEntry, error) {
|
|
if len(row) != 1 {
|
|
return nil, fmt.Errorf("Expected %d argument. Received %d.", 1, len(row))
|
|
}
|
|
vnet := networkingVNET{value: row[0]}
|
|
if !vnet.Valid() {
|
|
return nil, fmt.Errorf("Invalid format for VNET.")
|
|
}
|
|
|
|
result := networkingCommandEntry_remove_answer{vnet: vnet}
|
|
return &networkingCommandEntry{entry: result, remove_answer: &result}, nil
|
|
}
|
|
func parseNetworkingCommand_add_nat_portfwd(row []string) (*networkingCommandEntry, error) {
|
|
if len(row) != 5 {
|
|
return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 5, len(row))
|
|
}
|
|
|
|
vnet, err := strconv.Atoi(row[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0])
|
|
}
|
|
|
|
protocol := strings.ToLower(row[1])
|
|
if !(protocol == "tcp" || protocol == "udp") {
|
|
return nil, fmt.Errorf("Expected \"tcp\" or \"udp\" for second argument. : %v", row[1])
|
|
}
|
|
|
|
sport, err := strconv.Atoi(row[2])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Unable to parse third argument as an integer. : %v", row[2])
|
|
}
|
|
|
|
dest := net.ParseIP(row[3])
|
|
if dest == nil {
|
|
return nil, fmt.Errorf("Unable to parse fourth argument as an IPv4 address. : %v", row[2])
|
|
}
|
|
|
|
dport, err := strconv.Atoi(row[4])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Unable to parse fifth argument as an integer. : %v", row[4])
|
|
}
|
|
|
|
result := networkingCommandEntry_add_nat_portfwd{vnet: vnet - 1, protocol: protocol, port: sport, target_host: dest, target_port: dport}
|
|
return &networkingCommandEntry{entry: result, add_nat_portfwd: &result}, nil
|
|
}
|
|
func parseNetworkingCommand_remove_nat_portfwd(row []string) (*networkingCommandEntry, error) {
|
|
if len(row) != 3 {
|
|
return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 3, len(row))
|
|
}
|
|
|
|
vnet, err := strconv.Atoi(row[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0])
|
|
}
|
|
|
|
protocol := strings.ToLower(row[1])
|
|
if !(protocol == "tcp" || protocol == "udp") {
|
|
return nil, fmt.Errorf("Expected \"tcp\" or \"udp\" for second argument. : %v", row[1])
|
|
}
|
|
|
|
sport, err := strconv.Atoi(row[2])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Unable to parse third argument as an integer. : %v", row[2])
|
|
}
|
|
|
|
result := networkingCommandEntry_remove_nat_portfwd{vnet: vnet - 1, protocol: protocol, port: sport}
|
|
return &networkingCommandEntry{entry: result, remove_nat_portfwd: &result}, nil
|
|
}
|
|
func parseNetworkingCommand_add_dhcp_mac_to_ip(row []string) (*networkingCommandEntry, error) {
|
|
if len(row) != 3 {
|
|
return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 3, len(row))
|
|
}
|
|
|
|
vnet, err := strconv.Atoi(row[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0])
|
|
}
|
|
|
|
mac, err := net.ParseMAC(row[1])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Unable to parse second argument as hwaddr. : %v", row[1])
|
|
}
|
|
|
|
ip := net.ParseIP(row[2])
|
|
if ip == nil {
|
|
return nil, fmt.Errorf("Unable to parse third argument as ipv4. : %v", row[2])
|
|
}
|
|
|
|
result := networkingCommandEntry_add_dhcp_mac_to_ip{vnet: vnet - 1, mac: mac, ip: ip}
|
|
return &networkingCommandEntry{entry: result, add_dhcp_mac_to_ip: &result}, nil
|
|
}
|
|
func parseNetworkingCommand_remove_dhcp_mac_to_ip(row []string) (*networkingCommandEntry, error) {
|
|
if len(row) != 2 {
|
|
return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 2, len(row))
|
|
}
|
|
|
|
vnet, err := strconv.Atoi(row[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0])
|
|
}
|
|
|
|
mac, err := net.ParseMAC(row[1])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Unable to parse second argument as hwaddr. : %v", row[1])
|
|
}
|
|
|
|
result := networkingCommandEntry_remove_dhcp_mac_to_ip{vnet: vnet - 1, mac: mac}
|
|
return &networkingCommandEntry{entry: result, remove_dhcp_mac_to_ip: &result}, nil
|
|
}
|
|
func parseNetworkingCommand_add_bridge_mapping(row []string) (*networkingCommandEntry, error) {
|
|
if len(row) != 2 {
|
|
return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 2, len(row))
|
|
}
|
|
intf := networkingInterface{name: row[0]}
|
|
|
|
vnet, err := strconv.Atoi(row[1])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Unable to parse second argument as an integer. : %v", row[2])
|
|
}
|
|
|
|
result := networkingCommandEntry_add_bridge_mapping{intf: intf, vnet: vnet - 1}
|
|
return &networkingCommandEntry{entry: result, add_bridge_mapping: &result}, nil
|
|
}
|
|
func parseNetworkingCommand_remove_bridge_mapping(row []string) (*networkingCommandEntry, error) {
|
|
if len(row) != 1 {
|
|
return nil, fmt.Errorf("Expected %d argument. Received %d.", 1, len(row))
|
|
}
|
|
intf := networkingInterface{name: row[0]}
|
|
/*
|
|
number, err := strconv.Atoi(row[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0])
|
|
}
|
|
*/
|
|
result := networkingCommandEntry_remove_bridge_mapping{intf: intf}
|
|
return &networkingCommandEntry{entry: result, remove_bridge_mapping: &result}, nil
|
|
}
|
|
func parseNetworkingCommand_add_nat_prefix(row []string) (*networkingCommandEntry, error) {
|
|
if len(row) != 2 {
|
|
return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 2, len(row))
|
|
}
|
|
|
|
vnet, err := strconv.Atoi(row[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0])
|
|
}
|
|
|
|
if !strings.HasPrefix(row[1], "/") {
|
|
return nil, fmt.Errorf("Expected second argument to begin with \"/\". : %v", row[1])
|
|
}
|
|
|
|
prefix, err := strconv.Atoi(row[1][1:])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Unable to parse prefix out of second argument. : %v", row[1])
|
|
}
|
|
|
|
result := networkingCommandEntry_add_nat_prefix{vnet: vnet - 1, prefix: prefix}
|
|
return &networkingCommandEntry{entry: result, add_nat_prefix: &result}, nil
|
|
}
|
|
func parseNetworkingCommand_remove_nat_prefix(row []string) (*networkingCommandEntry, error) {
|
|
if len(row) != 2 {
|
|
return nil, fmt.Errorf("Expected %d arguments. Received only %d.", 2, len(row))
|
|
}
|
|
|
|
vnet, err := strconv.Atoi(row[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Unable to parse first argument as an integer. : %v", row[0])
|
|
}
|
|
|
|
if !strings.HasPrefix(row[1], "/") {
|
|
return nil, fmt.Errorf("Expected second argument to begin with \"/\". : %v", row[1])
|
|
}
|
|
|
|
prefix, err := strconv.Atoi(row[1][1:])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Unable to parse prefix out of second argument. : %v", row[1])
|
|
}
|
|
|
|
result := networkingCommandEntry_remove_nat_prefix{vnet: vnet - 1, prefix: prefix}
|
|
return &networkingCommandEntry{entry: result, remove_nat_prefix: &result}, nil
|
|
}
|
|
|
|
type networkingCommandParser struct {
|
|
command string
|
|
callback func([]string) (*networkingCommandEntry, error)
|
|
}
|
|
|
|
var NetworkingCommandParsers = []networkingCommandParser{
|
|
/* DictRecordParseFunct */ {command: "answer", callback: parseNetworkingCommand_answer},
|
|
/* DictRecordParseFunct */ {command: "remove_answer", callback: parseNetworkingCommand_remove_answer},
|
|
/* NatFwdRecordParseFunct */ {command: "add_nat_portfwd", callback: parseNetworkingCommand_add_nat_portfwd},
|
|
/* NatFwdRecordParseFunct */ {command: "remove_nat_portfwd", callback: parseNetworkingCommand_remove_nat_portfwd},
|
|
/* DhcpMacRecordParseFunct */ {command: "add_dhcp_mac_to_ip", callback: parseNetworkingCommand_add_dhcp_mac_to_ip},
|
|
/* DhcpMacRecordParseFunct */ {command: "remove_dhcp_mac_to_ip", callback: parseNetworkingCommand_remove_dhcp_mac_to_ip},
|
|
/* BridgeMappingRecordParseFunct */ {command: "add_bridge_mapping", callback: parseNetworkingCommand_add_bridge_mapping},
|
|
/* BridgeMappingRecordParseFunct */ {command: "remove_bridge_mapping", callback: parseNetworkingCommand_remove_bridge_mapping},
|
|
/* NatPrefixRecordParseFunct */ {command: "add_nat_prefix", callback: parseNetworkingCommand_add_nat_prefix},
|
|
/* NatPrefixRecordParseFunct */ {command: "remove_nat_prefix", callback: parseNetworkingCommand_remove_nat_prefix},
|
|
}
|
|
|
|
func NetworkingParserByCommand(command string) *func([]string) (*networkingCommandEntry, error) {
|
|
for _, p := range NetworkingCommandParsers {
|
|
if p.command == command {
|
|
return &p.callback
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseNetworkingConfig(rows chan []string) chan networkingCommandEntry {
|
|
out := make(chan networkingCommandEntry)
|
|
|
|
go func(in chan []string, out chan networkingCommandEntry) {
|
|
for {
|
|
row, ok := <-in
|
|
if !ok {
|
|
break
|
|
}
|
|
|
|
if len(row) >= 1 {
|
|
parser := NetworkingParserByCommand(row[0])
|
|
if parser == nil {
|
|
log.Printf("Invalid command : %v", row)
|
|
continue
|
|
}
|
|
|
|
callback := *parser
|
|
|
|
entry, err := callback(row[1:])
|
|
if err != nil {
|
|
log.Printf("Unable to parse command : %v %v", err, row)
|
|
continue
|
|
}
|
|
out <- *entry
|
|
}
|
|
}
|
|
close(out)
|
|
}(rows, out)
|
|
return out
|
|
}
|
|
|
|
type NetworkingConfig struct {
|
|
answer map[int]map[string]string
|
|
nat_portfwd map[int]map[string]string
|
|
dhcp_mac_to_ip map[int]map[string]net.IP
|
|
//bridge_mapping map[net.Interface]uint64 // XXX: we don't need the actual interface for anything but informing the user.
|
|
bridge_mapping map[string]int
|
|
nat_prefix map[int][]int
|
|
}
|
|
|
|
func (c NetworkingConfig) repr() string {
|
|
return fmt.Sprintf("answer -> %v\nnat_portfwd -> %v\ndhcp_mac_to_ip -> %v\nbridge_mapping -> %v\nnat_prefix -> %v", c.answer, c.nat_portfwd, c.dhcp_mac_to_ip, c.bridge_mapping, c.nat_prefix)
|
|
}
|
|
|
|
func flattenNetworkingConfig(in chan networkingCommandEntry) NetworkingConfig {
|
|
var result NetworkingConfig
|
|
var vmnet int
|
|
|
|
result.answer = make(map[int]map[string]string)
|
|
result.nat_portfwd = make(map[int]map[string]string)
|
|
result.dhcp_mac_to_ip = make(map[int]map[string]net.IP)
|
|
result.bridge_mapping = make(map[string]int)
|
|
result.nat_prefix = make(map[int][]int)
|
|
|
|
for {
|
|
e, ok := <-in
|
|
if !ok {
|
|
break
|
|
}
|
|
|
|
switch e.entry.(type) {
|
|
case networkingCommandEntry_answer:
|
|
vnet := e.answer.vnet
|
|
answers, exists := result.answer[vnet.Number()]
|
|
if !exists {
|
|
answers = make(map[string]string)
|
|
result.answer[vnet.Number()] = answers
|
|
}
|
|
answers[vnet.Option()] = e.answer.value
|
|
|
|
case networkingCommandEntry_remove_answer:
|
|
vnet := e.remove_answer.vnet
|
|
answers, exists := result.answer[vnet.Number()]
|
|
if exists {
|
|
delete(answers, vnet.Option())
|
|
} else {
|
|
log.Printf("Unable to remove answer %s as specified by `remove_answer`.\n", vnet.Repr())
|
|
}
|
|
|
|
case networkingCommandEntry_add_nat_portfwd:
|
|
vmnet = e.add_nat_portfwd.vnet
|
|
protoport := fmt.Sprintf("%s/%d", e.add_nat_portfwd.protocol, e.add_nat_portfwd.port)
|
|
target := fmt.Sprintf("%s:%d", e.add_nat_portfwd.target_host, e.add_nat_portfwd.target_port)
|
|
portfwds, exists := result.nat_portfwd[vmnet]
|
|
if !exists {
|
|
portfwds = make(map[string]string)
|
|
result.nat_portfwd[vmnet] = portfwds
|
|
}
|
|
portfwds[protoport] = target
|
|
|
|
case networkingCommandEntry_remove_nat_portfwd:
|
|
vmnet = e.remove_nat_portfwd.vnet
|
|
protoport := fmt.Sprintf("%s/%d", e.remove_nat_portfwd.protocol, e.remove_nat_portfwd.port)
|
|
portfwds, exists := result.nat_portfwd[vmnet]
|
|
if exists {
|
|
delete(portfwds, protoport)
|
|
} else {
|
|
log.Printf("Unable to remove nat port-forward %s from interface %s%d as requested by `remove_nat_portfwd`.\n", protoport, NetworkingInterfacePrefix, vmnet)
|
|
}
|
|
|
|
case networkingCommandEntry_add_dhcp_mac_to_ip:
|
|
vmnet = e.add_dhcp_mac_to_ip.vnet
|
|
dhcpmacs, exists := result.dhcp_mac_to_ip[vmnet]
|
|
if !exists {
|
|
dhcpmacs = make(map[string]net.IP)
|
|
result.dhcp_mac_to_ip[vmnet] = dhcpmacs
|
|
}
|
|
dhcpmacs[e.add_dhcp_mac_to_ip.mac.String()] = e.add_dhcp_mac_to_ip.ip
|
|
|
|
case networkingCommandEntry_remove_dhcp_mac_to_ip:
|
|
vmnet = e.remove_dhcp_mac_to_ip.vnet
|
|
dhcpmacs, exists := result.dhcp_mac_to_ip[vmnet]
|
|
if exists {
|
|
delete(dhcpmacs, e.remove_dhcp_mac_to_ip.mac.String())
|
|
} else {
|
|
log.Printf("Unable to remove dhcp_mac_to_ip entry %v from interface %s%d as specified by `remove_dhcp_mac_to_ip`.\n", e.remove_dhcp_mac_to_ip, NetworkingInterfacePrefix, vmnet)
|
|
}
|
|
|
|
case networkingCommandEntry_add_bridge_mapping:
|
|
intf := e.add_bridge_mapping.intf
|
|
if _, err := intf.Interface(); err != nil {
|
|
log.Printf("Interface \"%s\" as specified by `add_bridge_mapping` was not found on the current platform. This is a non-critical error. Ignoring.", intf.name)
|
|
}
|
|
result.bridge_mapping[intf.name] = e.add_bridge_mapping.vnet
|
|
|
|
case networkingCommandEntry_remove_bridge_mapping:
|
|
intf := e.remove_bridge_mapping.intf
|
|
if _, err := intf.Interface(); err != nil {
|
|
log.Printf("Interface \"%s\" as specified by `remove_bridge_mapping` was not found on the current platform. This is a non-critical error. Ignoring.", intf.name)
|
|
}
|
|
delete(result.bridge_mapping, intf.name)
|
|
|
|
case networkingCommandEntry_add_nat_prefix:
|
|
vmnet = e.add_nat_prefix.vnet
|
|
_, exists := result.nat_prefix[vmnet]
|
|
if exists {
|
|
result.nat_prefix[vmnet] = append(result.nat_prefix[vmnet], e.add_nat_prefix.prefix)
|
|
} else {
|
|
result.nat_prefix[vmnet] = []int{e.add_nat_prefix.prefix}
|
|
}
|
|
|
|
case networkingCommandEntry_remove_nat_prefix:
|
|
vmnet = e.remove_nat_prefix.vnet
|
|
prefixes, exists := result.nat_prefix[vmnet]
|
|
if exists {
|
|
for index := 0; index < len(prefixes); index++ {
|
|
if prefixes[index] == e.remove_nat_prefix.prefix {
|
|
result.nat_prefix[vmnet] = append(prefixes[:index], prefixes[index+1:]...)
|
|
break
|
|
}
|
|
}
|
|
|
|
} else {
|
|
log.Printf("Unable to remove nat prefix /%d from interface %s%d as specified by `remove_nat_prefix`.\n", e.remove_nat_prefix.prefix, NetworkingInterfacePrefix, vmnet)
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Constructor for networking file
|
|
func ReadNetworkingConfig(fd *os.File) (NetworkingConfig, error) {
|
|
|
|
// start piecing together all of the differents parts of the file and split
|
|
// it into its individual rows.
|
|
fromfile := consumeFile(fd)
|
|
tokenized := tokenizeNetworkingConfig(fromfile)
|
|
rows := splitNetworkingConfig(tokenized)
|
|
|
|
// consume the version _first_. this is important because if the version is
|
|
// wrong, then there's likely tokens that we won't know how to interpret.
|
|
parsed_version, err := networkingReadVersion(<-rows)
|
|
if err != nil {
|
|
return NetworkingConfig{}, err
|
|
}
|
|
|
|
// verify that it's 1.0 since that's all we support for now.
|
|
if version := parsed_version.Number(); version != 1.0 {
|
|
return NetworkingConfig{}, fmt.Errorf("Expected version %f of networking file. Received version %f.", 1.0, version)
|
|
}
|
|
|
|
// now that our version has been confirmed, we can proceed to parse the
|
|
// rest of the file and parseNetworkingConfig is free to consume rows as
|
|
// much as it wants to.
|
|
entries := parseNetworkingConfig(rows)
|
|
|
|
// convert what we've parsed into a configuration that's easy to interpret
|
|
return flattenNetworkingConfig(entries), nil
|
|
}
|
|
|
|
// netmapper interface
|
|
type NetworkingType int
|
|
|
|
const (
|
|
NetworkingType_HOSTONLY = iota + 1
|
|
NetworkingType_NAT
|
|
NetworkingType_BRIDGED
|
|
)
|
|
|
|
func networkingConfig_InterfaceTypes(config NetworkingConfig) map[int]NetworkingType {
|
|
result := make(map[int]NetworkingType)
|
|
|
|
// defaults
|
|
result[0] = NetworkingType_BRIDGED
|
|
result[1] = NetworkingType_HOSTONLY
|
|
result[8] = NetworkingType_NAT
|
|
|
|
// walk through config collecting bridged interfaces
|
|
for _, vmnet := range config.bridge_mapping {
|
|
result[vmnet] = NetworkingType_BRIDGED
|
|
}
|
|
|
|
// walk through answers finding out which ones are nat versus hostonly
|
|
for vmnet, table := range config.answer {
|
|
|
|
// everything should be defined as a virtual adapter...
|
|
if table["VIRTUAL_ADAPTER"] == "yes" {
|
|
|
|
// validate that the VNET entry contains everything we expect it to
|
|
_, subnetQ := table["HOSTONLY_SUBNET"]
|
|
_, netmaskQ := table["HOSTONLY_NETMASK"]
|
|
if !(subnetQ && netmaskQ) {
|
|
log.Printf("Interface %s%d is missing some expected keys (HOSTONLY_SUBNET, HOSTONLY_NETMASK). This is non-critical. Ignoring..", NetworkingInterfacePrefix, vmnet)
|
|
}
|
|
|
|
// distinguish between nat or hostonly
|
|
if table["NAT"] == "yes" {
|
|
result[vmnet] = NetworkingType_NAT
|
|
|
|
} else {
|
|
result[vmnet] = NetworkingType_HOSTONLY
|
|
}
|
|
|
|
} else {
|
|
// if it's not a virtual_adapter, then it must be an alias (really a bridge).
|
|
result[vmnet] = NetworkingType_BRIDGED
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func networkingConfig_NamesToVmnet(config NetworkingConfig) map[NetworkingType][]int {
|
|
types := networkingConfig_InterfaceTypes(config)
|
|
|
|
// now sort the keys
|
|
var keys []int
|
|
for vmnet := range types {
|
|
keys = append(keys, vmnet)
|
|
}
|
|
sort.Ints(keys)
|
|
|
|
// build result dictionary
|
|
result := make(map[NetworkingType][]int)
|
|
|
|
for i := 0; i < len(keys); i++ {
|
|
t := types[keys[i]]
|
|
result[t] = append(result[t], keys[i])
|
|
}
|
|
return result
|
|
}
|
|
|
|
const NetworkingInterfacePrefix = "vmnet"
|
|
|
|
func (e NetworkingConfig) NameIntoDevices(name string) ([]string, error) {
|
|
netmapper := networkingConfig_NamesToVmnet(e)
|
|
name = strings.ToLower(name)
|
|
|
|
var vmnets []string
|
|
var networkingType NetworkingType
|
|
if name == "hostonly" && len(netmapper[NetworkingType_HOSTONLY]) > 0 {
|
|
networkingType = NetworkingType_HOSTONLY
|
|
|
|
} else if name == "nat" && len(netmapper[NetworkingType_NAT]) > 0 {
|
|
networkingType = NetworkingType_NAT
|
|
|
|
} else if name == "bridged" && len(netmapper[NetworkingType_BRIDGED]) > 0 {
|
|
networkingType = NetworkingType_BRIDGED
|
|
|
|
} else {
|
|
return make([]string, 0), fmt.Errorf("Network name not found: %v", name)
|
|
}
|
|
|
|
for i := 0; i < len(netmapper[networkingType]); i++ {
|
|
vmnets = append(vmnets, fmt.Sprintf("%s%d", NetworkingInterfacePrefix, netmapper[networkingType][i]))
|
|
}
|
|
return vmnets, nil
|
|
}
|
|
|
|
func (e NetworkingConfig) DeviceIntoName(device string) (string, error) {
|
|
types := networkingConfig_InterfaceTypes(e)
|
|
|
|
lowerdevice := strings.ToLower(device)
|
|
if !strings.HasPrefix(lowerdevice, NetworkingInterfacePrefix) {
|
|
return device, nil
|
|
}
|
|
vmnet, err := strconv.Atoi(lowerdevice[len(NetworkingInterfacePrefix):])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
network := types[vmnet]
|
|
switch network {
|
|
case NetworkingType_HOSTONLY:
|
|
return "hostonly", nil
|
|
|
|
case NetworkingType_NAT:
|
|
return "nat", nil
|
|
|
|
case NetworkingType_BRIDGED:
|
|
return "bridged", nil
|
|
}
|
|
return "", fmt.Errorf("Unable to determine network type for device %s%d.", NetworkingInterfacePrefix, vmnet)
|
|
}
|
|
|
|
/** generic async file reader */
|
|
func consumeFile(fd *os.File) chan byte {
|
|
fromFile := make(chan byte)
|
|
go func() {
|
|
b := make([]byte, 1)
|
|
for {
|
|
_, err := fd.Read(b)
|
|
if err != nil {
|
|
// In case of any error we must stop
|
|
// ErrClosed may appear since file is closed and this goroutine still left running
|
|
break
|
|
}
|
|
fromFile <- b[0]
|
|
}
|
|
close(fromFile)
|
|
}()
|
|
return fromFile
|
|
}
|
|
|
|
/** Consume a byte channel until a terminal byte is reached, and write each list of bytes to a channel */
|
|
func consumeUntilSentinel(sentinel byte, in chan byte) (result []byte, ok bool) {
|
|
|
|
// This is a simple utility that will consume from a channel until a sentinel
|
|
// byte has been reached. Consumed data is returned in `result, and if
|
|
// there's no more data to read, then `ok` will be false.
|
|
for ok = true; ; {
|
|
if by, success := <-in; !success {
|
|
ok = false
|
|
break
|
|
|
|
} else if by == sentinel {
|
|
break
|
|
|
|
} else {
|
|
result = append(result, by)
|
|
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
/** Simple utility to ignore chars when consuming a channel */
|
|
func filterOutCharacters(ignore []byte, in chan byte) chan byte {
|
|
out := make(chan byte)
|
|
|
|
go func(ignore_s string) {
|
|
for {
|
|
if by, ok := <-in; !ok {
|
|
break
|
|
|
|
} else if !strings.ContainsAny(ignore_s, string(by)) {
|
|
out <- by
|
|
}
|
|
}
|
|
close(out)
|
|
}(string(ignore))
|
|
|
|
return out
|
|
}
|
|
|
|
/**
|
|
This consumes bytes within a pair of some bytes, like parentheses, brackets, braces...
|
|
|
|
We start by reading bytes until we encounter openByte. These will be returned as
|
|
the first parameter. Then we can enter a goro and consume bytes until we get to
|
|
closeByte. At that point we're done, and we can exit.
|
|
**/
|
|
func consumeOpenClosePair(openByte, closeByte byte, in chan byte) ([]byte, chan byte) {
|
|
result := make([]byte, 0)
|
|
|
|
// Consume until we get to openByte. We'll return what we consumed because
|
|
// it isn't actually relevant to what we're trying to accomplish.
|
|
for by := range in {
|
|
if by == openByte {
|
|
break
|
|
|
|
} else {
|
|
result = append(result, by)
|
|
}
|
|
}
|
|
|
|
// Now we can feed input to our goro and a consumer can see what's contained
|
|
// between their requested pairs
|
|
out := make(chan byte)
|
|
go func(out chan byte) {
|
|
by := openByte
|
|
|
|
// We only made it here because we received an openByte, so let's make
|
|
// sure we send it down the channel.
|
|
out <- by
|
|
|
|
// Now just spin in a loop shipping bytes down the channel until we hit
|
|
// closeByte, or we're at the very end...whichever comes first.
|
|
var ok bool
|
|
for by != closeByte {
|
|
by, ok = <-in
|
|
if !ok {
|
|
by = closeByte
|
|
}
|
|
|
|
out <- by
|
|
}
|
|
close(out)
|
|
}(out)
|
|
|
|
// Return what we consumed, and a channel that yields everything in between
|
|
// the openByte and closeByte pair.
|
|
return result, out
|
|
}
|
|
|
|
// Basic decoding of a dhcpd lease address
|
|
func decodeDhcpdLeaseBytes(input string) ([]byte, error) {
|
|
processed := &bytes.Buffer{}
|
|
|
|
// Split the string into pieces as we'll need to validate it.
|
|
for _, item := range strings.Split(input, ":") {
|
|
if len(item) != 2 {
|
|
return []byte{}, fmt.Errorf("bytes are not well-formed (%v)", input)
|
|
}
|
|
processed.WriteString(item)
|
|
}
|
|
|
|
length := hex.DecodedLen(processed.Len())
|
|
|
|
// Decode the processed data into the result...
|
|
result := make([]byte, length)
|
|
if n, err := hex.Decode(result, processed.Bytes()); err != nil {
|
|
return []byte{}, err
|
|
|
|
// Check that our decode length corresponds to what was intended
|
|
} else if n != length {
|
|
return []byte{}, fmt.Errorf("expected to decode %d bytes, got %d instead", length, n)
|
|
}
|
|
|
|
// ...and then return it.
|
|
return result, nil
|
|
}
|
|
|
|
/*** Dhcp Leases */
|
|
type dhcpLeaseEntry struct {
|
|
address string
|
|
starts, ends time.Time
|
|
starts_weekday, ends_weekday int
|
|
ether, uid []byte
|
|
extra []string
|
|
}
|
|
|
|
func readDhcpdLeaseEntry(in chan byte) (entry *dhcpLeaseEntry, err error) {
|
|
|
|
// Build the regexes we'll use to legitimately parse each item
|
|
ipLineRe := regexp.MustCompile(`lease\s+(.+?)\s*$`)
|
|
startTimeLineRe := regexp.MustCompile(`starts\s+(\d+)\s+(.+?)\s*$`)
|
|
endTimeLineRe := regexp.MustCompile(`ends\s+(\d+)\s+(.+?)\s*$`)
|
|
macLineRe := regexp.MustCompile(`hardware\s+ethernet\s+(.+?)\s*$`)
|
|
uidLineRe := regexp.MustCompile(`uid\s+(.+?)\s*$`)
|
|
|
|
/// Read up to the lease item and validate that it actually matches
|
|
lease, ch := consumeOpenClosePair('{', '}', in)
|
|
|
|
// If we couldn't read the lease, then this item is busted and we're prolly
|
|
// done reading the channel.
|
|
if len(lease) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
matches := ipLineRe.FindStringSubmatch(string(lease))
|
|
if matches == nil {
|
|
res := strings.TrimSpace(string(lease))
|
|
return &dhcpLeaseEntry{extra: []string{res}}, fmt.Errorf("Unable to parse lease entry (%#v)", string(lease))
|
|
}
|
|
|
|
if by, ok := <-ch; ok && by == '{' {
|
|
// If we found a lease match and we're definitely beginning a lease
|
|
// entry, then create our storage.
|
|
entry = &dhcpLeaseEntry{address: matches[1]}
|
|
|
|
} else if ok {
|
|
// If we didn't see a begin brace, then this entry is mangled which
|
|
// means that we should probably bail
|
|
return &dhcpLeaseEntry{address: matches[1]}, fmt.Errorf("Missing parameters for lease entry %v", matches[1])
|
|
|
|
} else if !ok {
|
|
// If our channel is closed, so we bail "cleanly".
|
|
return nil, nil
|
|
}
|
|
|
|
/// Now we can parse the inside of the block.
|
|
for insideBraces := true; insideBraces; {
|
|
item, ok := consumeUntilSentinel(';', ch)
|
|
item_s := string(item)
|
|
|
|
if !ok {
|
|
insideBraces = false
|
|
}
|
|
|
|
// Parse out the start time
|
|
matches = startTimeLineRe.FindStringSubmatch(item_s)
|
|
if matches != nil {
|
|
if entry.starts, err = time.Parse("2006/01/02 15:04:05", matches[2]); err != nil {
|
|
log.Printf("Error trying to parse start time (%v) for entry %v", matches[2], entry.address)
|
|
}
|
|
if entry.starts_weekday, err = strconv.Atoi(matches[1]); err != nil {
|
|
log.Printf("Error trying to parse start weekday (%v) for entry %v", matches[1], entry.address)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Parse out the end time
|
|
matches = endTimeLineRe.FindStringSubmatch(item_s)
|
|
if matches != nil {
|
|
if entry.ends, err = time.Parse("2006/01/02 15:04:05", matches[2]); err != nil {
|
|
log.Printf("Error trying to parse end time (%v) for entry %v", matches[2], entry.address)
|
|
}
|
|
if entry.ends_weekday, err = strconv.Atoi(matches[1]); err != nil {
|
|
log.Printf("Error trying to parse end weekday (%v) for entry %v", matches[1], entry.address)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Parse out the hardware ethernet
|
|
matches = macLineRe.FindStringSubmatch(item_s)
|
|
if matches != nil {
|
|
if entry.ether, err = decodeDhcpdLeaseBytes(matches[1]); err != nil {
|
|
log.Printf("Error trying to parse hardware ethernet address (%v) for entry %v", matches[1], entry.address)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Parse out the uid
|
|
matches = uidLineRe.FindStringSubmatch(item_s)
|
|
if matches != nil {
|
|
if entry.uid, err = decodeDhcpdLeaseBytes(matches[1]); err != nil {
|
|
log.Printf("Error trying to parse uid (%v) for entry %v", matches[1], entry.address)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Check to see if we're terminating the brace, so we can skip
|
|
// to the next iteration.
|
|
if strings.HasSuffix(item_s, "}") {
|
|
continue
|
|
}
|
|
|
|
// Just stash it for now because we have no idea what it is.
|
|
entry.extra = append(entry.extra, strings.TrimSpace(item_s))
|
|
}
|
|
|
|
return entry, nil
|
|
}
|
|
|
|
func ReadDhcpdLeaseEntries(fd *os.File) ([]dhcpLeaseEntry, error) {
|
|
fch := consumeFile(fd)
|
|
uncommentedch := uncomment(fch)
|
|
wch := filterOutCharacters([]byte{'\n', '\r', '\v'}, uncommentedch)
|
|
|
|
result := make([]dhcpLeaseEntry, 0)
|
|
errors := make([]error, 0)
|
|
|
|
// Consume dhcpd lease entries from the channel until we just plain run out.
|
|
for i := 0; ; i += 1 {
|
|
if entry, err := readDhcpdLeaseEntry(wch); entry == nil {
|
|
// If our entry is nil, then we've run out of input and finished
|
|
// parsing the file to completion.
|
|
break
|
|
|
|
} else if err != nil {
|
|
// If we received an error, then log it and keep track of it. This
|
|
// way we can warn the user later which entries we had issues with.
|
|
log.Printf("Error parsing dhcpd lease entry #%d: %s", 1+i, err)
|
|
errors = append(errors, err)
|
|
|
|
} else {
|
|
// If we've parsed an entry successfully, then aggregate it to
|
|
// our slice of results.
|
|
result = append(result, *entry)
|
|
}
|
|
}
|
|
|
|
// If we received any errors then include alongside our results.
|
|
if len(errors) > 0 {
|
|
return result, fmt.Errorf("Errors found while parsing dhcpd lease entries: %v", errors)
|
|
}
|
|
return result, nil
|
|
}
|