source: trunk/packages/sipb-xen-dhcp/code/dhcpserver.py @ 309

Last change on this file since 309 was 300, checked in by ecprice, 17 years ago

Clear the sql cache for dns and dhcp, fixing #42.

  • Property svn:executable set to *
File size: 9.6 KB
Line 
1#!/usr/bin/python
2import sys
3sys.path.append('pydhcplib/')
4import pydhcplib
5import pydhcplib.dhcp_network
6from pydhcplib.dhcp_packet import *
7from pydhcplib.type_hw_addr import hwmac
8from pydhcplib.type_ipv4 import ipv4
9from pydhcplib.type_strlist import strlist
10import socket
11import IN
12
13import event_logger
14if '__main__' == __name__:
15    event_logger.init("stdout", 'DEBUG', {})
16from event_logger import Log
17
18import psycopg2
19import time
20import sipb_xen_database
21from sqlalchemy import create_engine
22
23dhcp_options = {'subnet_mask': '255.255.0.0',
24                'router': '18.181.0.1',
25                'domain_name_server': '18.70.0.160,18.71.0.151,18.72.0.3',
26                'domain_name': 'mit.edu',
27                'ip_address_lease_time': 60*60*24}
28
29class DhcpBackend:
30    def __init__(self, database=None):
31        if database is not None:
32            self.database = database
33            sipb_xen_database.connect(create_engine(database))
34    def findNIC(self, mac):
35        sipb_xen_database.clear_cache()
36        for i in range(3):
37            try:
38                value = sipb_xen_database.NIC.get_by(mac_addr=mac)
39            except psycopg2.OperationalError:
40                time.sleep(0.5)
41                if i == 2:  #Try twice to reconnect.
42                    raise
43                #Sigh.  SQLAlchemy should do this itself.
44                sipb_xen_database.connect(create_engine(self.database))
45            else:
46                break
47        return value
48    def find_interface(self, packet):
49        chaddr = hwmac(packet.GetHardwareAddress())
50        nic = self.findNIC(str(chaddr))
51        if nic is None or nic.ip is None:
52            return ("18.181.0.60", None)
53        ipstr = ''.join(reversed(['%02X' % i for i in ipv4(nic.ip).list()]))
54        for line in open('/proc/net/route'):
55            parts = line.split()
56            if parts[1] == ipstr:
57                Log.Output(Log.debug, "find_interface found "+str(nic.ip)+" on "+parts[0])
58                return ("18.181.0.60", parts[0])
59        return ("18.181.0.60", None)
60                           
61    def getParameters(self, **extra):
62        all_options=dict(dhcp_options)
63        all_options.update(extra)
64        options = {}
65        for parameter, value in all_options.iteritems():
66            if value is None:
67                continue
68            option_type = DhcpOptionsTypes[DhcpOptions[parameter]]
69
70            if option_type == "ipv4" :
71                # this is a single ip address
72                options[parameter] = map(int,value.split("."))
73            elif option_type == "ipv4+" :
74                # this is multiple ip address
75                iplist = value.split(",")
76                opt = []
77                for single in iplist :
78                    opt.extend(ipv4(single).list())
79                options[parameter] = opt
80            elif option_type == "32-bits" :
81                # This is probably a number...
82                digit = int(value)
83                options[parameter] = [digit>>24&0xFF,(digit>>16)&0xFF,(digit>>8)&0xFF,digit&0xFF]
84            elif option_type == "16-bits" :
85                digit = int(value)
86                options[parameter] = [(digit>>8)&0xFF,digit&0xFF]
87
88            elif option_type == "char" :
89                digit = int(value)
90                options[parameter] = [digit&0xFF]
91
92            elif option_type == "bool" :
93                if value=="False" or value=="false" or value==0 :
94                    options[parameter] = [0]
95                else : options[parameter] = [1]
96                   
97            elif option_type == "string" :
98                options[parameter] = strlist(value).list()
99               
100            else :
101                options[parameter] = strlist(value).list()
102        return options
103
104    def Discover(self, packet):
105        Log.Output(Log.debug,"dhcp_backend : Discover ")
106        chaddr = hwmac(packet.GetHardwareAddress())
107        nic = self.findNIC(str(chaddr))
108        if nic is None or nic.machine is None:
109            return False
110        ip = nic.ip
111        if ip is None:  #Deactivated?
112            return False
113        if nic.hostname and '.' in nic.hostname:
114            hostname = nic.hostname
115        elif nic.machine.name:
116            hostname = nic.machine.name + '.servers.csail.mit.edu'
117        else:
118            hostname = None
119        if ip is not None:
120            ip = ipv4(ip)
121            Log.Output(Log.debug,"dhcp_backend : Discover result = "+str(ip))
122            packet_parameters = self.getParameters(host_name=hostname)
123
124            # FIXME: Other offer parameters go here
125            packet_parameters["yiaddr"] = ip.list()
126           
127            packet.SetMultipleOptions(packet_parameters)
128            return True
129        return False
130       
131    def Request(self, packet):
132        Log.Output(Log.debug, "dhcp_backend : Request")
133       
134        discover = self.Discover(packet)
135       
136        chaddr = hwmac(packet.GetHardwareAddress())
137        request = packet.GetOption("request_ip_address")
138        if not request:
139            request = packet.GetOption("ciaddr")
140        yiaddr = packet.GetOption("yiaddr")
141
142        if not discover:
143            Log.Output(Log.info,"Unknown MAC address: "+str(chaddr))
144            return False
145       
146        if yiaddr!="0.0.0.0" and yiaddr == request :
147            Log.Output(Log.info,"Ack ip "+str(yiaddr)+" for "+str(chaddr))
148            return True
149        else:
150            Log.Output(Log.info,"Requested ip "+str(request)+" not available for "+str(chaddr))
151        return False
152
153    def Decline(self, packet):
154        pass
155    def Release(self, packet):
156        pass
157   
158
159class DhcpServer(pydhcplib.dhcp_network.DhcpServer):
160    def __init__(self, backend, options = {'client_listenport':68,'server_listenport':67}):
161        pydhcplib.dhcp_network.DhcpServer.__init__(self,"0.0.0.0",options["client_listen_port"],options["server_listen_port"],)
162        self.backend = backend
163        Log.Output(Log.debug, "__init__ DhcpServer")
164
165    def SendDhcpPacketTo(self, To, packet):
166        (ip, intf) = self.backend.find_interface(packet)
167        if intf:
168            out_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
169            out_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST,1)
170            out_socket.setsockopt(socket.SOL_SOCKET, IN.SO_BINDTODEVICE, intf)
171            #out_socket.bind((ip, self.listen_port))
172            ret = out_socket.sendto(packet.EncodePacket(), (To,self.emit_port))
173            out_socket.close()
174            return ret
175        else:
176            return self.dhcp_socket.sendto(packet.EncodePacket(),(To,self.emit_port))
177
178    def SendPacket(self, packet):
179        """Encode and send the packet."""
180       
181        giaddr = packet.GetOption('giaddr')
182
183        # in all case, if giaddr is set, send packet to relay_agent
184        # network address defines by giaddr
185        if giaddr!=[0,0,0,0] :
186            agent_ip = ".".join(map(str,giaddr))
187            self.SendDhcpPacketTo(agent_ip,packet)
188            Log.Output(Log.debug, "SendPacket to agent : "+agent_ip)
189
190        # FIXME: This shouldn't broadcast if it has an IP address to send
191        # it to instead. See RFC2131 part 4.1 for full details
192        else :
193            Log.Output(Log.debug, "No agent, broadcast packet.")
194            self.SendDhcpPacketTo("255.255.255.255",packet)
195           
196
197    def HandleDhcpDiscover(self, packet):
198        """Build and send DHCPOFFER packet in response to DHCPDISCOVER
199        packet."""
200
201        logmsg = "Get DHCPDISCOVER packet from " + hwmac(packet.GetHardwareAddress()).str()
202
203        Log.Output(Log.info, logmsg)
204        offer = DhcpPacket()
205        offer.CreateDhcpOfferPacketFrom(packet)
206       
207        if self.backend.Discover(offer):
208            self.SendPacket(offer)
209        # FIXME : what if false ?
210
211
212    def HandleDhcpRequest(self, packet):
213        """Build and send DHCPACK or DHCPNACK packet in response to
214        DHCPREQUEST packet. 4 types of DHCPREQUEST exists."""
215
216        ip = packet.GetOption("request_ip_address")
217        sid = packet.GetOption("server_identifier")
218        ciaddr = packet.GetOption("ciaddr")
219        #packet.PrintHeaders()
220        #packet.PrintOptions()
221
222        if sid != [0,0,0,0] and ciaddr == [0,0,0,0] :
223            Log.Output(Log.info, "Get DHCPREQUEST_SELECTING_STATE packet")
224
225        elif sid == [0,0,0,0] and ciaddr == [0,0,0,0] and ip :
226            Log.Output(Log.info, "Get DHCPREQUEST_INITREBOOT_STATE packet")
227
228        elif sid == [0,0,0,0] and ciaddr != [0,0,0,0] and not ip :
229            Log.Output(Log.info,"Get DHCPREQUEST_INITREBOOT_STATE packet")
230
231        else : Log.Output(Log.info,"Get DHCPREQUEST_UNKNOWN_STATE packet : not implemented")
232
233        if self.backend.Request(packet) : packet.TransformToDhcpAckPacket()
234        else : packet.TransformToDhcpNackPacket()
235
236        self.SendPacket(packet)
237
238
239
240    # FIXME: These are not yet implemented.
241    def HandleDhcpDecline(self, packet):
242        Log.Output(Log.info, "Get DHCPDECLINE packet")
243        self.backend.Decline(packet)
244       
245    def HandleDhcpRelease(self, packet):
246        Log.Output(Log.info,"Get DHCPRELEASE packet")
247        self.backend.Release(packet)
248       
249    def HandleDhcpInform(self, packet):
250        Log.Output(Log.info, "Get DHCPINFORM packet")
251
252        if self.backend.Request(packet) :
253            packet.TransformToDhcpAckPacket()
254            # FIXME : Remove lease_time from options
255            self.SendPacket(packet)
256
257        # FIXME : what if false ?
258
259if '__main__' == __name__:
260    options = { "server_listen_port":67,
261                "client_listen_port":68,
262                "listen_address":"0.0.0.0"}
263    backend = DhcpBackend('postgres://sipb-xen@sipb-xen-dev/sipb_xen')
264    server = DhcpServer(backend, options)
265
266    while True : server.GetNextDhcpPacket()
Note: See TracBrowser for help on using the repository browser.