import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;
public class Reflector implements Runnable {
// valid names found in the config file:
public static final String MODE = "Mode";
public static final String SOURCE_IP = "SourceIP";
public static final String SOURCE_PORT = "SourcePort";
public static final String DEST_IP = "DestIP";
public static final String DEST_PORT = "DestPort";
// valid modes in the config file, unicast to
// multicast or multicast to unicast
public static final String INPUT_UNITOMULTI = "UNI_TO_MULTI";
public static final String INPUT_MULTITOUNI = "MULTI_TO_UNI";
// possible modes the reflector can be set to:
public static final int MODE_NONE = 0;
public static final int MODE_UNI_TO_MULTI = 1;
public static final int MODE_MULTI_TO_UNI = 2;
// variables to indicate source or destination
public static final int SOURCE = 1;
public static final int DEST = 2;
// min and max network ports allowed
public static final int MIN_PORT = 1024;
public static final int MAX_PORT = 65095;
// which mode the reflector is being run in:
private int mode = 0;
// source and destination hold variables:
private Address source;
private Hashtable dest;
private Address hold_dest = null;
// logging toggle and logger class
boolean logging = true;
Logger logger;
public Reflector() {
}
public void run() {
// validate the config file
if (readConfig() != 0) {
System.err.println("Error parsing config file\n");
System.exit(-1);
}
// start the logger
logger = new Logger(logging);
// spawn a thread to listen for packets
ReflectorListener listener = new ReflectorListener(source, mode, logger);
System.out.println("Listening on " + source.toString());
// spawn threads for each source address packets
// are to be forwarded on. Register each thread as
// PacketListenerInterface with the listener thread.
System.out.println("Sending on:");
for (Enumeration e = dest.elements(); e.hasMoreElements();) {
Address a = (Address) e.nextElement();
ReflectorSender sender = new ReflectorSender(a, mode, logger);
sender.start();
listener.addPacketListener((PacketListenerInterface) sender);
System.out.println(" " + a.toString());
}
// start the listener
listener.start();
}
public int readConfig() {
// validate the contents of the config file
BufferedReader input = null;
String name, value, inputLine = null;
dest = new Hashtable();
// open and read the config file
try {
input = new BufferedReader(new FileReader("reflector.conf"));
inputLine = input.readLine();
} catch (IOException e) {
System.err.println("Error reading reflector.conf.");
return (-1);
}
// loop until entire config file is read
while (inputLine != null) {
// skip comments:
if (inputLine.charAt(0) != '#') {
// extract a name/value pair, and branch
// based on the name:
StringTokenizer tokenizer = new StringTokenizer(inputLine, "=");
name = tokenizer.nextToken();
value = tokenizer.nextToken();
if (name == null) {
System.out.println("no name");
continue;
} else if (name.equals(MODE)) {
if (setMode(value) != 0) {
System.err.println("Error setting mode to " + value);
return (-1);
}
} else if (name.equals(SOURCE_IP)) {
if (setSourceIP(value) != 0) {
System.err.println("Error setting src IP address to "
+ value);
return (-1);
}
} else if (name.equals(SOURCE_PORT)) {
if (setSourcePort(value) != 0) {
System.err
.println("Error setting src port to " + value);
return (-1);
}
} else if (name.equals(DEST_IP)) {
if (setDestIP(value) != 0) {
System.err.println("Error setting dest IP address to "
+ value);
return (-1);
}
} else if (name.equals(DEST_PORT)) {
if (setDestPort(value) != 0) {
System.err.println("Error setting dest port to "
+ value);
return (-1);
}
} else {
System.err.println("Skipping invalid config file value: "
+ name);
}
}
// read next line in the config file
try {
inputLine = input.readLine();
} catch (IOException e) {
System.err.println("Error reading reflector.conf.");
return (-1);
}
}
// close the config file
try {
input.close();
} catch (IOException e) {
System.err.println("Error closing reflector.conf.");
return (-1);
}
// validate that the combined contents of the config file
// make sense
if (!isConfigValid()) {
System.err.println("Configuration file is not complete.");
return (-1);
}
return (0);
}
private int setMode(String value) {
// validate and set the mode from the config file
if (value.equals(INPUT_UNITOMULTI)) {
mode = MODE_UNI_TO_MULTI;
return (0);
} else if (value.equals(INPUT_MULTITOUNI)) {
mode = MODE_MULTI_TO_UNI;
return (0);
} else {
return (-1);
}
}
private int setSourceIP(String value) {
// validate and set the source IP from the config file
// call modeToAddress to validate IP address
InetAddress inet = modeToAddress(value, SOURCE);
if (inet == null)
return -1;
if (source != null) {
if (source.getAddress() != null)
System.err.println("Warning: overwriting src address "
+ source.getAddress().getHostAddress() + " with "
+ inet.getHostAddress() + ".");
source.setAddress(inet);
} else {
source = new Address(inet);
}
return (0);
}
private int setSourcePort(String value) {
// validate and set the source port from the config file
int port;
try {
port = Integer.parseInt(value);
} catch (NumberFormatException nfe) {
return (-1);
}
if ((port < MIN_PORT) || (port > 65095))
return (-1);
if (source != null) {
if (source.getPort() != 0)
System.err.println("Warning: overwriting src port "
+ source.getPort() + " with port " + port + ".");
source.setPort(port);
} else {
source = new Address(port);
}
return (0);
}
private int setDestIP(String value) {
// validate and set the dest IP from the config file
// call modeToAddress to validate IP address
InetAddress inet = modeToAddress(value, DEST);
if (inet == null)
return -1;
if (hold_dest != null) {
if (hold_dest.getAddress() != null)
System.err.println("Warning: overwriting dest address "
+ hold_dest.getAddress().getHostAddress() + " with "
+ inet.getHostAddress() + ".");
hold_dest.setAddress(inet);
if (hold_dest.isComplete())
return (addDest());
} else {
hold_dest = new Address(inet);
}
return (0);
}
private int setDestPort(String value) {
// validate and set the dest port from the config file
int port;
try {
port = Integer.parseInt(value);
} catch (NumberFormatException nfe) {
return (-1);
}
if ((port < MIN_PORT) || (port > MAX_PORT))
return (-1);
if (hold_dest != null) {
if (hold_dest.getPort() != 0)
System.err.println("Warning: overwriting dest port "
+ hold_dest.getPort() + " with port " + port + ".");
hold_dest.setPort(port);
if (hold_dest.isComplete())
return (addDest());
} else {
hold_dest = new Address(port);
}
return (0);
}
private int addDest() {
// once both a dest IP and port have been read, add them
// to our vector of all destinations.
switch (mode) {
case MODE_UNI_TO_MULTI:
if (!dest.isEmpty()) {
System.err.println("Warning: dest address overwritten");
dest.clear();
}
dest.put(hold_dest.toString(), hold_dest);
break;
case MODE_MULTI_TO_UNI:
dest.put(hold_dest.toString(), hold_dest);
break;
default:
// no mode set
System.err.println("Destination " + hold_dest.toString()
+ " skipped because no mode set.");
hold_dest = null;
return (-1);
}
hold_dest = null;
return (0);
}
private InetAddress modeToAddress(String value, int type) {
// validate the IP Address based on its text value, its
// type (DEST or SOURCE), and the mode (UNI_TO_MULTI or
// MULTI_TO_UNI). Returns an InetAddress if succesfull and
// null on failure.
InetAddress inet;
if ((type != DEST) && (type != SOURCE)) {
System.err.println("Invalid type passed to modeToAddress (" + type
+ ")");
return (null);
}
switch (mode) {
case MODE_UNI_TO_MULTI:
if (type == DEST)
inet = returnValidMCIP(value);
else
inet = returnValidIP(value);
break;
case MODE_MULTI_TO_UNI:
if (type == DEST)
inet = returnValidIP(value);
else
inet = returnValidMCIP(value);
break;
default:
// no mode set
System.err.println("Error: No Mode Selected.");
return (null);
}
if (inet == null)
System.err.println("Invalid dest IP address (" + value + ").");
return (inet);
}
private InetAddress returnValidIP(String IP) {
// return InetAddress if IP is valid, null otherwise
InetAddress inet;
try {
inet = InetAddress.getByName(IP);
} catch (UnknownHostException e) {
return (null);
}
return (inet);
}
private InetAddress returnValidMCIP(String IP) {
// return InetAddress if IP is valid multicast addr,
// null otherwise
InetAddress inet = returnValidIP(IP);
if (inet.isMulticastAddress()) {
return (inet);
} else {
return (null);
}
}
public boolean isConfigValid() {
// validate that the mode, source IP/port, and
// dest IP(s)/port(s) are all valid and a valid
// combination.
if (mode == MODE_NONE) {
System.err.println("No mode selected.");
return (false);
}
if (!source.isComplete()) {
if ((source.getPort() != 0) && (mode == MODE_UNI_TO_MULTI)) {
// if source is unicast local IP is implied
try {
source.setAddress(InetAddress.getLocalHost());
} catch (UnknownHostException e) {
System.err.println("Incomplete source address.");
return (false);
}
} else {
System.err.println("Incomplete source address.");
return (false);
}
}
if (dest.isEmpty()) {
System.err.println("No destination addresses.");
return (false);
}
for (Enumeration e = dest.elements(); e.hasMoreElements();) {
Address a = (Address) e.nextElement();
if (!a.isComplete()) {
System.err.println("Incompete destination address.");
return (false);
}
}
return (true);
}
public static void main(String args[]) {
Reflector r = new Reflector();
r.run();
}
}
//Address class is used to store an IP address and port
//combination.
class Address {
private InetAddress address = null;
private int port = 0;
public Address(InetAddress address, int port) {
this.address = address;
this.port = port;
}
public Address(InetAddress address) {
this.address = address;
}
public Address(int port) {
this.port = port;
}
public InetAddress getAddress() {
return (address);
}
public int getPort() {
return (port);
}
public void setPort(int port) {
this.port = port;
}
public void setAddress(InetAddress address) {
this.address = address;
}
public boolean isComplete() {
// return true if both IP and port are populated,
// false otherwise.
if ((address != null) && (port != 0))
return (true);
else
return (false);
}
public String toString() {
// return a string representation of the IP/port.
String str;
if (address == null)
str = "";
else
str = address.getHostAddress();
str = str + "/" + port;
return (str);
}
}
//Logger class opens and writes to the log file
//if boolean true is passed as constructor argument.
class Logger {
private boolean logging;
private FileWriter logfile;
public Logger(boolean logging) {
this.logging = logging;
if (logging) {
try {
// open logfile for append
logfile = new FileWriter("reflector.log", true);
} catch (IOException e) {
System.err.println("Error opening log file.");
}
log("Reflector started: " + new Date());
}
}
public void log(String str) {
// write string to logfile
// if logging disabled return
if (!logging)
return;
try {
logfile.write(str + "\n");
logfile.flush();
} catch (IOException e) {
System.err.println("Error writing to log file.");
}
}
}
//ReflectorSender creates a unicast or multicast socket
//and is registered to receive incoming packet notifications
//via the PacketListenerInterface. Incoming packets
//are forwarded on the outgoing socket.
class ReflectorSender extends Thread implements PacketListenerInterface {
private InetAddress sendAddr;
private int sendPort;
private int mode;
private DatagramSocket ds = null;
private Logger logger;
public ReflectorSender(Address a, int mode, Logger logger) {
sendAddr = a.getAddress();
sendPort = a.getPort();
this.mode = mode;
this.logger = logger;
}
public void run() {
// initialize a DatagramSocket or MulticastSocket
// based on the mode:
switch (mode) {
case Reflector.MODE_MULTI_TO_UNI:
ds = initUnicastSocket();
break;
case Reflector.MODE_UNI_TO_MULTI:
ds = (DatagramSocket) initMulticastSocket();
break;
default:
break;
}
}
private MulticastSocket initMulticastSocket() {
// initialize a MulticastSocket
MulticastSocket mc;
try {
mc = new MulticastSocket(sendPort);
} catch (Exception e) {
e.printStackTrace();
return (null);
}
return (mc);
}
private DatagramSocket initUnicastSocket() {
// initialize a DatagramSocket
DatagramSocket ds;
try {
ds = new DatagramSocket(sendPort);
} catch (Exception e) {
e.printStackTrace();
return (null);
}
return (ds);
}
public void packetReceived(DatagramPacket packet) {
// An incoming packet has been received. Override
// the old packet addressing to the new outgoing
// addressing, send it and log it.
try {
packet.setAddress(sendAddr);
packet.setPort(sendPort);
ds.send(packet);
logger.log("Packet forwarded to "
+ packet.getAddress().getHostAddress() + "/"
+ packet.getPort() + ", " + packet.getLength() + " bytes");
} catch (IOException e) {
System.err.println("Error sending packet");
e.printStackTrace();
}
}
}
//ReflectorListener thread listens for packets
//and notifies one or more interested threads
//who register as PacketListenerInterfaces.
class ReflectorListener extends Thread {
private InetAddress listenAddr;
private int listenPort;
private int mode;
private Vector packetListeners;
private Logger logger;
private static final int MAX_PACKET_SIZE = 1500;
public ReflectorListener(Address a, int mode, Logger logger) {
listenAddr = a.getAddress();
listenPort = a.getPort();
this.mode = mode;
this.logger = logger;
packetListeners = new Vector();
}
public void run() {
// create a unicast or multicast socket
// depending on the mode and listen for packets.
switch (mode) {
case Reflector.MODE_UNI_TO_MULTI:
DatagramSocket ds = initUnicastSocket();
if (ds != null)
listen(ds);
break;
case Reflector.MODE_MULTI_TO_UNI:
MulticastSocket mc = initMulticastSocket();
if (mc != null)
listen((DatagramSocket) mc);
break;
default:
break;
}
}
private MulticastSocket initMulticastSocket() {
// initialize a MulticastSocket and join the group
MulticastSocket mc;
try {
mc = new MulticastSocket(listenPort);
mc.joinGroup(listenAddr);
} catch (Exception e) {
System.err.println("Failed to create MulticastSocket on " + "port "
+ listenPort);
return (null);
}
return (mc);
}
private DatagramSocket initUnicastSocket() {
// initialize a DatagramSocket
DatagramSocket ds;
try {
ds = new DatagramSocket(listenPort);
} catch (Exception e) {
System.err.println("Failed to create DatagramSocket on " + "port "
+ listenPort);
return (null);
}
return (ds);
}
private void listen(DatagramSocket ds) {
// loop forever listening to packets, when they
// arrive log them and notify all interested threads.
byte[] buffer;
DatagramPacket packet;
while (true) {
try {
buffer = new byte[MAX_PACKET_SIZE];
packet = new DatagramPacket(buffer, buffer.length);
ds.receive(packet);
logger.log("Packet received, " + packet.getLength() + " bytes");
eventNotify(packet);
} catch (IOException e) {
System.err.println("Error receiving packet\n");
e.printStackTrace();
}
}
}
public void addPacketListener(PacketListenerInterface pl) {
// add interested thread to listeners vector
packetListeners.addElement(pl);
}
public void removePacketListener(PacketListenerInterface pl) {
// remove thread to listeners vector
packetListeners.removeElement(pl);
}
private void eventNotify(DatagramPacket packet) {
// notify all registered threads that a packet has arrived
// using the packetReceived(DatagramPacket) method.
for (Enumeration e = packetListeners.elements(); e.hasMoreElements();) {
PacketListenerInterface pl = (PacketListenerInterface) e
.nextElement();
pl.packetReceived(packet);
}
}
}
//PacketListenerInterface used by threads that need to
//be notified of datagram packet receipt. A single
//interface function packetReceived passes the packet
//information to the thread requiring the information.
interface PacketListenerInterface {
public void packetReceived(DatagramPacket packet);
}
|