2008-11-10 12 views
11

Me gustaría encontrar una forma de hacer una consulta SQL que calcule el cidr (representación de bits) de una máscara de subred almacenada en la base de datos. Entonces, por ejemplo, tengo 255.255.255.0 o su valor decimal (4294967040) almacenado en la base de datos. Me gustaría hacer una selección y obtener una representación de 24 a través de la consulta.Uso de SQL para determinar el valor de cidr de una máscara de subred

He hecho cosas como las siguientes para determinar la última IP de una subred, así que espero hacer algo similar para determinar la representación cidr de una máscara.

select concat(inet_ntoa(ip_addr),'-', 
    inet_ntoa(ip_addr+(POWER(2,32)-ip_mask-1))) range 
from subnets 
order by ip_addr 

Preferentemente, esta sería una instrucción SQL que funcionaría bajo MySQL, PostgreSQL, Oracle, etc.

Respuesta

8

Creo que he encontrado la solución a mi problema. Esto es lo que he hecho:

select CONCAT(INET_NTOA(ip_addr),'/',32-log2((4294967296-ip_mask))) net 
from subnets 
order by ip_addr 

Básicamente me tomo mi máscara decmial y restarlo del valor máximo decimal. Luego a un log2 en ese valor para obtener el valor del logaritmo. Luego, simplemente resta eso de 32 (el bit máximo disponible).

Espero que ayude a los demás.

Gracias

+0

log2 ... ¡increíble! :-) –

+0

Comprobé esto con todos los valores en esta tabla y encontré que es exacto: https://kthx.at/subnetmask/. – apostl3pol

2

consultas SQL no tienen una estructura iterativa del procedimiento (a pesar de lenguaje de procedimientos), pero se puede comparar una conjunto de filas a otro conjunto de filas, que es como un bucle.

Solo tiene 32 posibles máscaras de subred. En casos como este, tiene sentido crear una pequeña tabla que almacene estas 32 máscaras y el número CIDR asociado.

CREATE TABLE cidr (
    bits INT UNSIGNED PRIMARY KEY, 
    mask INT UNSIGNED NOT NULL 
); 

INSERT INTO cidr (bits) VALUES 
    (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), 
    (11), (12), (13), (14), (15), (16), (17), (18), (19), (20), 
    (21), (22), (23), (24), (25), (26), (27), (28), (29), (30), 
    (31), (32); 

UPDATE cidr SET mask = ((POWER(2,32)-1)<<(32-bits)) & (POWER(2,32)-1); 

SELECT CONCAT(s.ip_addr, '/', c.bits) 
FROM cidr c JOIN subnets s ON (c.mask = inet_aton(s.ip_mask)); 
+0

Adición de una mesa era lo que había esperado evitar. Esperaba que hubiera más cálculos matemáticos que pudieran hacerse para convertir de decimal a binario y luego contar los bits. Gracias, sin embargo, esto fue útil –

0
-- 
-- Dumping routines for database 
-- 
/*!50003 DROP FUNCTION IF EXISTS `INET_ATOC` */; 
/*!50003 SET @saved_cs_client  = @@character_set_client */ ; 
/*!50003 SET @saved_cs_results  = @@character_set_results */ ; 
/*!50003 SET @saved_col_connection = @@collation_connection */ ; 
/*!50003 SET character_set_client = utf8 */ ; 
/*!50003 SET character_set_results = utf8 */ ; 
/*!50003 SET collation_connection = utf8_general_ci */ ; 
/*!50003 SET @saved_sql_mode  = @@sql_mode */ ; 
/*!50003 SET sql_mode    = 'ALLOW_INVALID_DATES' */ ; 
DELIMITER ;; 
CREATE DEFINER=`root`@`localhost` FUNCTION `INET_ATOC`(`paramNETMASK` varchar(15)) RETURNS int(2) unsigned 
    DETERMINISTIC 
    COMMENT 'Converts an IPv4 netmask in dotted decimal notation to a CIDR integer between 0 and 32' 
BEGIN 
    DECLARE `netmask` int unsigned; 
    DECLARE `cidr` int unsigned; 
    SET `netmask` = INET_ATON(`paramNETMASK`); 
    IF (`netmask` IS NULL) 
    THEN 
     RETURN NULL; 
    ELSE 
     SET `cidr` = 0; 
     countNetBits: WHILE (`cidr` < 32) 
     DO 
      IF ((0x80000000 & `netmask`) = 0x80000000) 
      THEN 
       SET `netmask` = 0xFFFFFFFF & (`netmask` << 1); 
       SET `cidr` = `cidr` + 1; 
      ELSE 
       LEAVE countNetBits; 
      END IF; 
     END WHILE; 
     IF (`netmask` != 0) 
     THEN 
      RETURN NULL; 
     END IF; 
     RETURN `cidr`; 
    END IF; 
END ;; 
DELIMITER ; 
/*!50003 SET sql_mode    = @saved_sql_mode */ ; 
/*!50003 SET character_set_client = @saved_cs_client */ ; 
/*!50003 SET character_set_results = @saved_cs_results */ ; 
/*!50003 SET collation_connection = @saved_col_connection */ ; 
/*!50003 DROP FUNCTION IF EXISTS `INET_CTOA` */; 
/*!50003 SET @saved_cs_client  = @@character_set_client */ ; 
/*!50003 SET @saved_cs_results  = @@character_set_results */ ; 
/*!50003 SET @saved_col_connection = @@collation_connection */ ; 
/*!50003 SET character_set_client = utf8 */ ; 
/*!50003 SET character_set_results = utf8 */ ; 
/*!50003 SET collation_connection = utf8_general_ci */ ; 
/*!50003 SET @saved_sql_mode  = @@sql_mode */ ; 
/*!50003 SET sql_mode    = 'ALLOW_INVALID_DATES' */ ; 
DELIMITER ;; 
CREATE DEFINER=`root`@`localhost` FUNCTION `INET_CTOA`(`paramCIDR` int) RETURNS varchar(15) CHARSET utf8 
    DETERMINISTIC 
    COMMENT 'Converts a CIDR suffix (integer between 0 and 32) to an IPv4 netmask in dotted decimal notation' 
