Author: markt
Date: Wed May 23 20:33:35 2018
New Revision: 1832124

URL: http://svn.apache.org/viewvc?rev=1832124&view=rev
Log:
First part of implementation for BZ 51953
Add a NetMask utility class and some test cases

Added:
    tomcat/trunk/java/org/apache/catalina/util/NetMask.java   (with props)
    tomcat/trunk/test/org/apache/catalina/util/TestNetMask.java   (with props)
Modified:
    tomcat/trunk/java/org/apache/catalina/util/LocalStrings.properties

Modified: tomcat/trunk/java/org/apache/catalina/util/LocalStrings.properties
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/util/LocalStrings.properties?rev=1832124&r1=1832123&r2=1832124&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/util/LocalStrings.properties 
(original)
+++ tomcat/trunk/java/org/apache/catalina/util/LocalStrings.properties Wed May 
23 20:33:35 2018
@@ -38,6 +38,12 @@ lifecycleBase.stopFail=Failed to stop co
 lifecycleMBeanBase.registerFail=Failed to register object [{0}] with name 
[{1}] during component initialisation
 lifecycleMBeanBase.unregisterFail=Failed to unregister MBean with name [{0}] 
during component destruction
 lifecycleMBeanBase.unregisterNoServer=No MBean server was available to 
unregister the MBean [{0}]
+
+netmask.cidrNegative=The CIDR [{0}] is negative
+netmask.cidrNotNumeric=The CIDR [{0}] is not numeric
+netmask.cidrTooBig=The CIDR [{0}] is greater than the address length [{1}]
+netmask.invalidAddress=The address [{0}] is not valid
+
 SecurityUtil.doAsPrivilege=An exception occurs when running the 
PrivilegedExceptionAction block.
 sessionIdGeneratorBase.createRandom=Creation of SecureRandom instance for 
session ID generation using [{0}] took [{1}] milliseconds.
 sessionIdGeneratorBase.random=Exception initializing random number generator 
of class [{0}]. Falling back to java.secure.SecureRandom

