source: trunk/packages/invirt-dhcp/invirt-dhcpserver @ 2451

Last change on this file since 2451 was 2362, checked in by broder, 15 years ago

Revert the server identifier in the DHCP server. Including the server
ID implies that we're capable of handling unicast communication with
DHCP clients, which we're currently not.

This reverts commit 2361.

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