Added DHCP server and client support for service. ToDo: CLI implementation for DHCP modes

This commit is contained in:
mame82 2018-05-11 13:57:39 +00:00
parent ef57825312
commit 9553686940
4 changed files with 326 additions and 71 deletions

View File

@ -1,65 +0,0 @@
package service
import (
pb "../proto"
"fmt"
"io/ioutil"
"os"
)
/*
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)
*/
func defaultLeaseFile(s *pb.DHCPServerSettings) (lf string) {
return fmt.Sprintf("/tmp/dnsmasq_%s.leases", s.ListenInterface) //default lease file
}
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)
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", defaultLeaseFile(s)) //default lease file
}
//Iterate over Ranges
for _, pRange := range s.Ranges {
config += fmt.Sprintf("dhcp-range=%s,%s,%s\n", pRange.RangeLower, pRange.RangeUpper, pRange.LeaseTime)
}
//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") }
return
}

View File

@ -7,19 +7,28 @@ import (
func GetDefaultNetworkSettingsUSB() (*pb.EthernetInterfaceSettings) {
//configure 172.24.0.1/255.255.255.252 for usbeth
ifSettings := &pb.EthernetInterfaceSettings {
Enabled: false,
Enabled: true,
Name: USB_ETHERNET_BRIDGE_NAME,
IpAddress4: "172.16.0.1",
Netmask4: "255.255.255.252",
Mode: pb.EthernetInterfaceSettings_MANUAL,
Mode: pb.EthernetInterfaceSettings_DHCP_SERVER,
DhcpServerSettings: GetDefaultDHCPConfigUSB(),
}
return ifSettings
}
func GetDefaultNetworkSettingsWiFi() (*pb.EthernetInterfaceSettings) {
ifSettings := &pb.EthernetInterfaceSettings {
Enabled: true,
Name: "wlan0",
Mode: pb.EthernetInterfaceSettings_DHCP_CLIENT,
}
return ifSettings
}
func GetDefaultDHCPConfigUSB() (settings *pb.DHCPServerSettings) {
settings = &pb.DHCPServerSettings{
CallbackScript: "/bin/evilscript",
//CallbackScript: "/bin/evilscript",
DoNotBindInterface: false, //only bind to given interface
ListenInterface: USB_ETHERNET_BRIDGE_NAME,
LeaseFile: "/tmp/dnsmasq_" + USB_ETHERNET_BRIDGE_NAME + ".leases",
@ -47,14 +56,14 @@ func GetDefaultLEDSettings() (res pb.LEDSettings) {
func GetDefaultGadgetSettings() (res pb.GadgetSettings) {
res = pb.GadgetSettings{
Enabled: false,
Enabled: true,
Vid: "0x1d6b",
Pid: "0x1337",
Manufacturer: "MaMe82",
Product: "P4wnP1 by MaMe82",
Serial: "deadbeef1337",
Use_CDC_ECM: false,
Use_RNDIS: false,
Use_RNDIS: true,
Use_HID_KEYBOARD: false,
Use_HID_MOUSE: false,
Use_HID_RAW: false,

259
service/dhcp.go Normal file
View File

@ -0,0 +1,259 @@
package service
import (
pb "../proto"
"fmt"
"io/ioutil"
"os"
"os/exec"
"log"
"errors"
"strconv"
"syscall"
"strings"
)
/*
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 fmt.Sprintf("/tmp/dnsmasq_%s.leases", 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", nameIface)
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_r := IsDHCPServerRunning(nameIface)
if err != nil { return errors.New(fmt.Sprintf("Error fetching state of DHCP server: %v\n", err_r)) }
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))
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)
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 {
config += fmt.Sprintf("dhcp-range=%s,%s,%s\n", pRange.RangeLower, pRange.RangeUpper, pRange.LeaseTime)
}
//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") }
return
}

View File

@ -21,6 +21,12 @@ func InitDefaultNetworkSettings() (err error) {
err = ConfigureInterface(GetDefaultNetworkSettingsUSB())
if err != nil { return }
}
wifiEthActive, _ := CheckInterfaceExistence("wlan0")
if wifiEthActive {
err = ConfigureInterface(GetDefaultNetworkSettingsWiFi())
if err != nil { return }
}
return
}
@ -112,6 +118,12 @@ func ConfigureInterface(settings *pb.EthernetInterfaceSettings) (err error) {
iface, err := net.InterfaceByName(settings.Name)
if err != nil { return err }
//stop DHCP server / client if still running
running, _, err := IsDHCPServerRunning(settings.Name)
if (err == nil) && running {StopDHCPServer(settings.Name)}
running, _, err = IsDHCPClientRunning(settings.Name)
if (err == nil) && running {StopDHCPClient(settings.Name)}
switch settings.Mode {
case pb.EthernetInterfaceSettings_MANUAL:
//Generate net
@ -124,7 +136,6 @@ func ConfigureInterface(settings *pb.EthernetInterfaceSettings) (err error) {
log.Printf("Setting Interface %s to IP %s\n", iface.Name, settings.IpAddress4)
netlink.NetworkLinkAddIp(iface, net.ParseIP(settings.IpAddress4), ipNet)
//ToDo: Disabling doesn't work in gadget settings, but from test.go
if settings.Enabled {
log.Printf("Setting Interface %s to UP\n", iface.Name)
err = netlink.NetworkLinkUp(iface)
@ -133,6 +144,47 @@ func ConfigureInterface(settings *pb.EthernetInterfaceSettings) (err error) {
err = netlink.NetworkLinkDown(iface)
}
if err != nil { return err }
case pb.EthernetInterfaceSettings_DHCP_SERVER:
//Generate net
ipNet, err := IpNetFromIPv4AndNetmask(settings.IpAddress4, settings.Netmask4)
if err != nil { return err }
//Flush old IPs
netlink.NetworkLinkFlush(iface)
//set IP
log.Printf("Setting Interface %s to IP %s\n", iface.Name, settings.IpAddress4)
netlink.NetworkLinkAddIp(iface, net.ParseIP(settings.IpAddress4), ipNet)
if settings.Enabled {
log.Printf("Setting Interface %s to UP\n", iface.Name)
err = netlink.NetworkLinkUp(iface)
//check DhcpServerSettings
if settings.DhcpServerSettings == nil {
err = errors.New(fmt.Sprintf("Ethernet configuration for interface %s is set to DHCP Server mode, but doesn't provide DhcpServerSettings", settings.Name))
log.Println(err)
return err
}
ifName := settings.Name
confName := NameConfigFileDHCPSrv(ifName)
err = DHCPCreateConfigFile(settings.DhcpServerSettings, confName)
if err != nil {return err}
//stop already running DHCPServers for the interface
StopDHCPServer(ifName)
//start the DHCP server
err = StartDHCPServer(ifName, confName)
if err != nil {return err}
} else {
log.Printf("Setting Interface %s to DOWN\n", iface.Name)
err = netlink.NetworkLinkDown(iface)
}
if err != nil { return err }
case pb.EthernetInterfaceSettings_DHCP_CLIENT:
netlink.NetworkLinkFlush(iface)
if settings.Enabled {
StartDHCPClient(settings.Name)
}
}
return nil