Added: tomcat/trunk/java/org/apache/catalina/util/NetMask.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/util/NetMask.java?rev=1832124&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/util/NetMask.java (added)
+++ tomcat/trunk/java/org/apache/catalina/util/NetMask.java Wed May 23 20:33:35 
2018
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.catalina.util;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import org.apache.catalina.tribes.util.StringManager;
+
+/**
+ * A class representing a CIDR netmask.
+ *
+ * <p>
+ * The constructor takes a string as an argument which represents a netmask, as
+ * per the CIDR notation -- whether this netmask be IPv4 or IPv6. It then
+ * extracts the network address (before the /) and the CIDR prefix (after the
+ * /), and tells through the #matches() method whether a candidate
+ * {@link InetAddress} object fits in the recorded range.
+ * </p>
+ *
+ * <p>
+ * As byte arrays as returned by <code>InetAddress.getByName()</code> are 
always
+ * in network byte order, finding a match is therefore as simple as testing
+ * whether the n first bits (where n is the CIDR) are the same in both byte
+ * arrays (the one of the network address and the one of the candidate 
address).
+ * We do that by first doing byte comparisons, then testing the last bits if 
any
+ * (that is, if the remainder of the integer division of the CIDR by 8 is not
+ * 0).
+ * </p>
+ *
+ * <p>
+ * As a bonus, if no '/' is found in the input, it is assumed that an exact
+ * address match is required.
+ * </p>
+ */
+public final class NetMask {
+
+    private static final StringManager sm = 
StringManager.getManager(NetMask.class);
+
+    /**
+     * The argument to the constructor, used for .toString()
+     */
+    private final String expression;
+
+    /**
+     * The byte array representing the address extracted from the expression
+     */
+    private final byte[] netaddr;
+
+    /**
+     * The number of bytes to test for equality (CIDR / 8)
+     */
+    private final int nrBytes;
+
+    /**
+     * The right shift to apply to the last byte if CIDR % 8 is not 0; if it is
+     * 0, this variable is set to 0
+     */
+    private final int lastByteShift;
+
+
+    /**
+     * Constructor
+     *
+     * @param input the CIDR netmask
+     * @throws IllegalArgumentException if the netmask is not correct (invalid
+     *             address specification, malformed CIDR prefix, etc)
+     */
+    public NetMask(final String input) {
+
+        expression = input;
+
+        final int idx = input.indexOf("/");
+
+        /*
+         * Handle the "IP only" case first
+         */
+        if (idx == -1) {
+            try {
+                netaddr = InetAddress.getByName(input).getAddress();
+            } catch (UnknownHostException e) {
+                throw new 
IllegalArgumentException(sm.getString("netmask.invalidAddress", input));
+            }
+            nrBytes = netaddr.length;
+            lastByteShift = 0;
+            return;
+        }
+
+        /*
+         * OK, we do have a netmask specified, so let's extract both the 
address
+         * and the CIDR.
+         */
+
+        final String addressPart = input.substring(0, idx), cidrPart = 
input.substring(idx + 1);
+
+        try {
+            /*
+             * The address first...
+             */
+            netaddr = InetAddress.getByName(addressPart).getAddress();
+        } catch (UnknownHostException e) {
+            throw new 
IllegalArgumentException(sm.getString("netmask.invalidAddress", addressPart));
+        }
+
+        final int addrlen = netaddr.length * 8;
+        final int cidr;
+
+        try {
+            /*
+             * And then the CIDR.
+             */
+            cidr = Integer.parseInt(cidrPart);
+        } catch (NumberFormatException e) {
+            throw new 
IllegalArgumentException(sm.getString("netmask.cidrNotNumeric", cidrPart));
+        }
+
+        /*
+         * We don't want a negative CIDR, nor do we want a CIDR which is 
greater
+         * than the address length (consider 0.0.0.0/33, or ::/129)
+         */
+        if (cidr < 0) {
+            throw new 
IllegalArgumentException(sm.getString("netmask.cidrNegative", cidrPart));
+        }
+        if (cidr > addrlen) {
+            throw new IllegalArgumentException(
+                    sm.getString("netmask.cidrTooBig", cidrPart, 
Integer.valueOf(addrlen)));
+        }
+
+        nrBytes = cidr / 8;
+
+        /*
+         * These last two lines could be shortened to:
+         *
+         * lastByteShift = (8 - (cidr % 8)) & 7;
+         *
+         * But... It's not worth it. In fact, explaining why it could work 
would
+         * be too long to be worth the trouble, so let's do it the simple 
way...
+         */
+
+        final int remainder = cidr % 8;
+
+        lastByteShift = (remainder == 0) ? 0 : 8 - remainder;
+    }
+
+
+    /**
+     * Test if a given address matches this netmask.
+     *
+     * @param addr The {@link java.net.InetAddress} to test
+     * @return true on match, false otherwise
+     */
+    public boolean matches(final InetAddress addr) {
+        final byte[] candidate = addr.getAddress();
+
+        /*
+         * OK, remember that a CIDR prefix tells the number of BITS which 
should
+         * be equal between this NetMask's recorded address (netaddr) and the
+         * candidate address. One byte is 8 bits, no matter what, and IP
+         * addresses, whether they be IPv4 or IPv6, are big endian, aka MSB,
+         * Most Significant Byte (first).
+         *
+         * We therefore need to get the byte array of the candidate address,
+         * compare as many bytes of the candidate address with the recorded
+         * address as the CIDR prefix tells us to (that is, CIDR / 8), and then
+         * deal with the remaining bits -- if any.
+         *
+         * But prior to that, a simple test can be done: we deal with IP
+         * addresses here, which means IPv4 and IPv6. IPv4 addresses are 
encoded
+         * on 4 bytes, IPv6 addresses are encoded on 16 bytes. If the candidate
+         * address length is different than this NetMask's address, we don't
+         * have a match.
+         */
+        if (candidate.length != netaddr.length) {
+            return false;
+        }
+
+
+        /*
+         * Now do the byte-compare. The constructor has recorded the number of
+         * bytes to compare in nrBytes, use that. If any of the byte we have to
+         * compare is different than what we expect, we don't have a match.
+         *
+         * If, on the opposite, after this loop, all bytes have been deemed
+         * equal, then the loop variable i will point to the byte right after
+         * that -- which we will need...
+         */
+        int i = 0;
+        for (; i < nrBytes; i++) {
+            if (netaddr[i] != candidate[i]) {
+                return false;
+            }
+        }
+
+        /*
+         * ... if there are bits left to test. There aren't any if 
lastByteShift
+         * is set to 0.
+         */
+        if (lastByteShift == 0) {
+            return true;
+        }
+
+        /*
+         * If it is not 0, however, we must test for the relevant bits in the
+         * next byte (whatever is in the bytes after that doesn't matter). We 
do
+         * it this way (remember that lastByteShift contains the amount of bits
+         * we should _right_ shift the last byte):
+         *
+         * - grab both bytes at index i, both from the netmask address and the
+         * candidate address; - xor them both.
+         *
+         * After the xor, it means that all the remaining bits of the CIDR
+         * should be set to 0...
+         */
+        final int lastByte = netaddr[i] ^ candidate[i];
+
+        /*
+         * ... Which means that right shifting by lastByteShift should be 0.
+         */
+        return lastByte >> lastByteShift == 0;
+    }
+
+
+    @Override
+    public String toString() {
+        return expression;
+    }
+}

