【问题标题】:How to check if an IP address is from a particular network/netmask in Java?如何检查 IP 地址是否来自 Java 中的特定网络/网络掩码?
【发布时间】:2010-10-09 07:22:39
【问题描述】:

我需要确定给定的 IP 地址是否来自某个特殊网络,以便自动进行身份验证。

【问题讨论】:

    标签: java networking ip


    【解决方案1】:

    选项 1:

    使用spring-security-webIpAddressMatcher。与 Apache Commons Net 不同,它同时支持 ipv4 和 ipv6。

    import org.springframework.security.web.util.matcher.IpAddressMatcher;
    ...
    
    private void checkIpMatch() {
        matches("192.168.2.1", "192.168.2.1"); // true
        matches("192.168.2.1", "192.168.2.0/32"); // false
        matches("192.168.2.5", "192.168.2.0/24"); // true
        matches("92.168.2.1", "fe80:0:0:0:0:0:c0a8:1/120"); // false
        matches("fe80:0:0:0:0:0:c0a8:11", "fe80:0:0:0:0:0:c0a8:1/120"); // true
        matches("fe80:0:0:0:0:0:c0a8:11", "fe80:0:0:0:0:0:c0a8:1/128"); // false
        matches("fe80:0:0:0:0:0:c0a8:11", "192.168.2.0/32"); // false
    }
    
    private boolean matches(String ip, String subnet) {
        IpAddressMatcher ipAddressMatcher = new IpAddressMatcher(subnet);
        return ipAddressMatcher.matches(ip);
    }
    

    选项 2(轻量级解决方案!):

    上一部分的代码运行良好,但需要包含spring-security-web

    如果你不愿意在你的项目中包含 Spring 框架,你可以使用这个类,它是 Spring 的 original class 的略微修改版本,因此它没有非 JRE 依赖项。

    /*
     * Copyright 2002-2019 the original author or authors.
     *
     * 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
     *
     *      https://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.
     */
    
    import java.net.InetAddress;
    import java.net.UnknownHostException;
    
    /**
     * Matches a request based on IP Address or subnet mask matching against the remote
     * address.
     * <p>
     * Both IPv6 and IPv4 addresses are supported, but a matcher which is configured with an
     * IPv4 address will never match a request which returns an IPv6 address, and vice-versa.
     *
     * @author Luke Taylor
     * @since 3.0.2
     * 
     * Slightly modified by omidzk to have zero dependency to any frameworks other than the JRE.
     */
    public final class IpAddressMatcher {
        private final int nMaskBits;
        private final InetAddress requiredAddress;
    
        /**
         * Takes a specific IP address or a range specified using the IP/Netmask (e.g.
         * 192.168.1.0/24 or 202.24.0.0/14).
         *
         * @param ipAddress the address or range of addresses from which the request must
         * come.
         */
        public IpAddressMatcher(String ipAddress) {
    
            if (ipAddress.indexOf('/') > 0) {
                String[] addressAndMask = ipAddress.split("/");
                ipAddress = addressAndMask[0];
                nMaskBits = Integer.parseInt(addressAndMask[1]);
            }
            else {
                nMaskBits = -1;
            }
            requiredAddress = parseAddress(ipAddress);
            assert  (requiredAddress.getAddress().length * 8 >= nMaskBits) :
                    String.format("IP address %s is too short for bitmask of length %d",
                            ipAddress, nMaskBits);
        }
    
        public boolean matches(String address) {
            InetAddress remoteAddress = parseAddress(address);
    
            if (!requiredAddress.getClass().equals(remoteAddress.getClass())) {
                return false;
            }
    
            if (nMaskBits < 0) {
                return remoteAddress.equals(requiredAddress);
            }
    
            byte[] remAddr = remoteAddress.getAddress();
            byte[] reqAddr = requiredAddress.getAddress();
    
            int nMaskFullBytes = nMaskBits / 8;
            byte finalByte = (byte) (0xFF00 >> (nMaskBits & 0x07));
    
            // System.out.println("Mask is " + new sun.misc.HexDumpEncoder().encode(mask));
    
            for (int i = 0; i < nMaskFullBytes; i++) {
                if (remAddr[i] != reqAddr[i]) {
                    return false;
                }
            }
    
            if (finalByte != 0) {
                return (remAddr[nMaskFullBytes] & finalByte) == (reqAddr[nMaskFullBytes] & finalByte);
            }
    
            return true;
        }
    
        private InetAddress parseAddress(String address) {
            try {
                return InetAddress.getByName(address);
            }
            catch (UnknownHostException e) {
                throw new IllegalArgumentException("Failed to parse address" + address, e);
            }
        }
    }
    

    注意:请注意,使用此选项时,您有责任仔细检查license,以确保您在使用此代码时没有违反上述许可规定的任何条款. (当然,我将这段代码发布到 Stackoverflow.com 并不违法。)

    【讨论】:

      【解决方案2】:

      Apache Commons Netorg.apache.commons.net.util.SubnetUtils 似乎可以满足您的需求。看起来你做了这样的事情:

      SubnetInfo subnet = (new SubnetUtils("10.10.10.0", "255.255.255.128")).getInfo();
      boolean test = subnet.isInRange("10.10.10.10");
      

      注意,正如carson 指出的那样,Apache Commons Net 有a bug,这会阻止它在某些情况下给出正确的答案。 Carson 建议使用 SVN 版本来避免此错误。

      【讨论】:

      【解决方案3】:

      你也可以试试

      boolean inSubnet = (ip & netmask) == (subnet & netmask);
      

      或更短

      boolean inSubnet = (ip ^ subnet) & netmask == 0;
      

      【讨论】:

      • ip 和网络掩码是整数还是长整数?
      • 32 位地址 IPv4 是整数。我怀疑 IPv6 是 64 位值,但我自己没用过 hem
      • IPv6 是 128 位值(16 字节),因此它们不能存储在单个 long 中
      【解决方案4】:

      The open-source IPAddress Java library 将以多态方式为 IPv4 和 IPv6 执行此操作并处理子网。免责声明:我是该库的项目经理。

      示例代码:

      contains("10.10.20.0/30", "10.10.20.3");
      contains("10.10.20.0/30", "10.10.20.5");
      contains("1::/64", "1::1");
      contains("1::/64", "2::1");
      contains("1::3-4:5-6", "1::4:5");       
      contains("1-2::/64", "2::");
      contains("bla", "foo");
      
      static void contains(String network, String address) {
          IPAddressString one = new IPAddressString(network);
          IPAddressString two = new IPAddressString(address);
          System.out.println(one +  " contains " + two + " " + one.contains(two));
      }
      

      输出:

      10.10.20.0/30 contains 10.10.20.3 true
      10.10.20.0/30 contains 10.10.20.5 false
      1::/64 contains 1::1 true
      1::/64 contains 2::1 false
      1::3-4:5-6 contains 1::4:5 true
      1-2::/64 contains 2:: true
      bla contains foo false
      

      【讨论】:

      • 优秀的图书馆!我真的很喜欢它的易用性,并且它在绝大多数情况下都有效。但是,对于@Omid 的回复中所述的 IPv5 案例,它不能正常工作。能否请您合并该功能?
      • 差异是故意的,只是解释和库实现的差异,请参阅github.com/seancfoley/IPAddress/issues/40
      【解决方案5】:

      这是一个适用于 IPv4 和 IPv6 的版本,一个带有前缀,一个带有网络掩码。

      /**
       * Check if IP is within an Subnet defined by Network Address and Network Mask
       * @param  ip
       * @param  net
       * @param  mask
       * @return
       */
      public static final boolean isIpInSubnet(final String ip, final String net, final int prefix) {
          try {
              final byte[] ipBin   = java.net.InetAddress.getByName(ip  ).getAddress();
              final byte[] netBin  = java.net.InetAddress.getByName(net ).getAddress();
              if(ipBin.length  != netBin.length ) return false;
              int p = prefix;
              int i = 0;
              while(p>=8) { if(ipBin[i] != netBin[i] ) return false; ++i; p-=8; }
              final int m = (65280 >> p) & 255;
              if((ipBin[i] & m) != (netBin[i]&m) ) return false;
      
              return true;
          } catch(final Throwable t) {
              return false;
          }
      }
      
      /**
       * Check if IP is within an Subnet defined by Network Address and Network Mask
       * @param  ip
       * @param  net
       * @param  mask
       * @return
       */
      public static final boolean isIpInSubnet(final String ip, final String net, final String mask) {
          try {
              final byte[] ipBin   = java.net.InetAddress.getByName(ip  ).getAddress();
              final byte[] netBin  = java.net.InetAddress.getByName(net ).getAddress();
              final byte[] maskBin = java.net.InetAddress.getByName(mask).getAddress();
              if(ipBin.length  != netBin.length ) return false;
              if(netBin.length != maskBin.length) return false;
              for(int i = 0; i < ipBin.length; ++i) if((ipBin[i] & maskBin[i]) != (netBin[i] & maskBin[i])) return false;
              return true;
          } catch(final Throwable t) {
              return false;
          }
      }
      

      【讨论】:

        【解决方案6】:

        我知道这是一个非常古老的问题,但是当我想解决同样的问题时,我偶然发现了这个问题。

        我相信commons-ip-math 库做得很好。请注意,截至 2019 年 5 月,该库没有任何更新(可能是它已经非常成熟的库)。它可以在maven-central

        它支持使用 IPv4 和 IPv6 地址。他们的简短文档提供了有关如何检查地址是否在 IPv4IPv6 的特定范围内的示例

        IPv4 范围检查示例:

                String input1 = "192.168.1.0";
                Ipv4 ipv41 = Ipv4.parse(input1);
        
                // Using CIDR notation to specify the networkID and netmask
                Ipv4Range range = Ipv4Range.parse("192.168.0.0/24");
                boolean result = range.contains(ipv41);
                System.out.println(result); //false
        
                String input2 = "192.168.0.251";
                Ipv4 ipv42 = Ipv4.parse(input2);
        
                // Specifying the range with a start and end.
                Ipv4 start = Ipv4.of("192.168.0.0");
                Ipv4 end = Ipv4.of("192.168.0.255");
                range = Ipv4Range.from(start).to(end);
        
                result = range.contains(ipv42); //true
                System.out.println(result);
        

        【讨论】:

          【解决方案7】:

          为了检查子网中的 IP,我使用了 SubnetUtils 类中的 isInRange 方法。但是这种方法有一个错误,如果您的子网是 X,那么每个低于 X 的 IP 地址,isInRange 都会返回 true。例如,如果您的子网是 10.10.30.0/24,并且您想检查 10.10.20.5,则此方法返回 true。为了处理这个错误,我使用了下面的代码。

          public static void main(String[] args){
              String list = "10.10.20.0/24";
              String IP1 = "10.10.20.5";
              String IP2 = "10.10.30.5";
              SubnetUtils  subnet = new SubnetUtils(list);
              SubnetUtils.SubnetInfo subnetInfo = subnet.getInfo();
              if(MyisInRange(subnetInfo , IP1) == true)
                 System.out.println("True");
              else 
                 System.out.println("False");
              if(MyisInRange(subnetInfo , IP2) == true)
                 System.out.println("True");
              else
                 System.out.println("False");
          }
          
          private boolean MyisInRange(SubnetUtils.SubnetInfo info, String Addr )
          {
              int address = info.asInteger( Addr );
              int low = info.asInteger( info.getLowAddress() );
              int high = info.asInteger( info.getHighAddress() );
              return low <= address && address <= high;
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-01-25
            • 1970-01-01
            • 2017-09-02
            • 2013-03-03
            • 1970-01-01
            • 1970-01-01
            • 2010-12-31
            • 2016-12-27
            相关资源
            最近更新 更多