Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(linux.net): DHCP server selection method #5578

Merged
merged 13 commits into from
Nov 25, 2024
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*******************************************************************************
* Copyright (c) 2011, 2023 Eurotech and/or its affiliates and others
*
* Copyright (c) 2011, 2024 Eurotech and/or its affiliates and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
*
* SPDX-License-Identifier: EPL-2.0
*
*
* Contributors:
* Eurotech
*******************************************************************************/
Expand Down Expand Up @@ -58,7 +58,8 @@ public DhcpServerManager(CommandExecutorService service) {

public static DhcpServerTool getTool() {
if (dhcpServerTool == DhcpServerTool.NONE) {
if (LinuxNetworkUtil.toolExists(DhcpServerTool.DNSMASQ.getValue())) {
if (LinuxNetworkUtil.toolExists(DhcpServerTool.DNSMASQ.getValue())
&& LinuxNetworkUtil.systemdSystemUnitExists(DhcpServerTool.DNSMASQ.getValue() + ".service")) {
dhcpServerTool = DhcpServerTool.DNSMASQ;
} else if (LinuxNetworkUtil.toolExists(DhcpServerTool.UDHCPD.getValue())) {
dhcpServerTool = DhcpServerTool.UDHCPD;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*******************************************************************************/
package org.eclipse.kura.linux.net.dhcp.server;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -42,10 +43,10 @@ public class DnsmasqTool implements DhcpLinuxTool {
private String globalConfigFilename = "/etc/dnsmasq.d/dnsmasq-globals.conf";
private static final String GLOBAL_CONFIGURATION = "port=0\nbind-dynamic\n";

static final Command IS_ACTIVE_COMMAND = new Command(new String[] { "systemctl", "is-active", "--quiet",
DhcpServerTool.DNSMASQ.getValue() });
static final Command RESTART_COMMAND = new Command(new String[] { "systemctl", "restart",
DhcpServerTool.DNSMASQ.getValue() });
static final String[] IS_ACTIVE_COMMANDLINE = new String[] { "systemctl", "is-active", "--quiet",
DhcpServerTool.DNSMASQ.getValue() };
static final String[] RESTART_COMMANDLINE = new String[] { "systemctl", "restart",
DhcpServerTool.DNSMASQ.getValue() };

private CommandExecutorService executorService;
private Map<String, byte[]> configsLastHash = Collections.synchronizedMap(new HashMap<>());
Expand All @@ -57,7 +58,7 @@ public DnsmasqTool(CommandExecutorService service) {

@Override
public boolean isRunning(String interfaceName) throws KuraProcessExecutionErrorException {
CommandStatus status = this.executorService.execute(IS_ACTIVE_COMMAND);
CommandStatus status = this.executorService.execute(new Command(IS_ACTIVE_COMMANDLINE));

boolean isRunning;
try {
Expand Down Expand Up @@ -85,11 +86,19 @@ public CommandStatus startInterface(String interfaceName) throws KuraProcessExec
"Failed to start DHCP server for interface: " + interfaceName);
}

CommandStatus restartStatus = this.executorService.execute(RESTART_COMMAND);
Command restartCommand = new Command(RESTART_COMMANDLINE);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream err = new ByteArrayOutputStream();
restartCommand.setErrorStream(err);
restartCommand.setOutputStream(out);
CommandStatus restartStatus = this.executorService.execute(restartCommand);

if (!restartStatus.getExitStatus().isSuccessful()) {
logger.error("dnsmasq Systemd unit startup failed. Check if the tool is properly installed on the system.");
logger.error("dnsmasq stderr {}", new String(err.toByteArray(), StandardCharsets.UTF_8));
logger.error("dnsmasq stdout {}", new String(out.toByteArray(), StandardCharsets.UTF_8));
removeInterfaceConfig(interfaceName);
restartStatus = this.executorService.execute(RESTART_COMMAND);
restartStatus = this.executorService.execute(restartCommand);
}

return restartStatus;
Expand All @@ -100,7 +109,7 @@ public boolean disableInterface(String interfaceName) throws KuraProcessExecutio
boolean isInterfaceDisabled = true;

if (removeInterfaceConfig(interfaceName)) {
CommandStatus status = this.executorService.execute(RESTART_COMMAND);
CommandStatus status = this.executorService.execute(new Command(RESTART_COMMANDLINE));
isInterfaceDisabled = status.getExitStatus().isSuccessful();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*******************************************************************************
* Copyright (c) 2011, 2022 Eurotech and/or its affiliates and others
*
* Copyright (c) 2011, 2024 Eurotech and/or its affiliates and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
*
* SPDX-License-Identifier: EPL-2.0
*
*
* Contributors:
* Eurotech
*******************************************************************************/
Expand Down Expand Up @@ -44,12 +44,9 @@

public class LinuxNetworkUtil {

public static final String ACCESS_POINT_INTERFACE_SUFFIX = "_ap";

private static final String ETHTOOL_COMMAND = "ethtool";

private static final Logger logger = LoggerFactory.getLogger(LinuxNetworkUtil.class);

public static final String ACCESS_POINT_INTERFACE_SUFFIX = "_ap";
private static Map<String, LinuxIfconfig> ifconfigs = new HashMap<>();
private static final String[] IGNORE_IFACES = { "can", "sit", "mon.wlan" };
private static final ArrayList<String> TOOLS = new ArrayList<>();
Expand All @@ -65,13 +62,12 @@ public class LinuxNetworkUtil {
private static final String IFCONFIG = "ifconfig";
private static final String IWCONFIG = "iwconfig";
private static final String IP = "ip";

private static final String LINE_MSG = "line: {}";

private static final String ERR_EXECUTING_CMD_MSG = "error executing command --- {} --- exit value={}";

private static final String FAKE_MAC_ADDRESS = "12:34:56:78:ab:cd";

private static final String ETHTOOL_COMMAND = "ethtool";
private static final String[] DEFAULT_PATH = new String[] { "/sbin/", "/usr/sbin/", "/bin/", "/usr/bin/" };
mattdibi marked this conversation as resolved.
Show resolved Hide resolved
private static final ProcessBuilder PROCESS_BUILDER = new ProcessBuilder();
private final CommandExecutorService executorService;
private final WifiOptions wifiOptions;

Expand Down Expand Up @@ -264,22 +260,59 @@ private boolean isWifiLinkUpInternal(String ifaceName) throws KuraException {
}

public static boolean toolExists(String tool) {
boolean ret = false;
final String[] searchFolders = new String[] { "/sbin/", "/usr/sbin/", "/bin/", "/usr/bin/" };
return getToolPath(tool).isPresent();
}

if (TOOLS.contains(tool)) {
ret = true;
public static Optional<String> getToolPath(String tool) {
Optional<String> optionalToolPath = getCachedTool(tool);
if (optionalToolPath.isPresent()) {
return optionalToolPath;
} else {
for (String folder : searchFolders) {
File fTool = new File(folder + tool);
if (fTool.exists()) {
TOOLS.add(tool);
ret = true;
break;
for (String folder : DEFAULT_PATH) {
String toolPath = folder + tool;
File toolFile = new File(toolPath);
if (toolFile.exists()) {
TOOLS.add(toolPath);
return Optional.of(toolPath);
}
}
}
return ret;
return Optional.empty();
}

private static Optional<String> getCachedTool(String tool) {
return TOOLS.stream().filter(item -> item.endsWith(tool)).findFirst();
}

/**
* Checks if the given Systemd system unit is installed.
* The result is based on the exit code of "systemctl status <unitName>"
* as presented in https://www.man7.org/linux/man-pages/man1/systemctl.1.html#EXIT_STATUS
*
* @param unitName
* the name of the Systemd system unit
* @return true if the unit is installed
*/
public static boolean systemdSystemUnitExists(String unitName) {
Optional<String> optionalSystemctlPath = getToolPath("systemctl");
if (!optionalSystemctlPath.isPresent()) {
logger.debug("Systemctl command not found in default paths");
return false;
}
PROCESS_BUILDER.command(optionalSystemctlPath.get(), "status", unitName);
Process process;
try {
process = PROCESS_BUILDER.start();
int exitCode = process.waitFor();
return exitCode < 4;
} catch (IOException e) {
logger.error("Cannot check {} unit existence", unitName, e);
return false;
} catch (InterruptedException e1) {
Thread.currentThread().interrupt();
logger.error("Cannot check {} unit existence", unitName, e1);
return false;
}
}

/**
Expand Down Expand Up @@ -1124,7 +1157,7 @@ public void createApNetworkInterface(String ifaceName, String dedicatedApInterfa
return;
}

CommandStatus status = this.executeCommand(formIwDevIfaceInterfaceAddAp(ifaceName, dedicatedApInterface));
CommandStatus status = executeCommand(formIwDevIfaceInterfaceAddAp(ifaceName, dedicatedApInterface));
mattdibi marked this conversation as resolved.
Show resolved Hide resolved

if (!status.getExitStatus().isSuccessful()) {
throw new KuraException(KuraErrorCode.OS_COMMAND_ERROR,
Expand All @@ -1137,7 +1170,7 @@ public void setNetworkInterfaceMacAddress(String ifaceName) throws KuraException
return;
}

CommandStatus status = this.executeCommand(formIpLinkSetAddress(ifaceName, FAKE_MAC_ADDRESS));
CommandStatus status = executeCommand(formIpLinkSetAddress(ifaceName, FAKE_MAC_ADDRESS));

if (!status.getExitStatus().isSuccessful()) {
throw new KuraException(KuraErrorCode.OS_COMMAND_ERROR,
Expand All @@ -1150,7 +1183,7 @@ public void setNetworkInterfaceLinkUp(String ifaceName) throws KuraException {
return;
}

CommandStatus status = this.executeCommand(formIpLinkSetStatus(ifaceName, "up"));
CommandStatus status = executeCommand(formIpLinkSetStatus(ifaceName, "up"));

if (!status.getExitStatus().isSuccessful()) {
throw new KuraException(KuraErrorCode.OS_COMMAND_ERROR, "Failed to set link up for interface " + ifaceName);
Expand All @@ -1162,7 +1195,7 @@ public void setNetworkInterfaceLinkDown(String ifaceName) throws KuraException {
return;
}

CommandStatus status = this.executeCommand(formIpLinkSetStatus(ifaceName, "down"));
CommandStatus status = executeCommand(formIpLinkSetStatus(ifaceName, "down"));

if (!status.getExitStatus().isSuccessful()) {
throw new KuraException(KuraErrorCode.OS_COMMAND_ERROR, "Failed to set link up for interface " + ifaceName);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*******************************************************************************
* Copyright (c) 2017, 2023 Eurotech and/or its affiliates and others
*
* Copyright (c) 2017, 2024 Eurotech and/or its affiliates and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
*
* SPDX-License-Identifier: EPL-2.0
*
*
* Contributors:
* Eurotech
******************************************************************************/
Expand All @@ -27,9 +27,12 @@
import org.eclipse.kura.linux.net.dhcp.server.DnsmasqLeaseReader;
import org.eclipse.kura.linux.net.dhcp.server.UdhcpdConfigConverter;
import org.eclipse.kura.linux.net.dhcp.server.UdhcpdLeaseReader;
import org.eclipse.kura.linux.net.util.LinuxNetworkUtil;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

public class DhcpServerManagerTest {
Expand All @@ -50,6 +53,8 @@ public class DhcpServerManagerTest {
private Optional<DhcpServerConfigConverter> returnedConfigConverter;
private Optional<DhcpServerLeaseReader> returnedLeaseReader;
private Exception occurredException;
private MockedStatic<LinuxNetworkUtil> mockedLinuxNetworkUtil;
private DhcpServerTool tool;

/*
* Scenarios
Expand Down Expand Up @@ -268,6 +273,63 @@ public void shouldReturnLeaseReaderForNone() throws NoSuchFieldException {
thenReturnedLeaseReaderIsEmpty();
}

@Test
public void shouldGetDnsmasqToolIfBinaryAndUnitArePresent() throws NoSuchFieldException {
givenLinuxNetworkUtil("dnsmasq", Optional.of("dnsmasq.service"));
givenDhcpServerManager(DhcpServerTool.NONE);

whenGetTool();

thenToolIs(DhcpServerTool.DNSMASQ);
}

@Test
public void shouldNotGetDnsmasqToolIfOnlyBinaryIsPresent() throws NoSuchFieldException {
givenLinuxNetworkUtil("dnsmasq", Optional.empty());
givenDhcpServerManager(DhcpServerTool.NONE);

whenGetTool();

thenToolIs(DhcpServerTool.NONE);
}

@Test
public void shouldGetDhcpdTool() throws NoSuchFieldException {
givenLinuxNetworkUtil("dhcpd", Optional.empty());
givenDhcpServerManager(DhcpServerTool.NONE);

whenGetTool();

thenToolIs(DhcpServerTool.DHCPD);
}

@Test
public void shouldGetUdhcpdTool() throws NoSuchFieldException {
givenLinuxNetworkUtil("udhcpd", Optional.empty());
givenDhcpServerManager(DhcpServerTool.NONE);

whenGetTool();

thenToolIs(DhcpServerTool.UDHCPD);
}

@Test
public void shouldNotGetAnyTool() throws NoSuchFieldException {
givenLinuxNetworkUtil("", Optional.empty());
givenDhcpServerManager(DhcpServerTool.NONE);

whenGetTool();

thenToolIs(DhcpServerTool.NONE);
}

@After
public void deregisterStaticMocks() {
if (this.mockedLinuxNetworkUtil != null) {
this.mockedLinuxNetworkUtil.close();
}
}

/*
* Steps
*/
Expand All @@ -281,7 +343,16 @@ private void givenDhcpServerManager(DhcpServerTool dhcpServerTool) throws NoSuch

TestUtil.setFieldValue(new DhcpServerManager(null), "dhcpServerTool", dhcpServerTool);

this.dhcpServerManager = new DhcpServerManager(executorMock);
this.dhcpServerManager = new DhcpServerManager(this.executorMock);
}

private void givenLinuxNetworkUtil(String toolName, Optional<String> unitName) {
this.mockedLinuxNetworkUtil = Mockito.mockStatic(LinuxNetworkUtil.class);
this.mockedLinuxNetworkUtil.when(() -> LinuxNetworkUtil.toolExists(toolName)).thenReturn(true);
if (unitName.isPresent()) {
this.mockedLinuxNetworkUtil.when(() -> LinuxNetworkUtil.systemdSystemUnitExists(unitName.get()))
.thenReturn(true);
}
}

/*
Expand Down Expand Up @@ -328,6 +399,14 @@ private void whenGetLeaseReader() {
}
}

private void whenGetTool() {
try {
this.tool = DhcpServerManager.getTool();
} catch (Exception e) {
this.occurredException = e;
}
}

/*
* Then
*/
Expand Down Expand Up @@ -364,4 +443,7 @@ private void thenReturnedLeaseReaderIs(Class<?> dhcpServerLeaseReader) {
assertEquals(dhcpServerLeaseReader, this.returnedLeaseReader.get().getClass());
}

private void thenToolIs(DhcpServerTool expectedTool) {
assertEquals(expectedTool, this.tool);
}
}
Loading
Loading