DhcpLeaseParser.java
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bremersee.dccon.repository.cli;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import lombok.extern.slf4j.Slf4j;
import org.bremersee.dccon.model.DhcpLease;
import org.springframework.util.StringUtils;
/**
* The dhcp lease list parser parses the response of the linux command line tool {@code
* dhcp-lease-list}.
*
* <p>A response of {@code dhcp-lease-list} looks like this:
* <pre>
* MAC b8:xx:xx:xx:xx:xx IP 192.168.1.109 HOSTNAME ukelei BEGIN 2019-08-18 11:20:33 END 2019-08-18 11:50:33 MANUFACTURER Apple, Inc.
* MAC ac:xx:xx:xx:xx:yy IP 192.168.1.188 HOSTNAME -NA- BEGIN 2019-08-18 11:25:48 END 2019-08-18 11:55:48 MANUFACTURER Super Micro Computer, Inc.
* </pre>
*
* @author Christian Bremer
*/
public interface DhcpLeaseParser extends CommandExecutorResponseParser<List<DhcpLease>> {
/**
* The constant MAC.
*/
String MAC = "MAC ";
/**
* The constant IP.
*/
String IP = " IP ";
/**
* The constant HOSTNAME.
*/
String HOSTNAME = " HOSTNAME ";
/**
* The constant HOSTNAME_UNKNOWN.
*/
String HOSTNAME_UNKNOWN = "-NA-";
/**
* The constant BEGIN.
*/
String BEGIN = " BEGIN ";
/**
* The constant END.
*/
String END = " END ";
/**
* The constant MANUFACTURER.
*/
String MANUFACTURER = " MANUFACTURER ";
/**
* Default parser dhcp leases parser.
*
* @return the dhcp leases parser
*/
static DhcpLeaseParser defaultParser() {
return new Default();
}
/**
* Default parser dhcp leases parser.
*
* @param unknownHostConverter the unknown host converter
* @return the dhcp leases parser
*/
@SuppressWarnings("unused")
static DhcpLeaseParser defaultParser(BiFunction<String, String, String> unknownHostConverter) {
return new Default(unknownHostConverter);
}
/**
* The default parser.
*/
@Slf4j
class Default implements DhcpLeaseParser {
private BiFunction<String, String, String> unknownHostConverter;
/**
* Instantiates a new default parser.
*/
Default() {
this(null);
}
/**
* Instantiates a new default parser.
*
* <p>The unknown host converter converts the unknown host name {@link
* DhcpLeaseParser#HOSTNAME_UNKNOWN} into another host name. The parameters of the function are
* MAC and IP.
*
* @param unknownHostConverter the unknown host converter
*/
Default(
BiFunction<String, String, String> unknownHostConverter) {
if (unknownHostConverter == null) {
this.unknownHostConverter = (mac, ip) -> "dhcp-" + ip.replace(".", "-");
} else {
this.unknownHostConverter = unknownHostConverter;
}
}
@Override
public List<DhcpLease> parse(final CommandExecutorResponse response) {
if (!response.stdoutHasText()) {
log.warn("Dhcp lease list command did not produce output. Error is [{}].",
response.getStderr());
return Collections.emptyList();
}
final String output = response.getStdout();
try (final BufferedReader reader = new BufferedReader(new StringReader(output))) {
return parseDhcpLeaseList(reader);
} catch (IOException e) {
log.error("Parsing dhcp lease list failed:\n" + output + "\n", e);
return Collections.emptyList();
}
}
private List<DhcpLease> parseDhcpLeaseList(final BufferedReader reader) throws IOException {
final List<DhcpLease> leases = new ArrayList<>();
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
final String mac = findDhcpLeasePart(line, MAC, IP);
final String ip = findDhcpLeasePart(line, IP, HOSTNAME);
String hostname = findDhcpLeasePart(line, HOSTNAME, BEGIN);
if (HOSTNAME_UNKNOWN.equalsIgnoreCase(hostname) && ip != null) {
hostname = unknownHostConverter.apply(mac, ip);
}
final String begin = findDhcpLeasePart(line, BEGIN, END);
final String end = findDhcpLeasePart(line, END, MANUFACTURER);
final String manufacturer = findDhcpLeasePart(line, MANUFACTURER, null);
if (mac != null && ip != null && hostname != null && begin != null && end != null) {
final DhcpLease lease = new DhcpLease(
mac.replace("-", ":").trim().toLowerCase(),
ip,
hostname,
parseDhcpLeaseTime(begin),
parseDhcpLeaseTime(end),
manufacturer);
leases.add(lease);
}
}
return leases;
}
private String findDhcpLeasePart(String line, String field, String nextField) {
if (!StringUtils.hasText(line) || !StringUtils.hasText(field)) {
return null;
}
int start = line.indexOf(field);
if (start < 0) {
return null;
}
start = start + field.length();
int end = StringUtils.hasText(nextField)
? line.indexOf(nextField, start)
: line.length();
if (end < 0 || end <= start) {
return null;
}
return line.substring(start, end).trim();
}
private OffsetDateTime parseDhcpLeaseTime(String time) {
if (!StringUtils.hasText(time)) {
return null;
}
final LocalDateTime localDateTime = LocalDateTime.parse(
time,
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
// As of https://linux.die.net/man/5/dhcpd.leases the time zone is always UTC
return OffsetDateTime.of(localDateTime, ZoneOffset.UTC);
}
}
}