/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* 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 net.java.sip.communicator.impl.provdisc.dhcp;
import java.net.*;
import java.util.*;
import net.java.sip.communicator.service.netaddr.*;
import net.java.sip.communicator.service.provdisc.event.*;
import net.java.sip.communicator.util.*;
import org.dhcp4java.*;
/**
* Class that will perform DHCP provisioning discovery.
*
* @author Sebastien Vincent
*/
public class DHCPProvisioningDiscover
implements Runnable
{
/**
* Logger.
*/
private final Logger logger
= Logger.getLogger(DHCPProvisioningDiscover.class);
/**
* DHCP socket timeout (in milliseconds).
*/
private static final int DHCP_TIMEOUT = 10000;
/**
* UDP socket.
*/
private DatagramSocket socket = null;
/**
* DHCP transaction number.
*/
private int xid = 0;
/**
* Listening port of the client. Note that the socket will send packet to
* DHCP server on port - 1.
*/
private int port = 6768;
/**
* Option code of the specific provisioning option.
*/
private byte option = (byte)224;
/**
* List of <tt>ProvisioningListener</tt> that will be notified when
* a provisioning URL is retrieved.
*/
private List<DiscoveryListener> listeners =
new ArrayList<DiscoveryListener>();
/**
* Constructor.
*
* @param port port on which we will bound and listen for DHCP response
* @param option code of the specific provisioning option
* @throws Exception if anything goes wrong during initialization
*/
public DHCPProvisioningDiscover(int port, byte option) throws Exception
{
this.port = port;
this.option = option;
socket = new DatagramSocket(port);
xid = new Random().nextInt();
/* set timeout so that we will not blocked forever if we
* have no response from DHCP server
*/
socket.setSoTimeout(DHCP_TIMEOUT);
}
/**
* It sends a DHCPINFORM message from all interfaces and wait for a
* response. Thread stops after first successful answer that contains
* specific option and thus the provisioning URL.
*
* @return provisioning URL
*/
public String discoverProvisioningURL()
{
DHCPPacket inform = new DHCPPacket();
byte macAddress[] = null;
byte zeroIPAddress[] = {0x00, 0x00, 0x00, 0x00};
byte broadcastIPAddr[] = {(byte)255, (byte)255, (byte)255, (byte)255};
DHCPOption dhcpOpts[] = new DHCPOption[1];
List<DHCPTransaction> transactions = new ArrayList<DHCPTransaction>();
try
{
inform.setOp(DHCPConstants.BOOTREQUEST);
inform.setHtype(DHCPConstants.HTYPE_ETHER);
inform.setHlen((byte) 6);
inform.setHops((byte) 0);
inform.setXid(xid);
inform.setSecs((short) 0);
inform.setFlags((short) 0);
inform.setYiaddr(InetAddress.getByAddress(zeroIPAddress));
inform.setSiaddr(InetAddress.getByAddress(zeroIPAddress));
inform.setGiaddr(InetAddress.getByAddress(zeroIPAddress));
//inform.setChaddr(macAddress);
inform.setDhcp(true);
inform.setDHCPMessageType(DHCPConstants.DHCPINFORM);
dhcpOpts[0] = new DHCPOption(
DHCPConstants.DHO_DHCP_PARAMETER_REQUEST_LIST,
new byte[] {option});
inform.setOptions(dhcpOpts);
Enumeration<NetworkInterface> en =
NetworkInterface.getNetworkInterfaces();
while(en.hasMoreElements())
{
NetworkInterface iface = en.nextElement();
Enumeration<InetAddress> enAddr = iface.getInetAddresses();
while(enAddr.hasMoreElements())
{
InetAddress addr = enAddr.nextElement();
/* just take IPv4 address */
if(addr instanceof Inet4Address)
{
NetworkAddressManagerService netaddr =
ProvisioningDiscoveryDHCPActivator.
getNetworkAddressManagerService();
if(!addr.isLoopbackAddress())
{
macAddress = netaddr.getHardwareAddress(iface);
DHCPPacket p = inform.clone();
p.setCiaddr(addr);
p.setChaddr(macAddress);
byte msg[] = p.serialize();
DatagramPacket pkt = new DatagramPacket(msg,
msg.length,
InetAddress.getByAddress(broadcastIPAddr),
port - 1);
DHCPTransaction transaction =
new DHCPTransaction(socket, pkt);
transaction.schedule();
transactions.add(transaction);
msg = null;
pkt = null;
p = null;
}
}
}
}
/* now see if we receive DHCP ACK response and if it contains
* our custom option
*/
boolean found = false;
try
{
DatagramPacket pkt2 = new DatagramPacket(new byte[1500], 1500);
while(!found)
{
/* we timeout after some seconds if no DHCP response are
* received
*/
socket.receive(pkt2);
DHCPPacket dhcp = DHCPPacket.getPacket(pkt2);
if(dhcp.getXid() != xid)
{
continue;
}
DHCPOption optProvisioning = dhcp.getOption(option);
/* notify */
if(optProvisioning != null)
{
found = true;
for(DHCPTransaction t : transactions)
{
t.cancel();
}
return new String(optProvisioning.getValue());
}
}
}
catch(SocketTimeoutException est)
{
logger.warn("Timeout, no DHCP answer received", est);
}
}
catch(Exception e)
{
logger.warn("Exception occurred during DHCP discover", e);
}
for(DHCPTransaction t : transactions)
{
t.cancel();
}
return null;
}
/**
* Thread entry point. It runs <tt>discoverProvisioningURL</tt> in a
* separate thread.
*/
public void run()
{
String url = discoverProvisioningURL();
if(url != null)
{
/* as we run in an asynchronous manner, notify the listener */
DiscoveryEvent evt = new DiscoveryEvent(this, url);
for(DiscoveryListener listener : listeners)
{
listener.notifyProvisioningURL(evt);
}
}
}
/**
* Add a listener that will be notified when the
* <tt>discoverProvisioningURL</tt> has finished.
*
* @param listener <tt>ProvisioningListener</tt> to add
*/
public void addDiscoveryListener(DiscoveryListener listener)
{
if(!listeners.contains(listener))
{
listeners.add(listener);
}
}
/**
* Add a listener that will be notified when the
* <tt>discoverProvisioningURL</tt> has finished.
*
* @param listener <tt>ProvisioningListener</tt> to add
*/
public void removeDiscoveryListener(DiscoveryListener listener)
{
if(listeners.contains(listener))
{
listeners.remove(listener);
}
}
}