source: trunk/packages/sipb-xen-dhcp/files/usr/sbin/sipb-xen-dhcpserver @ 1036

Last change on this file since 1036 was 1035, checked in by broder, 16 years ago

Install the DHCP server through more standard means, since the DHCP
server code is fairly stable

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