source: trunk/dhcp/dhcpserver.py @ 252

Last change on this file since 252 was 252, checked in by ecprice, 16 years ago

Allow fully qualified domains in the NICs table to override the
name.servers.csail.mit.edu domains.

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