/* * JBoss, Home of Professional Open Source. * Copyright 2011, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.controller.interfaces; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ANY_ADDRESS; import static org.jboss.as.controller.logging.ControllerLogger.MGMT_OP_LOGGER; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.jboss.as.controller.ExpressionResolver; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.controller.logging.ControllerLogger; import org.jboss.as.controller.parsing.Element; import org.jboss.as.controller.parsing.ParseUtils; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; import org.jboss.dmr.Property; /** * Utility class to create an interface criteria based on a {@link ModelNode} description * * @author Brian Stansberry * @author Emanuel Muckenhuber */ public final class ParsedInterfaceCriteria { private static final ParsedInterfaceCriteria ANY = new ParsedInterfaceCriteria(true); private final String failureMessage; private final boolean anyLocal; private final Set<InterfaceCriteria> criteria = new HashSet<InterfaceCriteria>(); private ParsedInterfaceCriteria(final String failureMessage) { this.failureMessage = failureMessage; this.anyLocal = false; } private ParsedInterfaceCriteria(final boolean anyLocal) { this.failureMessage = null; this.anyLocal = anyLocal; } private ParsedInterfaceCriteria(final Set<InterfaceCriteria> criteria) { this.failureMessage = null; this.anyLocal = false; this.criteria.addAll(criteria); } public String getFailureMessage() { return failureMessage; } public boolean isAnyLocal() { return anyLocal; } public Set<InterfaceCriteria> getCriteria() { return criteria; } public static ParsedInterfaceCriteria parse(final ModelNode model, final boolean specified, final ExpressionResolver expressionResolver) { if (model.getType() != ModelType.OBJECT) { return new ParsedInterfaceCriteria(ControllerLogger.ROOT_LOGGER.illegalInterfaceCriteria(model.getType(), ModelType.OBJECT)); } // Remove operation params final ModelNode subModel = model.clone(); subModel.remove(ModelDescriptionConstants.OP); subModel.remove(ModelDescriptionConstants.OP_ADDR); subModel.remove(ModelDescriptionConstants.OPERATION_HEADERS); final ParsedInterfaceCriteria parsed; if(subModel.hasDefined(ANY_ADDRESS) && subModel.get(ANY_ADDRESS).asBoolean(false)) { parsed = ParsedInterfaceCriteria.ANY; } else { try { final List<Property> nodes = subModel.asPropertyList(); final Set<InterfaceCriteria> criteriaSet = new HashSet<InterfaceCriteria>(); for (final Property property : nodes) { final InterfaceCriteria criterion = parseCriteria(property, false, expressionResolver); if (criterion instanceof WildcardInetAddressInterfaceCriteria) { // AS7-1668: stop processing and just return the any binding. if (nodes.size() > 1) { MGMT_OP_LOGGER.wildcardAddressDetected(); } return ParsedInterfaceCriteria.ANY; } else if (criterion != null) { criteriaSet.add(criterion); } } String validation = new CriteriaValidator(criteriaSet).validate(); parsed = validation == null ? new ParsedInterfaceCriteria(criteriaSet) : new ParsedInterfaceCriteria(validation); } catch (ParsingException p) { return new ParsedInterfaceCriteria(p.msg); } catch (OperationFailedException e) { return new ParsedInterfaceCriteria(e.getMessage()); } } if (specified && parsed.getFailureMessage() == null && ! parsed.isAnyLocal() && parsed.getCriteria().size() == 0) { return new ParsedInterfaceCriteria(ControllerLogger.ROOT_LOGGER.noInterfaceCriteria()); } return parsed; } private static InterfaceCriteria parseCriteria(final Property property, final boolean nested, final ExpressionResolver expressionResolver) throws OperationFailedException { final Element element = Element.forName(property.getName()); switch (element) { case LINK_LOCAL_ADDRESS: return LinkLocalInterfaceCriteria.INSTANCE; case LOOPBACK: return LoopbackInterfaceCriteria.INSTANCE; case MULTICAST: return SupportsMulticastInterfaceCriteria.INSTANCE; case POINT_TO_POINT: return PointToPointInterfaceCriteria.INSTANCE; case PUBLIC_ADDRESS: return PublicAddressInterfaceCriteria.INSTANCE; case SITE_LOCAL_ADDRESS: return SiteLocalInterfaceCriteria.INSTANCE; case UP: return UpInterfaceCriteria.INSTANCE; case VIRTUAL: return VirtualInterfaceCriteria.INSTANCE; case INET_ADDRESS: { ModelNode value = parsePossibleExpression(property.getValue()); checkStringType(value, element.getLocalName(), true); return createInetAddressCriteria(value, expressionResolver); } case LOOPBACK_ADDRESS: { ModelNode value = parsePossibleExpression(property.getValue()); checkStringType(value, element.getLocalName(), true); return new LoopbackAddressInterfaceCriteria(parseInetAddress(value, expressionResolver)); } case NIC: { ModelNode value = parsePossibleExpression(property.getValue()); checkStringType(property.getValue(), element.getLocalName()); return new NicInterfaceCriteria(expressionResolver.resolveExpressions(value).asString()); } case NIC_MATCH: { ModelNode value = parsePossibleExpression(property.getValue()); checkStringType(property.getValue(), element.getLocalName()); return createNicMatchCriteria(expressionResolver.resolveExpressions(value)); } case SUBNET_MATCH: { ModelNode value = parsePossibleExpression(property.getValue()); return createSubnetMatchCriteria(expressionResolver.resolveExpressions(value)); } case ANY: case NOT: if(nested) { throw new ParsingException(ControllerLogger.ROOT_LOGGER.nestedElementNotAllowed(element)); } return parseNested(property.getValue(), element == Element.ANY, expressionResolver); default: throw new ParsingException(ControllerLogger.ROOT_LOGGER.unknownCriteriaInterfaceType(property.getName())); } } private static InterfaceCriteria parseNested(final ModelNode subModel, final boolean any, final ExpressionResolver expressionResolver) throws OperationFailedException { if(!subModel.isDefined() || subModel.asInt() == 0) { return null; } final Set<InterfaceCriteria> criteriaSet = new HashSet<InterfaceCriteria>(); for(final Property nestedProperty : subModel.asPropertyList()) { final Element element = Element.forName(nestedProperty.getName()); switch (element) { case INET_ADDRESS: case NIC : case NIC_MATCH: case SUBNET_MATCH: { if (nestedProperty.getValue().getType() == ModelType.LIST) { for (ModelNode item : nestedProperty.getValue().asList()) { Property prop = new Property(nestedProperty.getName(), item); InterfaceCriteria itemCriteria = parseCriteria(prop, true, expressionResolver); if(itemCriteria != null) { criteriaSet.add(itemCriteria); } } break; } // else drop down into default: block } default: { final InterfaceCriteria criteria = parseCriteria(nestedProperty, true, expressionResolver); if(criteria != null) { criteriaSet.add(criteria); } } } } if(criteriaSet.isEmpty()) { return null; } return any ? new AnyInterfaceCriteria(criteriaSet) : new NotInterfaceCriteria(criteriaSet); } private static InterfaceCriteria createInetAddressCriteria(final ModelNode model, final ExpressionResolver expressionResolver) throws ParsingException, OperationFailedException { InetAddress address = parseInetAddress(model, expressionResolver); if (address.isAnyLocalAddress()) { // they've entered a wildcard address return new WildcardInetAddressInterfaceCriteria(address); } else if (address.isLoopbackAddress()) { // support any loopback address via the -b argument, without xml files changes - WFLY-248 return new LoopbackAddressInterfaceCriteria(address); } else { return new InetAddressMatchInterfaceCriteria(address); } } private static InterfaceCriteria createNicMatchCriteria(final ModelNode model) throws ParsingException { try { Pattern pattern = Pattern.compile(model.asString()); return new NicMatchInterfaceCriteria(pattern); } catch (PatternSyntaxException e) { throw new ParsingException(ControllerLogger.ROOT_LOGGER.invalidInterfaceCriteriaPattern(model.asString(), Element.NIC_MATCH.getLocalName())); } } private static InterfaceCriteria createSubnetMatchCriteria(final ModelNode model) throws ParsingException { String value; String[] split = null; try { value = model.asString(); split = value.split("/"); if (split.length != 2) { throw new ParsingException(ControllerLogger.ROOT_LOGGER.invalidAddressMaskValue(value)); } // todo - possible DNS hit here final InetAddress addr = InetAddress.getByName(split[0]); // Validate both parts of the split final byte[] net = addr.getAddress(); final int mask = Integer.parseInt(split[1]); return new SubnetMatchInterfaceCriteria(net, mask); } catch (final NumberFormatException e) { throw new ParsingException(ControllerLogger.ROOT_LOGGER.invalidAddressMask(split[1], e.getLocalizedMessage())); } catch (final UnknownHostException e) { throw new ParsingException(ControllerLogger.ROOT_LOGGER.invalidAddressValue(split[0], e.getLocalizedMessage())); } } private static InetAddress parseInetAddress(final ModelNode model, final ExpressionResolver expressionResolver) throws OperationFailedException { final String rawAddress = expressionResolver.resolveExpressions(model).asString(); try { return InetAddress.getByName(rawAddress); } catch (UnknownHostException e) { throw new ParsingException(ControllerLogger.ROOT_LOGGER.invalidAddress(model.asString(), e.getLocalizedMessage())); } } private static void checkStringType(ModelNode node, String id) { checkStringType(node, id, false); } private static void checkStringType(ModelNode node, String id, boolean allowExpressions) { if (node.getType() != ModelType.STRING && (!allowExpressions || node.getType() != ModelType.EXPRESSION)) { throw new ParsingException(ControllerLogger.ROOT_LOGGER.illegalValueForInterfaceCriteria(node.getType(), id, ModelType.STRING)); } } private static ModelNode parsePossibleExpression(final ModelNode node) { return (node.getType() == ModelType.STRING) ? ParseUtils.parsePossibleExpression(node.asString()) : node; } private static class ParsingException extends RuntimeException { private static final long serialVersionUID = -5627251228393035383L; private final String msg; private ParsingException(String msg) { this.msg = msg; } } }