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