2018-06-02 18:05:09 +02:00
|
|
|
// Package dnsmasq manages the process lifecycle of the dnsmasq(8) DHCP server.
|
|
|
|
package dnsmasq
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Process is a handle for a dnsmasq(8) process.
|
|
|
|
type Process struct {
|
|
|
|
killed bool // whether Kill was called
|
|
|
|
done chan struct{} // closed when Done() is called
|
|
|
|
wait chan struct{} // closed when Wait() returns
|
|
|
|
dnsmasq *exec.Cmd
|
|
|
|
|
|
|
|
mu sync.Mutex
|
|
|
|
actions []string
|
|
|
|
}
|
|
|
|
|
|
|
|
var dhcpActionRe = regexp.MustCompile(` (DHCP[^(]+\(.*)$`)
|
|
|
|
|
|
|
|
// Run starts a dnsmasq(8) process and returns a handle to it.
|
2018-06-24 08:33:39 +02:00
|
|
|
func Run(t *testing.T, iface string) *Process {
|
2018-06-02 18:05:09 +02:00
|
|
|
const ns = "ns0" // TODO: configurable?
|
|
|
|
|
|
|
|
ready, err := ioutil.TempFile("", "router7")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
ready.Close() // dnsmasq will re-create the file anyway
|
|
|
|
defer os.Remove(ready.Name()) // dnsmasq does not clean up its pid file
|
|
|
|
|
|
|
|
// iface, err := net.InterfaceByName("veth0a")
|
|
|
|
// if err != nil {
|
|
|
|
// t.Fatal(err)
|
|
|
|
// }
|
|
|
|
|
|
|
|
p := &Process{
|
|
|
|
wait: make(chan struct{}),
|
|
|
|
}
|
|
|
|
|
|
|
|
p.dnsmasq = exec.Command("ip", "netns", "exec", ns, "dnsmasq",
|
|
|
|
"--keep-in-foreground", // cannot use --no-daemon because we need --pid-file
|
|
|
|
"--log-facility=-", // log to stderr
|
|
|
|
"--pid-file="+ready.Name(),
|
|
|
|
"--bind-interfaces",
|
2018-06-24 08:33:39 +02:00
|
|
|
"--interface="+iface,
|
2018-06-02 18:05:09 +02:00
|
|
|
"--dhcp-range=192.168.23.2,192.168.23.10",
|
2018-06-24 08:33:39 +02:00
|
|
|
"--dhcp-range=::1,::10,constructor:"+iface,
|
2018-06-02 18:05:09 +02:00
|
|
|
"--dhcp-authoritative", // eliminate timeouts
|
|
|
|
"--no-ping", // disable ICMP confirmation of unused addresses to eliminate tedious timeout
|
|
|
|
"--leasefile-ro", // do not create a lease database
|
|
|
|
)
|
|
|
|
|
|
|
|
p.dnsmasq.Stdout = os.Stdout
|
|
|
|
stderr := make(chan string)
|
|
|
|
r, w := io.Pipe()
|
|
|
|
scanner := bufio.NewScanner(r)
|
|
|
|
go func() {
|
|
|
|
for scanner.Scan() {
|
|
|
|
stderr <- scanner.Text()
|
|
|
|
}
|
|
|
|
close(stderr)
|
|
|
|
}()
|
|
|
|
p.dnsmasq.Stderr = w
|
|
|
|
//mac := iface.HardwareAddr.String()
|
|
|
|
go func() {
|
|
|
|
for line := range stderr {
|
|
|
|
fmt.Printf("dnsmasq log line: %s\n", line)
|
|
|
|
if !strings.HasPrefix(line, "dnsmasq-dhcp") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// if !strings.Contains(line, mac) {
|
|
|
|
// continue
|
|
|
|
// }
|
|
|
|
matches := dhcpActionRe.FindStringSubmatch(line)
|
|
|
|
if matches == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
p.mu.Lock()
|
|
|
|
p.actions = append(p.actions, matches[1])
|
|
|
|
p.mu.Unlock()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
if err := p.dnsmasq.Start(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
p.done = make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
err := p.dnsmasq.Wait()
|
|
|
|
close(p.wait)
|
|
|
|
select {
|
|
|
|
case <-p.done:
|
|
|
|
return // test done, any errors are from our Kill()
|
|
|
|
default:
|
|
|
|
t.Fatalf("dnsmasq exited prematurely: %v", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// TODO(later): use inotify instead of polling
|
|
|
|
// Wait for dnsmasq to write its process id, at which point it is already
|
|
|
|
// listening for requests.
|
|
|
|
for {
|
|
|
|
b, err := ioutil.ReadFile(ready.Name())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if strings.TrimSpace(string(b)) == strconv.Itoa(p.dnsmasq.Process.Pid) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
}
|
|
|
|
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
|
|
|
// Kill shuts down the dnsmasq(8) process and returns once waitpid returns.
|
|
|
|
func (p *Process) Kill() {
|
|
|
|
if p.killed {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
p.killed = true
|
|
|
|
close(p.done)
|
|
|
|
p.dnsmasq.Process.Kill()
|
|
|
|
<-p.wait
|
|
|
|
}
|
|
|
|
|
|
|
|
// Actions returns a string slice of dnsmasq(8) actions (as per its stderr log)
|
|
|
|
// received up until now. Use Kill before calling Actions to force a log flush.
|
|
|
|
func (p *Process) Actions() []string {
|
|
|
|
p.mu.Lock()
|
|
|
|
defer p.mu.Unlock()
|
|
|
|
result := make([]string, len(p.actions))
|
|
|
|
copy(result, p.actions)
|
|
|
|
return result
|
|
|
|
}
|