2012-04-03 20 views
5

Tengo una tabla A con las direcciones IP (ipNumeric) almacenados como enteros sin signo y una tabla B con subredes (subnetNumeric):Comprobar si es IP en la subred

INET_NTOA(ipNumeric) = 192.168.0.1 
INET_NTOA(subnetNumeric) = 192.168.0.0 

me gustaría comprobar si esta IP es un miembro de una subred.

Las subredes son de clase A, B y C.

Es esto posible hacerlo en un plazo razonable en MySQL sobre la marcha o debe ser precalculados los rangos de subred?

Respuesta

11

Claro, es factible. La idea es que calculemos la máscara de subred configurando los bits más significativos en 1, tantos como dicte la clase de subred. Para una clase C, eso sería

SELECT -1 << 8; 

Luego, AND la máscara de subred con la dirección IP que tiene; si la IP está dentro de la subred, el resultado debe ser igual a la dirección de la subred: elementos de red estándar. Así nos encontramos con:

SELECT (-1 << 8) & INET_ATON("192.168.0.1") = INET_ATON("192.168.0.0"); 

Actualización: Sí, es necesario conocer la clase de red o la máscara de subred (que es una información equivalente). Considere cómo podría manejar el caso donde la subred es X.Y.0.0 si no tenía esta información. ¿Es esto X.Y.0.0/16 o X.Y.0.0/8 donde el tercer octeto sucede que es 0? No hay forma de saberlo

Si lo hace saber la máscara de subred, la consulta puede escribirse como

SELECT (-1 << (33 - INSTR(BIN(INET_ATON("255.255.255.0")), "0"))) & 
     INET_ATON("192.168.0.1") = INET_ATON("192.168.0.0"); 
+0

Gracias, pero es esto depende de saber la clase de subred? – matiasf

+0

@matiasf: Lo es. Eche un vistazo a la actualización. – Jon

+0

Excelente respuesta, pero ¿qué pasa con IPv6? ¿Es eso posible? – TSG

0

En primer lugar crear tablas para apoyar la prueba.

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")); 

Ahora haz coincide con un hallazgo de selección entre las tablas basadas en dirección & máscara de subred =. Aquí suponemos subredes de clase B (255.255.0.0). No es necesario cambiar de bit ya que podemos usar la función 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     | 
+------------------------+----------------------------+ 
0

Esta es la función de MySQL que utilizamos.

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 ; 

e.g. Buscar todas las filas donde t.ip está en el rango 192.168.0.x donde 0 = x < < = 255

SELECT * 
FROM t 
WHERE 
    ip_in_subnet_mask(t.ip, "192.168.0.0", "255.255.255.0") 
0

tengo una solución para cuando las direcciones se almacenan como cadenas. Entonces, si desea hacer esa parte de su algoritmo, puede usar mi solución aquí.

Es un poco complicado, especialmente para IPv6. En IPv6 opcionalmente puede haber segmentos comprimidos, como 1 :: 1 que es equivalente a 1: 0: 0: 0: 0: 0: 0: 1, que es la razón principal por la que es complicado.

The IPAddress Java library producirá mysql SQL para buscar direcciones en una subred determinada. El enlace describe este problema con más detalle. Descargo de responsabilidad: soy el gerente del proyecto.

El algoritmo básico es tomar la sección de red de la dirección de subred, luego tomar cada variante de esa sección (por ejemplo, las dos cadenas anteriores son variantes de la dirección completa 1 :: 1), luego contar el número de segmento separadores, luego haga una subcadena mysql en la dirección que coincida, pero también cuente los separadores totales en la dirección que coincida.

Aquí es código de ejemplo:

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(); 
} 

ouptut:

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))) 
Cuestiones relacionadas