Initial configuration parser

master
Ambrose Chua 2020-01-01 15:08:59 +00:00
parent 58851a6218
commit f2982da264
2 changed files with 222 additions and 6 deletions

View File

@ -1,14 +1,193 @@
// TODO: Should split between encoder and decoder
package lib
import (
"bufio"
"fmt"
"io"
"net"
"strconv"
"strings"
"time"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
const (
CommentChar = "#"
AssignmentChar = "="
)
const (
SectionNone = iota
SectionDevice
SectionPeer
)
var sectionNames = []string{
"None",
"Device",
"Peer",
}
var (
ErrUnknownSection = fmt.Errorf("unknown section")
ErrUnknownKey = fmt.Errorf("unknown key")
ErrValueParse = fmt.Errorf("value parse failed")
)
// ReadConfig is yet another INI-like configuration file parser, but for WireGuard
func ReadConfig(r io.Reader) (wgtypes.Config, error) {
tmp := make([]byte, 1000)
r.Read(tmp)
return wgtypes.Config{}, nil
scanner := bufio.NewScanner(r)
config := wgtypes.Config{ReplacePeers: true}
section := SectionNone
for scanner.Scan() {
text := scanner.Text()
line, _ := readConfigLine(text)
s, k, v := parseLine(line)
switch {
case insensetiveMatch(s, "Interface"):
section = SectionDevice
case insensetiveMatch(s, "Peer"):
section = SectionPeer
config.Peers = append(config.Peers, wgtypes.PeerConfig{
ReplaceAllowedIPs: true,
})
case len(s) > 0:
return config, fmt.Errorf("%w: %v", ErrUnknownSection, s)
}
if len(k) == 0 {
continue
}
// TODO: break out parsers into functions
switch section {
case SectionDevice:
switch {
case insensetiveMatch(k, "ListenPort"):
listenPort, err := strconv.ParseInt(v, 0, 0)
if err != nil {
return config, fmt.Errorf("%w: %w: %v=%v", ErrValueParse, err, k, v)
}
listenPortInt := int(listenPort)
config.ListenPort = &listenPortInt
case insensetiveMatch(k, "FwMark"):
fwMarkInt := 0
if !insensetiveMatch(v, "off") {
fwMark, err := strconv.ParseInt(v, 0, 0)
if err != nil {
return config, fmt.Errorf("%w: %w: %v=%v", ErrValueParse, err, k, v)
}
fwMarkInt = int(fwMark)
}
config.FirewallMark = &fwMarkInt
case insensetiveMatch(k, "PrivateKey"):
key, err := wgtypes.ParseKey(v)
if err != nil {
return config, fmt.Errorf("%w: %w: %v=%v", ErrValueParse, err, k, v)
}
config.PrivateKey = &key
default:
return config, fmt.Errorf("%w: %v: %v", ErrUnknownKey, sectionNames[section], k)
}
case SectionPeer:
peer := &config.Peers[len(config.Peers)-1]
switch {
case insensetiveMatch(k, "Endpoint"):
endpoint, err := net.ResolveUDPAddr("udp", v)
if err != nil {
return config, fmt.Errorf("%w: %w: %v=%v", ErrValueParse, err, k, v)
}
peer.Endpoint = endpoint
case insensetiveMatch(k, "PublicKey"):
key, err := wgtypes.ParseKey(v)
if err != nil {
return config, fmt.Errorf("%w: %w: %v=%v", ErrValueParse, err, k, v)
}
peer.PublicKey = key
case insensetiveMatch(k, "AllowedIPs"):
allowedIPs, err := parseAllowedIPs(v)
if err != nil {
return config, fmt.Errorf("%w: %w: %v=%v", ErrValueParse, err, k, v)
}
peer.AllowedIPs = allowedIPs
case insensetiveMatch(k, "PersistentKeepalive"):
persistentKeepalive := int64(0)
var err error
if !insensetiveMatch(v, "off") {
persistentKeepalive, err = strconv.ParseInt(v, 0, 64)
if err != nil {
return config, fmt.Errorf("%w: %w: %v=%v", ErrValueParse, err, k, v)
}
}
if persistentKeepalive < 0 || persistentKeepalive > 65535 {
return config, fmt.Errorf("%w: Persistent keepalive interval is neither 0/off nor 1-65535: %v=%v", ErrValueParse, k, v)
}
persistentKeepaliveDuration := time.Duration(persistentKeepalive * int64(time.Second))
peer.PersistentKeepaliveInterval = &persistentKeepaliveDuration
case insensetiveMatch(k, "PresharedKey"):
default:
return config, fmt.Errorf("%w: %v: %v", ErrUnknownKey, sectionNames[section], k)
}
}
}
return config, nil
}
func readConfigLine(text string) (line, comments string) {
line = text
comments = ""
comment := strings.Index(line, CommentChar)
if comment >= 0 {
line = text[:comment]
comments = text[comment+1:]
}
line = strings.TrimSpace(line)
comments = strings.TrimSpace(comments)
return
}
func parseLine(line string) (section, key, value string) {
if len(line) < 1 {
return "", "", ""
}
if line[0] == '[' && line[len(line)-1] == ']' {
return line[1 : len(line)-1], "", ""
}
assign := strings.Index(line, AssignmentChar)
if assign >= 0 {
return "", strings.TrimSpace(line[:assign]), strings.TrimSpace(line[assign+1:])
}
return "", "", ""
}
func insensetiveMatch(a string, b string) bool {
return strings.ToLower(a) == strings.ToLower(b)
}
func parseAllowedIPs(s string) ([]net.IPNet, error) {
parsedIPs := make([]net.IPNet, 0)
stringIPs := strings.Split(s, ",")
for _, stringIP := range stringIPs {
stringIP := strings.TrimSpace(stringIP)
_, parsedIP, err := net.ParseCIDR(stringIP)
if err != nil {
return parsedIPs, err
}
parsedIPs = append(parsedIPs, *parsedIP)
}
return parsedIPs, nil
}

View File

@ -1,8 +1,10 @@
package lib
import (
"net"
"strings"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
@ -18,21 +20,56 @@ PrivateKey = MITUgapB4QfRFF54ITXL3TaiYiSsVYkchqfjAXjxM10=
[Peer]
PublicKey = pjFx72IjbMh84SH1nq8Qfbl7HD5mSScHXCV1eISR7lk=
AllowedIPs = 192.168.10.2/32, 2001:470:ed5d:a::2/128
PersistentKeepalive = 80
[Peer]
AllowedIPs = 192.168.10.40/32, 2001:470:ed5d:a::28/128
PublicKey = wXU+vSTdEoIwSi+Tmv35SCOFg17wCAwnmYxeQPpbzDg=
`
var testGoodConfig1Want = wgtypes.Config{}
func TestReadConfig1(t *testing.T) {
buf := strings.NewReader(testGoodConfig1)
got, err := ReadConfig(buf)
if err != nil {
t.Fatalf("config read failed: %w", err)
}
if diff := cmp.Diff(testGoodConfig1Want, got); diff != "" {
wantPrivateKey, _ := wgtypes.ParseKey("MITUgapB4QfRFF54ITXL3TaiYiSsVYkchqfjAXjxM10=")
wantListenPort := 3333
wantPeer1PublicKey, _ := wgtypes.ParseKey("pjFx72IjbMh84SH1nq8Qfbl7HD5mSScHXCV1eISR7lk=")
_, wantPeer1AllowedIP1, _ := net.ParseCIDR("192.168.10.2/32")
_, wantPeer1AllowedIP2, _ := net.ParseCIDR("2001:470:ed5d:a::2/128")
wantPeer1PersistentKeepalive, _ := time.ParseDuration("80s")
wantPeer2PublicKey, _ := wgtypes.ParseKey("wXU+vSTdEoIwSi+Tmv35SCOFg17wCAwnmYxeQPpbzDg=")
_, wantPeer2AllowedIP1, _ := net.ParseCIDR("192.168.10.40/32")
_, wantPeer2AllowedIP2, _ := net.ParseCIDR("2001:470:ed5d:a::28/128")
want := wgtypes.Config{
PrivateKey: &wantPrivateKey,
ListenPort: &wantListenPort,
ReplacePeers: true,
Peers: []wgtypes.PeerConfig{
wgtypes.PeerConfig{
PublicKey: wantPeer1PublicKey,
ReplaceAllowedIPs: true,
AllowedIPs: []net.IPNet{
*wantPeer1AllowedIP1,
*wantPeer1AllowedIP2,
},
PersistentKeepaliveInterval: &wantPeer1PersistentKeepalive,
},
wgtypes.PeerConfig{
PublicKey: wantPeer2PublicKey,
ReplaceAllowedIPs: true,
AllowedIPs: []net.IPNet{
*wantPeer2AllowedIP1,
*wantPeer2AllowedIP2,
},
},
},
}
if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("returned config is not what is wanted: \n%s", diff)
}
}