package org.zstack.network.l3;
import org.apache.commons.net.util.SubnetUtils;
import org.apache.commons.net.util.SubnetUtils.SubnetInfo;
import org.apache.commons.validator.routines.DomainValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.db.SimpleQuery.Op;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.header.errorcode.SysErrors;
import org.zstack.header.apimediator.ApiMessageInterceptionException;
import org.zstack.header.apimediator.ApiMessageInterceptor;
import org.zstack.header.apimediator.StopRoutingException;
import org.zstack.header.message.APIMessage;
import org.zstack.header.network.l3.*;
import org.zstack.header.zone.ZoneVO;
import org.zstack.header.zone.ZoneVO_;
import org.zstack.utils.network.NetworkUtils;
import static org.zstack.core.Platform.argerr;
import static org.zstack.core.Platform.operr;
import java.util.List;
/**
* Created with IntelliJ IDEA.
* User: frank
* Time: 9:04 PM
* To change this template use File | Settings | File Templates.
*/
public class L3NetworkApiInterceptor implements ApiMessageInterceptor {
@Autowired
private CloudBus bus;
@Autowired
private DatabaseFacade dbf;
@Autowired
private ErrorFacade errf;
private void setServiceId(APIMessage msg) {
if (msg instanceof IpRangeMessage) {
IpRangeMessage dmsg = (IpRangeMessage)msg;
SimpleQuery<IpRangeVO> q = dbf.createQuery(IpRangeVO.class);
q.select(IpRangeVO_.l3NetworkUuid);
q.add(IpRangeVO_.uuid, SimpleQuery.Op.EQ, dmsg.getIpRangeUuid());
String l3NwUuid = q.findValue();
dmsg.setL3NetworkUuid(l3NwUuid);
bus.makeTargetServiceIdByResourceUuid(msg, L3NetworkConstant.SERVICE_ID, l3NwUuid);
} else if (msg instanceof L3NetworkMessage) {
L3NetworkMessage l3msg = (L3NetworkMessage)msg;
bus.makeTargetServiceIdByResourceUuid(msg, L3NetworkConstant.SERVICE_ID, l3msg.getL3NetworkUuid());
}
}
@Override
public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionException {
if (msg instanceof APIAddDnsToL3NetworkMsg) {
validate((APIAddDnsToL3NetworkMsg) msg);
} else if (msg instanceof APIAddIpRangeMsg) {
validate((APIAddIpRangeMsg) msg);
} else if (msg instanceof APIDeleteL3NetworkMsg) {
validate((APIDeleteL3NetworkMsg) msg);
} else if (msg instanceof APIRemoveDnsFromL3NetworkMsg) {
validate((APIRemoveDnsFromL3NetworkMsg) msg);
} else if (msg instanceof APICreateL3NetworkMsg) {
validate((APICreateL3NetworkMsg) msg);
} else if (msg instanceof APIGetIpAddressCapacityMsg) {
validate((APIGetIpAddressCapacityMsg) msg);
} else if (msg instanceof APIAddIpRangeByNetworkCidrMsg) {
validate((APIAddIpRangeByNetworkCidrMsg) msg);
} else if (msg instanceof APIGetFreeIpMsg) {
validate((APIGetFreeIpMsg) msg);
} else if (msg instanceof APICheckIpAvailabilityMsg) {
validate((APICheckIpAvailabilityMsg) msg);
}
setServiceId(msg);
return msg;
}
private void validate(APICheckIpAvailabilityMsg msg) {
if (!NetworkUtils.isIpv4Address(msg.getIp())) {
throw new ApiMessageInterceptionException(argerr("invalid IP[%s]", msg.getIp()));
}
}
private void validate(APIGetFreeIpMsg msg) {
if (msg.getIpRangeUuid() == null && msg.getL3NetworkUuid() == null) {
throw new ApiMessageInterceptionException(argerr(
"ipRangeUuid and l3NetworkUuid cannot both be null; you must set either one."
));
}
if (msg.getIpRangeUuid() != null && msg.getL3NetworkUuid() == null) {
SimpleQuery<IpRangeVO> q = dbf.createQuery(IpRangeVO.class);
q.select(IpRangeVO_.l3NetworkUuid);
q.add(IpRangeVO_.uuid, Op.EQ, msg.getIpRangeUuid());
String l3Uuid = q.findValue();
msg.setL3NetworkUuid(l3Uuid);
}
if (msg.getLimit() < 0) {
msg.setLimit(Integer.MAX_VALUE);
}
}
private void validate(APIAddIpRangeByNetworkCidrMsg msg) {
try {
SubnetUtils utils = new SubnetUtils(msg.getNetworkCidr());
utils.setInclusiveHostCount(false);
SubnetInfo subnet = utils.getInfo();
if (subnet.getAddressCount() == 0) {
throw new ApiMessageInterceptionException(argerr("%s is not an allowed network cidr, because it doesn't have usable ip range", msg.getNetworkCidr()));
}
} catch (IllegalArgumentException e) {
throw new ApiMessageInterceptionException(argerr("%s is not a valid network cidr", msg.getNetworkCidr()));
}
IpRangeInventory ipr = IpRangeInventory.fromMessage(msg);
validate(ipr);
}
private void validate(APIGetIpAddressCapacityMsg msg) {
boolean pass = false;
if (msg.getIpRangeUuids() != null && !msg.getIpRangeUuids().isEmpty()) {
pass = true;
}
if (msg.getL3NetworkUuids() != null && !msg.getL3NetworkUuids().isEmpty()) {
pass = true;
}
if (msg.getZoneUuids() != null && !msg.getZoneUuids().isEmpty()) {
pass = true;
}
if (!pass && !msg.isAll()) {
throw new ApiMessageInterceptionException(argerr(
"ipRangeUuids, L3NetworkUuids, zoneUuids must have at least one be none-empty list, or all is set to true"
));
}
if (msg.isAll() && (msg.getZoneUuids() == null || msg.getZoneUuids().isEmpty())) {
SimpleQuery<ZoneVO> q = dbf.createQuery(ZoneVO.class);
q.select(ZoneVO_.uuid);
List<String> zuuids = q.listValue();
msg.setZoneUuids(zuuids);
if (msg.getZoneUuids().isEmpty()) {
APIGetIpAddressCapacityReply reply = new APIGetIpAddressCapacityReply();
bus.reply(msg, reply);
throw new StopRoutingException();
}
}
}
private void validate(APICreateL3NetworkMsg msg) {
if (!L3NetworkType.hasType(msg.getType())) {
throw new ApiMessageInterceptionException(argerr("unsupported l3network type[%s]", msg.getType()));
}
if (msg.getDnsDomain() != null) {
DomainValidator validator = DomainValidator.getInstance();
if (!validator.isValid(msg.getDnsDomain())) {
throw new ApiMessageInterceptionException(argerr("%s is not a valid domain name", msg.getDnsDomain()));
}
}
}
private void validate(APIRemoveDnsFromL3NetworkMsg msg) {
SimpleQuery<L3NetworkDnsVO> q = dbf.createQuery(L3NetworkDnsVO.class);
q.add(L3NetworkDnsVO_.dns, Op.EQ, msg.getDns());
q.add(L3NetworkDnsVO_.l3NetworkUuid, Op.EQ, msg.getL3NetworkUuid());
if (!q.isExists()) {
APIRemoveDnsFromL3NetworkEvent evt = new APIRemoveDnsFromL3NetworkEvent(msg.getId());
bus.publish(evt);
throw new StopRoutingException();
}
}
private void validate(APIDeleteL3NetworkMsg msg) {
if (!dbf.isExist(msg.getUuid(), L3NetworkVO.class)) {
APIDeleteL3NetworkEvent evt = new APIDeleteL3NetworkEvent(msg.getId());
bus.publish(evt);
throw new StopRoutingException();
}
}
private void validate(IpRangeInventory ipr) {
if (NetworkUtils.isIpv4RangeOverlap("224.0.0.0", "239.255.255.255", ipr.getStartIp(), ipr.getEndIp())) {
throw new ApiMessageInterceptionException(argerr("the IP range[%s ~ %s] contains D class addresses which are for multicast", ipr.getStartIp(), ipr.getEndIp()));
}
if (NetworkUtils.isIpv4RangeOverlap("240.0.0.0", "255.255.255.255", ipr.getStartIp(), ipr.getEndIp())) {
throw new ApiMessageInterceptionException(argerr("the IP range[%s ~ %s] contains E class addresses which are reserved", ipr.getStartIp(), ipr.getEndIp()));
}
if (NetworkUtils.isIpv4RangeOverlap("169.254.1.0", "169.254.254.255", ipr.getStartIp(), ipr.getEndIp())) {
throw new ApiMessageInterceptionException(argerr("the IP range[%s ~ %s] contains link local addresses which are reserved", ipr.getStartIp(), ipr.getEndIp()));
}
SubnetUtils sub = new SubnetUtils(ipr.getStartIp(), ipr.getNetmask());
SubnetInfo info = sub.getInfo();
if (!info.isInRange(ipr.getGateway())) {
throw new ApiMessageInterceptionException(argerr("the gateway[%s] is not in the subnet %s/%s", ipr.getGateway(), ipr.getStartIp(), ipr.getNetmask()));
}
if (!NetworkUtils.isIpv4Address(ipr.getStartIp())) {
throw new ApiMessageInterceptionException(argerr("start ip[%s] is not a IPv4 address", ipr.getStartIp()));
}
if (!NetworkUtils.isIpv4Address(ipr.getEndIp())) {
throw new ApiMessageInterceptionException(argerr("end ip[%s] is not a IPv4 address", ipr.getEndIp()));
}
if (!NetworkUtils.isIpv4Address(ipr.getGateway())) {
throw new ApiMessageInterceptionException(argerr("gateway[%s] is not a IPv4 address", ipr.getGateway()));
}
if (!NetworkUtils.isNetmaskExcept(ipr.getNetmask(), "0.0.0.0")) {
throw new ApiMessageInterceptionException(argerr("netmask[%s] is not a netmask, and the IP range netmask cannot be 0.0.0.0", ipr.getNetmask()));
}
if (ipr.getStartIp().equals(info.getNetworkAddress()) || ipr.getEndIp().equals(info.getBroadcastAddress())){
throw new ApiMessageInterceptionException(argerr(
"ip allocation can not contain network address or broadcast address")
);
}
long startip = NetworkUtils.ipv4StringToLong(ipr.getStartIp());
long endip = NetworkUtils.ipv4StringToLong(ipr.getEndIp());
if (startip > endip) {
throw new ApiMessageInterceptionException(argerr("start ip[%s] is behind end ip[%s]", ipr.getStartIp(), ipr.getEndIp()));
}
long gw = NetworkUtils.ipv4StringToLong(ipr.getGateway());
if (startip <= gw && gw <= endip) {
throw new ApiMessageInterceptionException(argerr("gateway[%s] can not be part of range[%s, %s]", ipr.getGateway(), ipr.getStartIp(), ipr.getEndIp()));
}
String cidr = ipr.toSubnetUtils().getInfo().getCidrSignature();
SimpleQuery<IpRangeVO> q = dbf.createQuery(IpRangeVO.class);
q.add(IpRangeVO_.l3NetworkUuid, Op.EQ, ipr.getL3NetworkUuid());
List<IpRangeVO> ranges = q.list();
for (IpRangeVO r : ranges) {
if (NetworkUtils.isIpv4RangeOverlap(ipr.getStartIp(), ipr.getEndIp(), r.getStartIp(), r.getEndIp())) {
throw new ApiMessageInterceptionException(argerr("overlap with ip range[uuid:%s, start ip:%s, end ip: %s]", r.getUuid(), r.getStartIp(), r.getEndIp()));
}
String rcidr = IpRangeInventory.valueOf(r).toSubnetUtils().getInfo().getCidrSignature();
if (!cidr.equals(rcidr)) {
throw new ApiMessageInterceptionException(argerr("multiple CIDR on the same L3 network is not allowed. There has been a IP" +
" range[uuid:%s, CIDR:%s], the new IP range[CIDR:%s] is not in the CIDR with the existing one",
r.getUuid(), rcidr, cidr));
}
}
}
private void validate(APIAddIpRangeMsg msg) {
IpRangeInventory ipr =IpRangeInventory.fromMessage(msg);
validate(ipr);
}
private void validate(APIAddDnsToL3NetworkMsg msg) {
if (!NetworkUtils.isIpv4Address(msg.getDns())) {
throw new ApiMessageInterceptionException(argerr("DNS[%s] is not a IPv4 address", msg.getDns()));
}
SimpleQuery<L3NetworkDnsVO> q = dbf.createQuery(L3NetworkDnsVO.class);
q.add(L3NetworkDnsVO_.l3NetworkUuid, Op.EQ, msg.getL3NetworkUuid());
q.add(L3NetworkDnsVO_.dns, Op.EQ, msg.getDns());
if (q.isExists()) {
throw new ApiMessageInterceptionException(operr("there has been a DNS[%s] on L3 network[uuid:%s]", msg.getDns(), msg.getL3NetworkUuid()));
}
}
}