BEGIN 
    DECLARE `netmask` int unsigned; 
    IF ((`paramCIDR` < 0) OR (`paramCIDR` > 32)) 
    THEN 
     RETURN NULL; 
    ELSE 
     SET `netmask` = 0xFFFFFFFF - (pow(2, (32-`paramCIDR`)) - 1); 
     RETURN INET_NTOA(`netmask`); 
    END IF; 
END ;; 
DELIMITER ; 
/*!50003 SET sql_mode    = @saved_sql_mode */ ; 
/*!50003 SET character_set_client = @saved_cs_client */ ; 
/*!50003 SET character_set_results = @saved_cs_results */ ; 
/*!50003 SET collation_connection = @saved_col_connection */ ; 
/*!50003 DROP PROCEDURE IF EXISTS `getSubnet` */; 
/*!50003 SET @saved_cs_client  = @@character_set_client */ ; 
/*!50003 SET @saved_cs_results  = @@character_set_results */ ; 
/*!50003 SET @saved_col_connection = @@collation_connection */ ; 
/*!50003 SET character_set_client = utf8 */ ; 
/*!50003 SET character_set_results = utf8 */ ; 
/*!50003 SET collation_connection = utf8_general_ci */ ; 
/*!50003 SET @saved_sql_mode  = @@sql_mode */ ; 
/*!50003 SET sql_mode    = '' */ ; 
DELIMITER ;; 
CREATE DEFINER=`root`@`localhost` PROCEDURE `getSubnet`(INOUT `paramADDR` VARCHAR(15), INOUT `paramCIDR` INT, OUT `paramMASK` VARCHAR(15), OUT `paramNETWORK` VARCHAR(15), OUT `paramBROADCAST` VARCHAR(15), OUT `paramNUMHOSTS` INT) CHARSET utf8 
    DETERMINISTIC 
BEGIN 
    DECLARE `numaddrs` int unsigned; 
    DECLARE `ipaddr` int unsigned; 
    DECLARE `netmask` int unsigned; 
    DECLARE `wildcard` int unsigned; 
    DECLARE `network` int unsigned; 
    DECLARE `broadcast` int unsigned; 
    DECLARE `numhosts` int unsigned; 

    SET `ipaddr` = INET_ATON(`paramADDR`); 

    IF (`ipaddr` IS NULL) OR (`paramCIDR` < 1) OR (`paramCIDR` > 30) 
    THEN 
     SELECT 
      NULL, NULL, NULL, NULL, NULL, NULL 
     INTO 
      `paramADDR`, `paramCIDR`, `paramMASK`, `paramNETWORK`, `paramBROADCAST`, `paramNUMHOSTS`; 
    ELSE 
     SET `numaddrs` = pow(2, (32-`paramCIDR`)); 
     SET `numhosts` = `numaddrs` - 2; 
     SET `netmask` = 0xFFFFFFFF - (`numaddrs` - 1); 
     SET `wildcard` = 0xFFFFFFFF & (~`netmask`); 
     SET `network` = `ipaddr` & `netmask`; 
     SET `broadcast` = `ipaddr` | `wildcard`; 

     SELECT 
      INET_NTOA(`ipaddr`), `paramCIDR`, INET_NTOA(`netmask`), INET_NTOA(`network`), INET_NTOA(`broadcast`), `numhosts` 
     INTO 
      `paramADDR`, `paramCIDR`, `paramMASK`, `paramNETWORK`, `paramBROADCAST`, `paramNUMHOSTS`; 
    END IF; 
END ;; 
DELIMITER ; 
/*!50003 SET sql_mode    = @saved_sql_mode */ ; 
/*!50003 SET character_set_client = @saved_cs_client */ ; 
/*!50003 SET character_set_results = @saved_cs_results */ ; 
/*!50003 SET collation_connection = @saved_col_connection */ ; 
/*!40103 SET [email protected]_TIME_ZONE */; 

/*!40101 SET [email protected]_SQL_MODE */; 
/*!40014 SET [email protected]_FOREIGN_KEY_CHECKS */; 
/*!40014 SET [email protected]_UNIQUE_CHECKS */; 
/*!40101 SET [email protected]_CHARACTER_SET_CLIENT */; 
/*!40101 SET [email protected]_CHARACTER_SET_RESULTS */; 
/*!40101 SET [email protected]_COLLATION_CONNECTION */; 
/*!40111 SET [email protected]_SQL_NOTES */; 
0

por ejemplo necesita convertir 255.255.255.252 netmask en máscara de bits.

Siempre lo uso simple consulta (PostgreSQL):

SELECT 32-length(trim(((split_part('255.255.255.252','.',1)::bigint*(256^3)::bigint + 
         split_part('255.255.255.252','.',2)::bigint*(256^2)::bigint + 
         split_part('255.255.255.252','.',3)::bigint*256 + 
         split_part('255.255.255.252','.',4)::bigint)::bit(32))::text,'1')); 

no es tan agradable como podría ser, pero es corto y el trabajo como un encanto ..

Cuestiones relacionadas