我有一个存储为无符号整数的 IP 地址(ipNumeric)的表 A 和一个带有子网(subnetNumeric)的表 B:
INET_NTOA(ipNumeric) = 192.168.0.1
INET_NTOA(subnetNumeric) = 192.168.0.0
我想检查这个 IP 是否是子网的成员。
子网是 A、B 和 C 类。
这是否可以在 MySQL 中在合理的时间内即时完成,或者是否应该预先计算子网范围?
当然,这是可行的。这个想法是,我们通过将最高有效位设置为 1 来计算子网掩码,与子网类规定的一样多。对于 C 类,那将是
SELECT -1 << 8;
然后,将子网掩码与您拥有的 IP 地址相结合;如果 IP 在子网内,则结果应等于子网地址——标准网络内容。所以我们最终得到:
SELECT (-1 << 8) & INET_ATON("192.168.0.1") = INET_ATON("192.168.0.0");
更新:是的,有必要知道网络类或子网掩码(这是等效信息)。X.Y.0.0
考虑一下如果您没有此信息,您将如何处理子网所在的情况。这是X.Y.0.0/16
还是X.Y.0.0/8
第三个八位字节恰好是0?没办法知道。
如果您确实知道子网掩码,则查询可以写为
SELECT (-1 << (33 - INSTR(BIN(INET_ATON("255.255.255.0")), "0"))) &
INET_ATON("192.168.0.1") = INET_ATON("192.168.0.0");
这是我们使用的 MySQL 函数。
DELIMITER $$
DROP FUNCTION IF EXISTS `ip_in_subnet_mask`$$
CREATE DEFINER=`root`@`%` FUNCTION `ip_in_subnet_mask`(ip VARCHAR(20), subnet VARCHAR(20), netmask VARCHAR(20)) RETURNS TINYINT(1)
DETERMINISTIC
BEGIN
RETURN (INET_ATON(ip) & INET_ATON(netmask)) = INET_ATON(subnet);
END$$
DELIMITER ;
例如,查找 t.ip 在 192.168.0.x 范围内的所有行,其中 0<=x<=255
SELECT *
FROM t
WHERE
ip_in_subnet_mask(t.ip, "192.168.0.0", "255.255.255.0")
首先创建表来支持测试。
CREATE TABLE a (ipNumeric INT UNSIGNED);
INSERT INTO a (ipNumeric) VALUES(INET_ATON("172.16.40.101"));
INSERT INTO a (ipNumeric) VALUES(INET_ATON("192.168.1.12"));
INSERT INTO a (ipNumeric) VALUES(INET_ATON("10.1.5.51"));
INSERT INTO a (ipNumeric) VALUES(INET_ATON("10.7.1.78"));
CREATE TABLE b (subnetNumeric INT UNSIGNED);
INSERT INTO b (subnetNumeric) VALUES (INET_ATON("192.168.0.0"));
INSERT INTO b (subnetNumeric) VALUES (INET_ATON("10.1.0.0"));
INSERT INTO b (subnetNumeric) VALUES (INET_ATON("172.16.0.0"));
现在根据地址和掩码 = 子网进行选择查找表之间的匹配项。这里我们假设 B 类子网 (255.255.0.0)。无需移位,因为我们可以使用 INET_ATON() 函数。
SELECT INET_NTOA(a.ipNumeric),INET_NTOA(b.subnetNumeric) FROM a,b
WHERE (a.ipNumeric & INET_ATON("255.255.0.0")) = b.subnetNumeric;
+------------------------+----------------------------+
| INET_NTOA(a.ipNumeric) | INET_NTOA(b.subnetNumeric) |
+------------------------+----------------------------+
| 192.168.1.12 | 192.168.0.0 |
| 10.1.5.51 | 10.1.0.0 |
| 172.16.40.101 | 172.16.0.0 |
+------------------------+----------------------------+
当地址存储为字符串时,我有一个解决方案。因此,如果您希望将那部分作为您的算法,您可以在此处使用我的解决方案。
这有点棘手,尤其是对于 IPv6。在 IPv6 中,可以选择压缩段,例如 1::1,相当于 1:0:0:0:0:0:0:1,这是它很棘手的主要原因。
IPAddress Java 库将生成 mysql SQL 来搜索给定子网中的地址。该链接更详细地描述了这个问题。免责声明:我是项目经理。
基本算法是取子网地址的网络部分,然后取该部分的每个变体(例如上面的两个字符串是完整地址 1::1 的变体),然后计算段分隔符的数量,然后执行正在匹配的地址上的 mysql 子字符串,但也计算正在匹配的地址中的总分隔符。
这是示例代码:
public static void main(String[] args) throws IPAddressStringException {
System.out.println(getSearchSQL("columnX", "1.2.3.4/16"));
System.out.println(getSearchSQL("columnX", "1:2:3:4:5:6:7:8/64"));
System.out.println(getSearchSQL("columnX", "1::8/64"));
}
static String getSearchSQL(String expression, String ipAddressStr) throws IPAddressStringException {
IPAddressString ipAddressString = new IPAddressString(ipAddressStr);
IPAddress ipAddress = ipAddressString.toAddress();
IPAddressSection networkPrefix = ipAddress.getNetworkSection(ipAddress.getNetworkPrefixLength(), false);
StringBuilder sql = new StringBuilder("Select rows from table where ");
networkPrefix.getStartsWithSQLClause(sql, expression);
return sql.toString();
}
输出:
Select rows from table where (substring_index(columnX,'.',2) = '1.2')
Select rows from table where (substring_index(columnX,':',4) = '1:2:3:4')
Select rows from table where ((substring_index(columnX,':',4) = '1:0:0:0') OR ((substring_index(columnX,':',2) = '1:') AND (LENGTH (columnX) - LENGTH(REPLACE(columnX, ':', '')) <= 5)))