/*
* Copyright 2011 Future Systems
*
* 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.krakenapps.dhcp.server;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Invalidate;
import org.apache.felix.ipojo.annotations.Provides;
import org.apache.felix.ipojo.annotations.Validate;
import org.krakenapps.dhcp.DhcpMessage;
import org.krakenapps.dhcp.DhcpMessageListener;
import org.krakenapps.dhcp.DhcpOption;
import org.krakenapps.dhcp.DhcpOptionCode;
import org.krakenapps.dhcp.DhcpServer;
import org.krakenapps.dhcp.MacAddress;
import org.krakenapps.dhcp.model.DhcpFilter;
import org.krakenapps.dhcp.model.DhcpIpGroup;
import org.krakenapps.dhcp.model.DhcpIpLease;
import org.krakenapps.dhcp.model.DhcpIpReservation;
import org.krakenapps.dhcp.model.DhcpOptionConfig;
import org.krakenapps.dhcp.options.ByteConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component(name = "dhcp-server")
@Provides(specifications = { DhcpServer.class })
public class DhcpServerImpl implements DhcpServer, Runnable {
private final Logger logger = LoggerFactory.getLogger(DhcpServerImpl.class.getName());
private Thread t;
private DhcpServerContext c;
private DhcpDiscoverHandler discoverHandler;
private DhcpRequestHandler requestHandler;
private DhcpDeclineHandler declineHandler;
private DhcpInformHandler informHandler;
private DhcpReleaseHandler releaseHandler;
/**
* listener thread stop indicator
*/
private volatile boolean doStop = false;
public static void main(String[] args) throws Exception {
DhcpServerImpl server = new DhcpServerImpl();
server.start();
}
@Validate
public void start() throws Exception {
initHandlers();
loadDatabase();
t = new Thread(this, "DHCP Server");
t.start();
logger.info("kraken dhcp: server started");
}
private void initHandlers() throws Exception {
c = new DhcpServerContext();
discoverHandler = new DhcpDiscoverHandler(this, c);
requestHandler = new DhcpRequestHandler(this, c);
declineHandler = new DhcpDeclineHandler(c);
informHandler = new DhcpInformHandler(this, c);
releaseHandler = new DhcpReleaseHandler(c);
}
private void loadDatabase() {
DhcpDatabase.checkSchema(c.conn);
for (DhcpIpGroup group : DhcpDatabase.getIpGroups(c.conn)) {
for (DhcpIpLease lease : DhcpDatabase.getIpLeases(group.getName()))
c.leaseMap.put(lease.getIp(), lease);
for (DhcpIpReservation r : DhcpDatabase.getIpReservations(group.getName()))
c.reserveMap.put(r.getMac(), r);
}
for (DhcpFilter f : DhcpDatabase.getAllowFilters())
c.allowFilters.put(f.getMac(), f);
for (DhcpFilter f : DhcpDatabase.getBlockFilters())
c.blockFilters.put(f.getMac(), f);
}
@Invalidate
public void stop() {
// thread will stop automatically when socket is closed
c.close();
logger.info("kraken dhcp: server stopped");
}
@Override
public void addListener(DhcpMessageListener callback) {
c.callbacks.add(callback);
}
@Override
public void removeListener(DhcpMessageListener callback) {
c.callbacks.remove(callback);
}
@Override
public void run() {
try {
while (doStop == false) {
DhcpMessage msg = c.receive();
if (msg == null)
continue;
logger.debug("kraken dhcp: received {}", msg);
handle(msg);
}
} catch (IOException e) {
if (!e.getMessage().contains("socket closed"))
logger.error("kraken dhcp: server socket error", e);
} finally {
logger.info("kraken dhcp: server stopped");
}
}
private void handle(DhcpMessage msg) {
try {
DhcpOption option = msg.getOption(DhcpOptionCode.DhcpMessageType.code());
int type = option.getValue()[0] & 0xff;
switch (type) {
case 1:
discoverHandler.handle(msg);
break;
case 3:
requestHandler.handle(msg);
break;
case 4:
declineHandler.handle(msg);
break;
case 7:
releaseHandler.handle(msg);
break;
case 8:
informHandler.handle(msg);
break;
default:
logger.warn("kraken dhcp: maybe other server's message", msg);
break;
}
clearTimeout();
} catch (IOException e) {
logger.error("kraken dhcp: cannot handle msg - " + msg, e);
} catch (RuntimeException e) {
logger.error("kraken dhcp: cannot handle msg - " + msg, e);
}
}
private void clearTimeout() {
for (InetAddress ip : c.offerMap.keySet()) {
DhcpIpLease offer = c.offerMap.get(ip);
long gap = new Date().getTime() - offer.getCreated().getTime();
// remind only under 60sec offers
if (gap > 60 * 1000)
c.offerMap.remove(ip);
}
}
@Override
public DhcpIpGroup getIpGroup(InetAddress ip) {
for (DhcpIpGroup g : getIpGroups()) {
long from = ByteConverter.toUnsignedInteger(g.getFrom().getAddress());
long to = ByteConverter.toUnsignedInteger(g.getTo().getAddress());
long n = ByteConverter.toUnsignedInteger(ip.getAddress());
if (from <= n && n <= to)
return g;
}
return null;
}
@Override
public List<DhcpOptionConfig> getGroupOptions(String groupName) {
return DhcpDatabase.getGroupConfigs(groupName);
}
@Override
public void createGroupOption(DhcpOptionConfig config) {
DhcpDatabase.createGroupConfig(config);
}
@Override
public void removeGroupOption(int id) {
DhcpDatabase.removeGroupConfig(id);
}
@Override
public List<DhcpFilter> getAllowFilters() {
return DhcpDatabase.getAllowFilters();
}
@Override
public List<DhcpFilter> getBlockFilters() {
return DhcpDatabase.getBlockFilters();
}
@Override
public List<DhcpIpGroup> getIpGroups() {
return DhcpDatabase.getIpGroups();
}
@Override
public List<DhcpIpLease> getIpOffers() {
return new ArrayList<DhcpIpLease>(c.offerMap.values());
}
@Override
public List<DhcpIpLease> getIpLeases(String groupName) {
return DhcpDatabase.getIpLeases(groupName);
}
@Override
public List<DhcpIpReservation> getIpReservations(String groupName) {
return DhcpDatabase.getIpReservations(groupName);
}
@Override
public void purgeIpLease() {
DhcpDatabase.purgeIpLease();
c.leaseMap.clear();
}
@Override
public void purgeIpLease(InetAddress ip) {
DhcpDatabase.purgeIpLease(ip);
c.leaseMap.remove(ip);
}
@Override
public void createIpGroup(DhcpIpGroup group) {
DhcpDatabase.createIpGroup(group);
}
@Override
public void updateIpGroup(DhcpIpGroup group) {
DhcpDatabase.updateIpGroup(group);
}
@Override
public void removeIpGroup(String name) {
// TODO: check existence
DhcpDatabase.removeIpGroup(name);
}
@Override
public void reserve(DhcpIpReservation entry) {
// in memory
c.reserveMap.put(entry.getMac(), entry);
// persist
DhcpDatabase.createIpReservation(entry);
}
@Override
public void unreserve(DhcpIpReservation entry) {
// persist
DhcpDatabase.removeIpReservation(entry);
// in memory
c.reserveMap.remove(entry.getMac());
}
@Override
public void createFilter(DhcpFilter filter) {
DhcpDatabase.createFilter(filter);
if (filter.isAllow())
c.allowFilters.put(filter.getMac(), filter);
else
c.blockFilters.put(filter.getMac(), filter);
}
@Override
public void removeFilter(MacAddress mac) {
DhcpDatabase.removeFilter(mac);
// only one side has this mac
c.allowFilters.remove(mac);
c.blockFilters.remove(mac);
}
}