/* * Copyright (c) 2014 tabletoptool.com team. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Contributors: * rptools.com team - initial implementation * tabletoptool.com team - further development */ package com.t3.util; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Font; import java.io.IOException; import java.net.Inet4Address; import java.net.InterfaceAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingConstants; import net.sbbi.upnp.Discovery; import net.sbbi.upnp.impls.InternetGatewayDevice; import net.sbbi.upnp.messages.ActionResponse; import net.sbbi.upnp.messages.UPNPResponseException; import org.apache.log4j.Logger; import com.t3.client.TabletopTool; import com.t3.swing.SwingUtil; /** * @author Phil Wright */ public class UPnPUtil { private static final Logger log = Logger.getLogger(UPnPUtil.class); private static int discoveryTimeout = 5000; // Should be made a preference setting private static Map<InternetGatewayDevice, NetworkInterface> igds; private static List<InternetGatewayDevice> mappings; private static JDialog dialog = null; private static JPanel panel = new JPanel(new BorderLayout()); private static Font labelFont = new Font("Dialog", Font.BOLD, 14); private static JLabel label = new JLabel("", SwingConstants.CENTER); private static void showMessage(String device, String msg) { if (dialog == null) { dialog = new JDialog(TabletopTool.getFrame()); dialog.setContentPane(panel); panel.add(label, BorderLayout.CENTER); label.setFont(labelFont); } if (device == null) { dialog.setVisible(false); } else { dialog.setTitle("Scanning device " + device); label.setText(msg); Dimension d = label.getMinimumSize(); d.width += 50; d.height += 50; label.setPreferredSize(d); dialog.pack(); SwingUtil.centerOver(dialog, TabletopTool.getFrame()); dialog.setVisible(true); } } public static boolean findIGDs() { igds = new HashMap<InternetGatewayDevice, NetworkInterface>(); try { Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces(); while (e.hasMoreElements()) { NetworkInterface ni = e.nextElement(); try { if (ni.isUp() && !ni.isLoopback() && !ni.isVirtual()) { int found = 0; try { if (log.isInfoEnabled()) log.info("UPnP: Trying interface " + ni.getDisplayName()); InternetGatewayDevice[] thisNI; showMessage(ni.getDisplayName(), "Looking for gateway devices on " + ni.getDisplayName()); thisNI = InternetGatewayDevice.getDevices(discoveryTimeout, Discovery.DEFAULT_TTL, Discovery.DEFAULT_MX, ni); showMessage(null, null); if (thisNI != null) { for (InternetGatewayDevice igd : thisNI) { found++; if (log.isInfoEnabled()) log.info("UPnP: Found IGD: " + igd.getIGDRootDevice().getModelName()); if (igds.put(igd, ni) != null) { // There was a previous mapping for this IGD! It's unlikely to have two NICs on the // the same network segment, but it IS possible. For example, both a wired and // wireless connection using the same router as the gateway. For our purposes it // doesn't really matter which one we use, but in the future we should give the // user a choice. // FIXME We SHOULD be using the "networking binding order" (Windows) // or "network service order" on OSX. if (log.isInfoEnabled()) log.info("UPnP: This was not the first time this IGD was found!"); } } } } catch (IOException ex) { showMessage(null, null); // some IO Exception occurred during communication with device log.warn("While searching for internet gateway devices", ex); } if (log.isInfoEnabled()) log.info("Found " + found + " IGDs on interface " + ni.getDisplayName()); } } catch (SocketException se) { continue; } } } catch (SocketException se) { // Nothing to do, but we DO want the 'mappings' member to be initialized } mappings = new ArrayList<InternetGatewayDevice>(igds.size()); return !igds.isEmpty(); } public static boolean openPort(int port) { if (igds == null || igds.isEmpty()) { findIGDs(); } if (igds == null || igds.isEmpty()) { TabletopTool.showError("UPnP Error - No Internet Gateway Devices found.<br><br>UPnP port mapping will not be available."); return false; } for (InternetGatewayDevice gd : igds.keySet()) { NetworkInterface ni = null; String localHostIP = "(NULL)"; try { ni = igds.get(gd); switch (ni.getInterfaceAddresses().size()) { case 0: log.error("IGD shows up in list of IGDs, but no NICs stored therein?!"); break; case 1: localHostIP = ni.getInterfaceAddresses().get(0).getAddress().getHostAddress(); break; default: for (Iterator<InterfaceAddress> iter = ni.getInterfaceAddresses().iterator(); iter.hasNext();) { InterfaceAddress ifAddr = iter.next(); if (ifAddr.getAddress() instanceof Inet4Address) { localHostIP = ifAddr.getAddress().getHostAddress(); if (log.isInfoEnabled()) log.info("IP address " + localHostIP + " on interface " + ni.getDisplayName()); } } break; } boolean mapped = gd.addPortMapping("TabletopTool", null, port, port, localHostIP, 0, "TCP"); if (mapped) { mappings.add(gd); if (log.isInfoEnabled()) log.info("UPnP: Port " + port + " mapped on " + ni.getDisplayName() + " at address " + localHostIP); } } catch (UPNPResponseException respEx) { // oops the IGD did not like something !! log.error("UPnP Error 1: Could not add port mapping on device " + ni.getDisplayName() + ", IP address " + localHostIP, respEx); } catch (IOException ioe) { log.error("UPnP Error 2: Could not add port mapping on device " + ni.getDisplayName() + ", IP address " + localHostIP, ioe); } } if (mappings.isEmpty()) TabletopTool.showError("UPnP: found " + igds.size() + " IGDs but no port mapping succeeded!?"); return !mappings.isEmpty(); } public static boolean closePort(int port) { if (igds == null || igds.isEmpty()) return true; int count = 0; for (Iterator<InternetGatewayDevice> iter = igds.keySet().iterator(); iter.hasNext();) { InternetGatewayDevice gd = iter.next(); try { ActionResponse actResp = gd.getSpecificPortMappingEntry(null, port, "TCP"); if (actResp != null && "TabletopTool".equals(actResp.getOutActionArgumentValue("NewPortMappingDescription"))) { // NewInternalPort=51234 // NewEnabled=1 // NewInternalClient=192.168.0.30 // NewLeaseDuration=0 // NewPortMappingDescription=TabletopTool boolean unmapped = gd.deletePortMapping(null, port, "TCP"); if (unmapped) { iter.remove(); count++; if (log.isInfoEnabled()) log.info("UPnP: Port unmapped from " + igds.get(gd).getDisplayName()); } else { if (log.isInfoEnabled()) log.info("UPnP: Failed to unmap port from " + igds.get(gd).getDisplayName()); } } } catch (IOException e) { log.info("UPnP: IOException while talking to IGD", e); } catch (UPNPResponseException e) { log.info("UPnP: UPNPResponseException while talking to IGD", e); } } return count > 0; } public static int getDiscoveryTimeout() { return discoveryTimeout; } public static void setDiscoveryTimeout(int discoveryTimeout) { UPnPUtil.discoveryTimeout = discoveryTimeout; } }