package com.faforever.client.upnp;
import org.bitlet.weupnp.GatewayDevice;
import org.bitlet.weupnp.GatewayDiscover;
import org.bitlet.weupnp.PortMappingEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.xml.sax.SAXException;
import javax.annotation.PreDestroy;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.Objects;
public class WeUpnpServiceImpl implements UpnpService {
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final String UDP = "UDP";
@Value("${upnp.timeout}")
int upnpTimeout;
private GatewayDevice validGateway;
private int mappedPort;
@Override
public void forwardPort(int port) {
GatewayDiscover discover = new GatewayDiscover();
logger.debug("Looking for UPnP capable gateway");
try {
discover.setTimeout(upnpTimeout);
discover.discover();
validGateway = discover.getValidGateway();
if (validGateway == null) {
logger.info("Could not find a UPnP capable gateway");
return;
}
logger.info("Found UPnP capable gateway at {}", validGateway.getPresentationURL());
String localAddress = validGateway.getLocalAddress().getHostAddress();
logger.debug("Looking for existing port mapping for {}:{}", localAddress, port);
PortMappingEntry portMappingEntry = new PortMappingEntry();
boolean mappingExists = validGateway.getSpecificPortMappingEntry(port, UDP, portMappingEntry);
if (mappingExists) {
String mappedIp = portMappingEntry.getInternalClient();
int mappedPort = portMappingEntry.getInternalPort();
if (Objects.equals(mappedIp, localAddress) && Objects.equals(mappedPort, port)) {
logger.info("Port {} is already mapped to {}:{}, not changing anything", port, localAddress, port);
return;
} else {
logger.info("Port {} is mapped to {}:{}. Replacing entry.", port, localAddress, port);
deletePortMapping(port);
}
}
addPortMapping(port, localAddress);
} catch (IOException | SAXException | ParserConfigurationException e) {
throw new RuntimeException(e);
}
}
private void deletePortMapping(int port) throws IOException, SAXException {
if (validGateway != null && !validGateway.deletePortMapping(port, UDP)) {
logger.warn("Mapping for port {} could not be removed", port);
}
}
private void addPortMapping(int port, String localAddress) throws IOException, SAXException {
boolean added = validGateway.addPortMapping(port, port, localAddress, UDP, "Downlord's FAF Client");
if (!added) {
logger.warn("Port {} could not be mapped to {}:{}", port, localAddress, port);
} else {
mappedPort = port;
logger.info("Port {} has been mapped to {}:{}", port, localAddress, port);
}
}
@PreDestroy
void close() throws IOException, SAXException {
deletePortMapping(mappedPort);
}
}