Cómo escaneamos toda Internet

¡Hola a todos! Mi nombre es Alexander y estoy escribiendo el código para 2ip.ru. Durante una buena mitad de los servicios, puedes patearme, listo para luchar. Hoy quiero contaros un poco sobre la reelaboración de uno de nuestros antiguos servicios. Ciertamente, esto no es "big data", pero sí una gran cantidad de información, así que creo que será interesante.





Vamos a hablar de Sitios en una IP , que, lo adivinaste, te permite conocer todos los dominios registrados en una IP. Es bastante conveniente ver quién se ha atascado en su servidor (sí, hay algunos) o en el de otra persona (por ejemplo, alojamiento compartido).





¿Cómo ha funcionado siempre? Fuimos a Bing desde una gran cantidad de direcciones y analizamos los resultados para una solicitud especial. Sí, la decisión fue regular, pero eso fue todo. Fue porque Bing atornilló las tuercas y decidimos hacerlo todo humanamente.





Base propia

¿Qué pasa si toma y analiza todo Internet? En general, esto no es un problema, pero no somos Google y no tenemos grandes recursos para rastrear. ¿O tenemos?





Hay un servidor con 12 núcleos y 64 gigas de memoria, y en el arsenal de MySQL, PHP, golang y un montón de todo tipo de frameworks. Obviamente, se pueden lograr buenos resultados con gorutinas. Golang es rápido y requiere recursos mínimos. Sobre la base de las preguntas, ¿extraerá todo el MySQL habitual?





Intentemos.





Haciendo un prototipo

Recopilar todos los dominios es una tarea ingrata, por lo que compramos una base de datos de dominios de 260 millones de registros. Hay muchos servicios que brindan sus servicios y cuestan un centavo.





Entonces, en mi disco, un archivo CSV de 5 GB de tamaño, es fácil, escriba un resolutor masivo que lea línea por línea y envíe un par "dominio - dirección IP" a la salida STDOUT





La única pregunta es el rendimiento, debe hacerlo muy, muy rápido, no podemos esperar un mes para obtener el resultado.





Unas pocas horas de trabajo y mi demonio está en movimiento. Maine resultó algo como esto:





func main() {
    file, err := os.Open("domains.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    maxGoroutines := 500
    guard := make(chan struct{}, maxGoroutines)

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        guard <- struct{}{}

        host := scanner.Text()
        go func(host string) {
            resolve(host)
            <-guard
        }(host)
    }

    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }
}

      
      



, 500 , 12 .





resolve , — IP STDOUT. DNS, A , .





DNS

, DNS IP, . undbound Docker.





DNS, . DNS , , . , .





Google DNS, , . 500 —  .





localhost

. 500 , . .





1000 12 , 500 . ~2000 .





, . , TLD .bar, .





tmux CSV 10 . .





! .





domain_ip, — IP. , IP .





IP - BIGINT domain - VARCHAR 255





, 260 . , IP , .





20 , . 260 . .





.





IP 20 200 . :





ALTER TABLE domain_ip PARTITION BY RANGE COLUMNS (ip)  (
    PARTITION p0 VALUES LESS THAN (200000000),
    PARTITION p1 VALUES LESS THAN (400000000),
    PARTITION p2 VALUES LESS THAN (600000000),
    PARTITION p3 VALUES LESS THAN (800000000),
    PARTITION p4 VALUES LESS THAN (1000000000),
    PARTITION p5 VALUES LESS THAN (1200000000),
    PARTITION p6 VALUES LESS THAN (1400000000),
    PARTITION p7 VALUES LESS THAN (1600000000),
    PARTITION p8 VALUES LESS THAN (1800000000),
    PARTITION p9 VALUES LESS THAN (2000000000),
    PARTITION p10 VALUES LESS THAN (2200000000),
    PARTITION p11 VALUES LESS THAN (2400000000),
    PARTITION p12 VALUES LESS THAN (2600000000),
    PARTITION p13 VALUES LESS THAN (2800000000),
    PARTITION p14 VALUES LESS THAN (3000000000),
    PARTITION p15 VALUES LESS THAN (3200000000),
    PARTITION p16 VALUES LESS THAN (3400000000),
    PARTITION p17 VALUES LESS THAN (3600000000),
    PARTITION p18 VALUES LESS THAN (3800000000),
    PARTITION p19 VALUES LESS THAN (4000000000),
    PARTITION p20 VALUES LESS THAN (MAXVALUE) 
);

      
      



, ?





Cualquiera que haya trabajado con MySQL sabe que verter grandes volúmenes de datos es una operación bastante larga. A lo largo de los años, no he encontrado nada mejor que importar datos desde CSV. Se parece a esto:





LOAD DATA INFILE '/tmp/domains.csv' IGNORE 
INTO TABLE domain_ip
FIELDS TERMINATED BY ',' 
LINES TERMINATED BY '\n'

      
      



La máquina digiere ~ 10 GB de CSV en 30 minutos.





El final

El resultado es un servicio tan agradable . Una selección de ~ 300 millones de registros se produce instantáneamente en un servidor bastante modesto para los estándares actuales. Necesita aproximadamente 8 GB de RAM para esto.





Ahora puede descubrir, por ejemplo, que la humanidad ha adjuntado 8194 dominios a la IP 8.8.8.8, bueno, o inventarlo usted mismo ...





Gracias por la atención.








All Articles