Propchange: tomcat/trunk/java/org/apache/catalina/util/NetMask.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/trunk/test/org/apache/catalina/util/TestNetMask.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/util/TestNetMask.java?rev=1832124&view=auto
==============================================================================
--- tomcat/trunk/test/org/apache/catalina/util/TestNetMask.java (added)
+++ tomcat/trunk/test/org/apache/catalina/util/TestNetMask.java Wed May 23 
20:33:35 2018
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.catalina.util;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public final class TestNetMask {
+
+    @Parameter(0)
+    public String mask;
+
+    @Parameter(1)
+    public String input;
+
+    @Parameter(2)
+    public Boolean valid;
+
+    @Parameter(3)
+    public Boolean matches;
+
+
+    @Parameters(name="{index}: mask [{0}], input [{1}]")
+    public static Collection<Object[]> inputs() {
+        List<Object[]> result = new ArrayList<>();
+
+        // Invalid IPv4 netmasks
+        result.add(new Object[] { "260.1.1.1", null, Boolean.FALSE, null });
+        result.add(new Object[] { "1.2.3.4/foo", null, Boolean.FALSE, null });
+        result.add(new Object[] { "1.2.3.4/-1", null, Boolean.FALSE, null });
+        result.add(new Object[] { "1.2.3.4/33", null, Boolean.FALSE, null });
+
+        // Invalid IPv6 netmasks
+        result.add(new Object[] { "fffff::/71", null, Boolean.FALSE, null });
+        result.add(new Object[] { "ae31::27:ef2:1/foo", null, Boolean.FALSE, 
null });
+        result.add(new Object[] { "ae31::27:ef2:1/-1", null, Boolean.FALSE, 
null });
+        result.add(new Object[] { "ae31::27:ef2:1/129", null, Boolean.FALSE, 
null });
+
+        // IPv4
+        result.add(new Object[] { "1.2.3.4/32", "1.2.3.3", Boolean.TRUE, 
Boolean.FALSE });
+        result.add(new Object[] { "1.2.3.4/32", "1.2.3.4", Boolean.TRUE, 
Boolean.TRUE });
+        result.add(new Object[] { "1.2.3.4/32", "1.2.3.5", Boolean.TRUE, 
Boolean.FALSE });
+
+        result.add(new Object[] { "1.2.3.4/31", "1.2.3.3", Boolean.TRUE, 
Boolean.FALSE });
+        result.add(new Object[] { "1.2.3.4/31", "1.2.3.4", Boolean.TRUE, 
Boolean.TRUE });
+        result.add(new Object[] { "1.2.3.4/31", "1.2.3.5", Boolean.TRUE, 
Boolean.TRUE });
+        result.add(new Object[] { "1.2.3.4/31", "1.2.3.6", Boolean.TRUE, 
Boolean.FALSE });
+
+        return result;
+    }
+
+
+    @Test
+    public void testNetMask() {
+        Exception exception = null;
+        NetMask netMask = null;
+        try {
+            netMask = new NetMask(mask);
+        } catch (Exception e) {
+            exception =e;
+        }
+
+        if (valid.booleanValue()) {
+            Assert.assertNull(exception);
+            Assert.assertNotNull(netMask);
+        } else {
+            Assert.assertNotNull(exception);
+            Assert.assertEquals(IllegalArgumentException.class.getName(),
+                    exception.getClass().getName());
+            return;
+        }
+
+        InetAddress inetAddress = null;
+        try {
+            inetAddress = InetAddress.getByName(input);
+        } catch (UnknownHostException e) {
+            e.printStackTrace();
+            Assert.fail();
+        }
+
+        Assert.assertEquals(matches, 
Boolean.valueOf(netMask.matches(inetAddress)));
+    }
+}

Propchange: tomcat/trunk/test/org/apache/catalina/util/TestNetMask.java
------------------------------------------------------------------------------
    svn:eol-style = native



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to