如何检查 IP 地址是不是来自 Java 中的特定网络/网络掩码?

Posted

技术标签:

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

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

【问题讨论】:

【参考方案1】:

选项 1:

使用spring-security-web 的IpAddressMatcher。与 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,以确保您在使用此代码时没有违反上述许可规定的任何条款. (当然,我将这段代码发布到 ***.com 并不违法。)

【讨论】:

【参考方案2】:

Apache Commons Net 的 org.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 版本来避免此错误。

【讨论】:

小心使用它。有一个错误会阻止它正常工作。您可能希望将其从 SVN 中提取出来。 mail-archives.apache.org/mod_mbox/commons-issues/200902.mbox/… @carson:感谢您的警告。我编辑了我的答案以包含这些信息。 这个好像也不支持 IPv6【参考方案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 地址。他们的简短文档提供了有关如何检查地址是否在 IPv4 和 IPv6 的特定范围内的示例

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;

【讨论】:

以上是关于如何检查 IP 地址是不是来自 Java 中的特定网络/网络掩码?的主要内容,如果未能解决你的问题,请参考以下文章

如何检查一个IP地址是不是在PHP中的两个IP范围内?

PHP:我如何通过他/她的 IP 地址确定某人是不是来自美国?

无法检查特定 IP 地址的可达性。是不是有任何 MAC 框架支持此功能

如何让套接字仅接受来自本地主机的连接(在 Java 中)?

Java:检查当前日期是不是在特定日期范围内[重复]

如何检查我的 C# 应用程序是不是使用来自 nuget 包的正确平台特定 dll?