1## This file is part of Scapy
2## See http://www.secdev.org/projects/scapy for more informations
3## Copyright (C) Philippe Biondi <phil@secdev.org>
4## This program is published under a GPLv2 license
5
6"""
7Resolve Autonomous Systems (AS).
8"""
9
10
11from __future__ import absolute_import
12import socket, errno
13from scapy.config import conf
14from scapy.compat import *
15
16class AS_resolver:
17    server = None
18    options = "-k"
19    def __init__(self, server=None, port=43, options=None):
20        if server is not None:
21            self.server = server
22        self.port = port
23        if options is not None:
24            self.options = options
25
26    def _start(self):
27        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
28        self.s.connect((self.server,self.port))
29        if self.options:
30            self.s.send(self.options.encode("utf8")+b"\n")
31            self.s.recv(8192)
32    def _stop(self):
33        self.s.close()
34
35    def _parse_whois(self, txt):
36        asn,desc = None,b""
37        for l in txt.splitlines():
38            if not asn and l.startswith(b"origin:"):
39                asn = plain_str(l[7:].strip())
40            if l.startswith(b"descr:"):
41                if desc:
42                    desc += r"\n"
43                desc += l[6:].strip()
44            if asn is not None and desc:
45                break
46        return asn, plain_str(desc.strip())
47
48    def _resolve_one(self, ip):
49        self.s.send(("%s\n" % ip).encode("utf8"))
50        x = b""
51        while not (b"%" in x  or b"source" in x):
52            x += self.s.recv(8192)
53        asn, desc = self._parse_whois(x)
54        return ip,asn,desc
55    def resolve(self, *ips):
56        self._start()
57        ret = []
58        for ip in ips:
59            ip,asn,desc = self._resolve_one(ip)
60            if asn is not None:
61                ret.append((ip,asn,desc))
62        self._stop()
63        return ret
64
65class AS_resolver_riswhois(AS_resolver):
66    server = "riswhois.ripe.net"
67    options = "-k -M -1"
68
69
70class AS_resolver_radb(AS_resolver):
71    server = "whois.ra.net"
72    options = "-k -M"
73
74
75class AS_resolver_cymru(AS_resolver):
76    server = "whois.cymru.com"
77    options = None
78    def resolve(self, *ips):
79        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
80        s.connect((self.server,self.port))
81        s.send(b"begin\r\n"+b"\r\n".join(ip.encode("utf8") for ip in ips)+b"\r\nend\r\n")
82        r = b""
83        while True:
84            l = s.recv(8192)
85            if l == b"":
86                break
87            r += l
88        s.close()
89
90        return self.parse(r)
91
92    def parse(self, data):
93        """Parse bulk cymru data"""
94
95        ASNlist = []
96        for l in data.splitlines()[1:]:
97            l = plain_str(l)
98            if "|" not in l:
99                continue
100            asn, ip, desc = [elt.strip() for elt in l.split('|')]
101            if asn == "NA":
102                continue
103            asn = "AS%s" % asn
104            ASNlist.append((ip, asn, desc))
105        return ASNlist
106
107class AS_resolver_multi(AS_resolver):
108    resolvers_list = ( AS_resolver_riswhois(),AS_resolver_radb(),AS_resolver_cymru() )
109    def __init__(self, *reslist):
110        if reslist:
111            self.resolvers_list = reslist
112    def resolve(self, *ips):
113        todo = ips
114        ret = []
115        for ASres in self.resolvers_list:
116            try:
117                res = ASres.resolve(*todo)
118            except socket.error as e:
119                if e[0] in [errno.ECONNREFUSED, errno.ETIMEDOUT, errno.ECONNRESET]:
120                    continue
121            resolved = [ ip for ip,asn,desc in res ]
122            todo = [ ip for ip in todo if ip not in resolved ]
123            ret += res
124            if len(todo) == 0:
125                break
126        if len(ips) != len(ret):
127            raise RuntimeError("Could not contact whois providers")
128        return ret
129
130
131conf.AS_resolver = AS_resolver_multi()
132