mirror of
https://github.com/RoganDawes/P4wnP1_aloa.git
synced 2025-11-15 08:32:06 +01:00
283 lines
9.1 KiB
Go
283 lines
9.1 KiB
Go
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
|
|
}
|
|
|