Files
P4wnP1_aloa/service/dhcp.go
2018-10-14 12:03:08 +02:00

285 lines
9.1 KiB
Go

// +build linux
package service
import (
pb "github.com/mame82/P4wnP1_go/proto"
"fmt"
"io/ioutil"
"os"
"os/exec"
"log"
"errors"
"strconv"
"syscall"
"strings"
"github.com/mame82/P4wnP1_go/common_web"
)
/*
The DHCP server part relies on "dnsmasq" and thus depends on the binary
Note: dnsmasq default option have to be disabled explicitly if not needed, by setting an empty value (1, 3, 6, 12, 28)
The DHCP client relies on dhcpcd binary
*/
func pidFileDHCPSrv(nameIface string) string {
return fmt.Sprintf("/var/run/dnsmasq_%s.pid", nameIface)
}
func pidFileDHCPClient(nameIface string) string {
return fmt.Sprintf("/var/run/dhcpcd-%s.pid", nameIface)
}
func leaseFileDHCPSrv(s *pb.DHCPServerSettings) (lf string) {
return common_web.NameLeaseFileDHCPSrv(s.ListenInterface) //default lease file
}
func NameConfigFileDHCPSrv(nameIface string) string {
return fmt.Sprintf("/tmp/dnsmasq_%s.conf", nameIface)
}
func StartDHCPClient(nameIface string) (err error) {
log.Printf("Starting DHCP client for interface '%s'...\n", nameIface)
//check if interface is valid
if_exists,_ := CheckInterfaceExistence(nameIface)
if !if_exists {
return errors.New(fmt.Sprintf("The given interface '%s' doesn't exist", nameIface))
}
//We use the run command and allow dhcpcd to daemonize
proc := exec.Command("/sbin/dhcpcd", "-C", "wpa_supplicant", nameIface) //we avoid starting wpa_supplicant along with the dhcp client
dhcpcd_out, err := proc.CombinedOutput()
//err = proc.Run()
if err != nil { return err}
fmt.Printf("Dhcpcd output for %s:\n%s", nameIface, dhcpcd_out)
log.Printf("... DHCP client for interface '%s' started\n", nameIface)
return nil
}
func IsDHCPClientRunning(nameIface string) (running bool, pid int, err error) {
if_exists,_ := CheckInterfaceExistence(nameIface)
if !if_exists {
return false, 0, errors.New(fmt.Sprintf("The given interface '%s' doesn't exist", nameIface))
}
pid_file := pidFileDHCPClient(nameIface)
//Check if the pidFile exists
if _, err := os.Stat(pid_file); os.IsNotExist(err) {
return false, 0,nil //file doesn't exist, so we assume dhcpcd isn't running
}
//File exists, read the PID
content, err := ioutil.ReadFile(pid_file)
if err != nil { return false, 0, err}
pid, err = strconv.Atoi(strings.TrimSuffix(string(content), "\n"))
if err != nil { return false, 0, errors.New(fmt.Sprintf("Error parsing PID file %s: %v", pid_file, err))}
//With PID given, check if the process is indeed running (pid_file could stay, even if the process has died already)
err_kill := syscall.Kill(pid, 0) //sig 0: doesn't send a signal, but error checking is still performed
switch err_kill{
case nil:
//ToDo: Check if the running process image is indeed dhcpcd
return true, pid, nil //Process is running
case syscall.ESRCH:
//Process doesn't exist
return false, pid, nil
case syscall.EPERM:
//process exists, but we have no access permission
return true, pid, err_kill
default:
return false, pid, err_kill
}
}
func StopDHCPClient(nameIface string) (err error) {
log.Printf("Stoping DHCP client for interface '%s'...\n", nameIface)
//check if interface is valid
if_exists,_ := CheckInterfaceExistence(nameIface)
if !if_exists {
return errors.New(fmt.Sprintf("The given interface '%s' doesn't exist", nameIface))
}
//We use the run command and allow dhcpcd to daemonize
proc := exec.Command("/sbin/dhcpcd", "-x", nameIface)
dhcpcd_out, err := proc.CombinedOutput()
//err = proc.Run()
if err != nil { return err}
fmt.Printf("Dhcpcd out for %s:\n%s", nameIface, dhcpcd_out)
log.Printf("... DHCP client for interface '%s' stopped\n", nameIface)
return nil
}
func StartDHCPServer(nameIface string, configPath string) (err error) {
log.Printf("Starting DHCP server for interface '%s' with config '%s'...\n", nameIface, configPath)
//check if interface is valid
if_exists,_ := CheckInterfaceExistence(nameIface)
if !if_exists {
return errors.New(fmt.Sprintf("The given interface '%s' doesn't exist", nameIface))
}
//Check if there's already a DHCP server running for the given interface
running, _, err := IsDHCPServerRunning(nameIface)
if err != nil { return errors.New(fmt.Sprintf("Error fetching state of DHCP server: %v\n", err)) }
if running {return errors.New(fmt.Sprintf("Error starting DHCP server for interface '%s', there is already a DHCP server running\n", nameIface))}
//We use the run command and allow dnsmasq to daemonize
proc := exec.Command("/usr/sbin/dnsmasq", "-x", pidFileDHCPSrv(nameIface), "-C", configPath)
//dnsmasq_out, err := proc.CombinedOutput()
err = proc.Run()
if err != nil { return err}
//fmt.Printf("Dnsmasq out %s\n", dnsmasq_out)
log.Printf("... DHCP server for interface '%s' started\n", nameIface)
return nil
}
func IsDHCPServerRunning(nameIface string) (running bool, pid int, err error) {
if_exists,_ := CheckInterfaceExistence(nameIface)
if !if_exists {
return false, 0, errors.New(fmt.Sprintf("The given interface '%s' doesn't exist", nameIface))
}
pid_file := pidFileDHCPSrv(nameIface)
//Check if the pidFile exists
if _, err := os.Stat(pid_file); os.IsNotExist(err) {
return false, 0,nil //file doesn't exist, so we assume dnsmasq isn't running
}
//File exists, read the PID
content, err := ioutil.ReadFile(pid_file)
if err != nil { return false, 0, err}
pid, err = strconv.Atoi(strings.TrimSuffix(string(content), "\n"))
if err != nil { return false, 0, errors.New(fmt.Sprintf("Error parsing PID file %s: %v", pid_file, err))}
//With PID given, check if the process is indeed running (pid_file could stay, even if the dnsmasq process has died already)
err_kill := syscall.Kill(pid, 0) //sig 0: doesn't send a signal, but error checking is still performed
switch err_kill{
case nil:
//ToDo: Check if the running process image is indeed dnsmasq
return true, pid, nil //Process is running
case syscall.ESRCH:
//Process doesn't exist
return false, pid, nil
case syscall.EPERM:
//process exists, but we have no access permission
return true, pid, err_kill
default:
return false, pid, err_kill
}
}
func StopDHCPServer(nameIface string) (err error) {
//don't check if interface is valid, to allow closing of orphaned DHCP procs (interface went down while running)
log.Printf("Stopping DHCP server for interface '%s' ...\n", nameIface)
running,pid,err := IsDHCPServerRunning(nameIface)
if err != nil { return }
if running {
//send SIGTERM
err = syscall.Kill(pid, syscall.SIGTERM)
if err != nil { return }
} else {
log.Printf("... DHCP server for interface '%s' wasn't started\n", nameIface)
}
running,pid,err = IsDHCPServerRunning(nameIface)
if err != nil { return }
if (running) {
log.Printf("... couldn't terminate DHCP server for interface '%s'\n", nameIface)
} else {
log.Printf("... DHCP server for interface '%s' stopped\n", nameIface)
}
//Delete PID file
os.Remove(pidFileDHCPSrv(nameIface))
//Deleting leaseFile
os.Remove(common_web.NameLeaseFileDHCPSrv(nameIface))
return nil
}
func DHCPCreateConfigFile(s *pb.DHCPServerSettings, filename string) (err error) {
file_content, err := DHCPCreateConfigFileString(s)
if err != nil {return}
err = ioutil.WriteFile(filename, []byte(file_content), os.ModePerm)
//ToDo: test config with `dnsmasq -C configfile --test`
return
}
func DHCPCreateConfigFileString(s *pb.DHCPServerSettings) (config string, err error) {
config = fmt.Sprintf("interface=%s\n", s.ListenInterface)
//bind only o the given interface, except suppressed by `doNotBindInterface` option
if !s.DoNotBindInterface { config += fmt.Sprintf("bind-interfaces\n") }
config += fmt.Sprintf("port=%d\n", s.ListenPort)
if len(s.CallbackScript) > 0 { config += fmt.Sprintf("dhcp-script=%s\n", s.CallbackScript) }
if len(s.LeaseFile) > 0 {
config += fmt.Sprintf("dhcp-leasefile=%s\n", s.LeaseFile)
} else {
config += fmt.Sprintf("dhcp-leasefile=%s\n", leaseFileDHCPSrv(s)) //default lease file
}
//Iterate over Ranges
for _, pRange := range s.Ranges {
//ToDo: regex check for leaseTime
//ToDo: check rangeLower + rangeUpper to be valid IP addresses
if len(pRange.LeaseTime) > 0 {
config += fmt.Sprintf("dhcp-range=%s,%s,%s\n", pRange.RangeLower, pRange.RangeUpper, pRange.LeaseTime)
} else {
//default to 5 minute lease
config += fmt.Sprintf("dhcp-range=%s,%s,5m\n", pRange.RangeLower, pRange.RangeUpper)
}
}
//Iterate over options
//
// Note: for duplicates only the last one should be used, but not in any case, see
// https://developers.google.com/protocol-buffers/docs/proto3#maps)
//
// "... When parsing from the wire or when merging, if there are duplicate map keys the last key seen is used.
// When parsing a map from text format, parsing may fail if there are duplicate keys...."
for o_num, o_val := range s.Options {
o_str := fmt.Sprintf("dhcp-option=%d", o_num)
if len(o_val) > 0 {
o_str += fmt.Sprintf(",%s\n", o_val)
} else {
o_str += "\n"
}
config += o_str
}
config += fmt.Sprintf("log-dhcp\n") //extensive logging by default
if (!s.NotAuthoritative) { config += fmt.Sprintf("dhcp-authoritative\n") }
//Iterate over static hosts
for _,host := range s.StaticHosts {
config+=fmt.Sprintf("dhcp-host=%s,%s\n", host.Mac, host.Ip)
}
return
}