summaryrefslogtreecommitdiff
path: root/tasks/isc_dhcp_live_boot/task.py
blob: a6f4c5cbc9275fc1d3659ec1829ef52d89e5d206 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# SPDX-License-Identifier: AGPL-3.0-or-later

# TODO: dokoncaj!
# kpov_util should be imported by add_assignment.py

instructions = {
    'si': '''\
<p>
Postavi štiri navidezne računalnike: <em>simpleArbiter</em>, <em>DHCP_server</em>, <em>BootableClientA</em> in <em>BootableClientB</em>.

<p>
Na <em>DHCP_server</em> postavi strežnik DHCP s pomočjo ISC dhcp 3 na naslovu <code>{{IP_DHCP}}</code>. <em>SimpleArbiter</em> naj dobi <code>{{IP_GW}}</code>. <em>DHCP_server</em> naj ga uporabi kot privzeti prehod (angl. <em lang="en">gateway</em>).

<p>
Če se zaganja <em>BootableClientB</em>, naj se sistem zažene v datoteko z imenom <code>{{BOOT_FNAME}}</code>. Če se zaganja katerikoli drug, naj se sistem zažene z živega USB, ki ga predstavlja slika diska <code>bootable_usb</code>, ime datoteke z zagonskim nalagalnikom pa naj bo kakršno koli razen <code>{{BOOT_FNAME}}</code>.

<p>
Živi USB priklopite na na <em>DHCP_server</em> in z njega poberite datoteke, potrebne za zagon. Datoteke z nastavitvami za PXELinux, ki jih najdete na živem USB, <em>morajo</em> biti dostopne na korenskem imeniku strežnika TFTP.

<p>
Tako <em>BootableClientA</em> kot <em>BootableClientB</em> naj bosta brez diskov.
''',
    'en': '''\
<p>
Set up four virtual machines: <em>simpleArbiter</em>, <em>DHCP_server</em>, <em>BootableClientA</em> and <em>BootableClientB</em>.

<p>
On <em>DHCP_server</em>, set up a DHCP server using ISC dhcp 3. Set the IP address of this server to <code>{{IP_DHCP}}</code>. If <em>SimpleArbiter</em> requests an IP, have the DHCP server serve it <code>{{IP_GW}}</code>. <em>DHCP_server</em> should use <em>SimpleArbiter</em> as the gateway.

<p>
If <em>BootableClientB</em> tries to boot over the network, have the DHCP server tell it to boot from the file <code>{{BOOT_FNAME}}</code>. If any other system tries to boot over the network, have it boot from a live USB drive represented by the disk image <code>bootable_usb</code> and have the bootloader filename be different from <code>{{BOOT_FNAME}}</code>.

<p>
Connect the live USB to DHCP_server and copy the files neccessarry for it to boot. The PXELinux configuration files must be the same as the ones found on the live USB and <em>must</em> be accessible on the TFTP server root.

<p>
Both <em>BootableCLientA</em> and <em>BootableClientB</em> should be diskless.
''',
}

computers = {
    'DHCPServer': {
        'disks': [
            {   'name': 'student-DHCPServer',
            },
            {   'name': 'bootable_usb',
                'options':{'readonly': False},
                'parts': [ {'dev': 'sdb1', 'path':'/mnt'} ],
            },
        ],
        'network_interfaces': [{'network': 'net1'}],
        'flavor': 'm1.tiny',
        'config_drive': False
    },
    'BootableClientA': {
        'disks': [
        ],
        'network_interfaces': [{'network': 'net1'}],
        'flavor': 'm1.tiny',
        'config_drive': False
    },
    'BootableClientB': {
        'disks': [
        ],
        'network_interfaces': [{'network': 'net1'}],
        'flavor': 'm1.tiny',
        'config_drive': False
    },
    'SimpleArbiter': {
        'disks': [
            {   'name': 'simpleArbiterGW',
            },
        ],
        'network_interfaces': [{'network': 'net1'}, {'network': 'test-net'}],
        'flavor': 'm1.tiny',
        'config_drive': False
    }
}

networks = { 'net1': {'public': False}, 'test-net': {'public': True} }

params_meta = {
    'IP_DHCP': {'descriptions': {'si': 'IP DHCP streznika'}, 'w': False, 'public': True, 'type':'IP', 'generated': True},
    'IP_GW': {'descriptions': {'si': 'IP SimpleArbiterja'}, 'w': False, 'public': True, 'type':'IP', 'generated': True},
    'MAC_BOOT': {'descriptions': {'si': 'MAC racunalnika, ki se zazene z ISO'}, 'w': True, 'public': True, 'type':'MAC', 'generated': False},
    # 'IP_BOOT': {'descriptions': {'si': 'IP racunalnika, ki se zazene z ISO'}, 'w': True, 'public': True, 'type':'IP', 'generated': False},
    'TFTP_STRING': {'descriptions': {'si': 'vsebina'}, 'w': False, 'public': False, 'type':'short', 'generated': True},
    'BOOT_FNAME': {'descriptions': {'si': 'Ime datoteke'}, 'w': False, 'public': True, 'type': 'filename', 'generated': True},
}

def task(IP_DHCP, IP_GW, MAC_BOOT, BOOT_FNAME):
    # check the IP
    # TODO (polz): Do not use tabs instead of spaces!
    import pexpect
    import re
    import tftpy
    import io
    import time
    results={}
    # TODO (polz): Please use pexpect instead of os.system, it's much nicer.
    # Also, test your functions. This function was obviously never run.
    # 
    # check whether the fname served by the dhcp server is
    # correct
    # you should check the DHCP response from the server.
    # You can use dhcpdump to get some packets, dhcping to create a
    # DHCP Request. You may also use any other tool.
    # If you choose to use dhcping, do not forget to set the hw address 
    # and ip arguments. You can simply feed it MAC_BOOT and IP_BOOT.
    # dhcping -h MAC_BOOT -c IP_BOOT -V -r
    # could work (but you should test it)
    ip_str = pexpect.run('ip addr show')
    eth_re_str = r"ether (([0-9a-f]{{2}}:){{5}}[0-9a-f]{{2}})(.*)\r\n(.*){}(.*)\s(\w*)\r\n"
    ip_re = re.search(eth_re_str.format(IP_GW), ip_str)
    mac_SA = ip_re.group(1)
    eth_dev_SA = ip_re.group(6)
    dhcpdump = pexpect.spawn("sudo dhcpdump -i {}".format(eth_dev_SA))
    time.sleep(2)
    results['dhcping_other'] = pexpect.run('sudo dhcping -s {} -h {} -c {}'.format(
        IP_DHCP, MAC_BOOT, IP_GW))
    dhcpdump.expect('---------------------------------------------------------------------------')
    results['dhcpdump_other_req'] = dhcpdump.before
    dhcpdump.expect('---------------------------------------------------------------------------')
    results['dhcpdump_other_reply'] = dhcpdump.before
    dhcpdump.expect('---------------------------------------------------------------------------')
    results['dhcpdump_other_release'] = dhcpdump.before
    results['dhcping_SA'] = pexpect.run('sudo dhcping -s {} -h {} -c {}'.format(
        IP_DHCP, mac_SA, IP_GW))
    dhcpdump.expect('---------------------------------------------------------------------------')
    results['dhcpdump_SA_req'] = dhcpdump.before
    dhcpdump.expect('---------------------------------------------------------------------------')
    results['dhcpdump_SA_reply'] = dhcpdump.before
    dhcpdump.expect('---------------------------------------------------------------------------')
    results['dhcpdump_SA_release'] = dhcpdump.before
    dhcpdump.sendintr()
    tftp_client = pexpect.spawn('tftp {}'.format(IP_DHCP))
    tftp_client.expect(r'tftp>')
    tftp_client.sendline('get pxelinux.cfg/default /dev/stdout')
    tftp_client.expect(r'tftp>')
    results['tftp_string'] = tftp_client.before
    # check whether the fname served by the dhcp server is correct
    # connect to the service in the special ISO
    # check the MAC of the server on IP_BOOT
    return results
    
def gen_params(user_id, params_meta):
    params = dict()
    r = random.Random(user_id)
    net = kpov_util.IPv4_subnet_gen(r, '10.64.0.0/10', 24)
    params['IP_DHCP'], params['IP_GW'] = kpov_util.IPv4_addr_gen(r, net, 2)
    params['BOOT_FNAME'] = kpov_util.fname_gen(r)
    params['TFTP_STRING'] = kpov_util.alnum_gen(r, 45) 
    return params

def task_check(results, params):
    import re
    score = 0
    hints = [] 
    #TO FINISH SCORING WE REQUIRE DICT KEYS AND FUNCTIONS gen_params AND task TO BE FINISHED
    #POINTS FOR EACH TASK MAY BE ADJUSTED IN THE FUTURE
    if results['dhcping_other'].find(params['IP_DHCP']) >= 0:
        score += 1
    else:
        hints += ["DHCP wrong"]
    if results['dhcping_SA'].find(params['IP_DHCP']) >= 0:
        score += 1
    else:
        hints += ["DHCP wrong"]
    p = re.search(r"FNAME:(.*)\.\r",
        results['dhcpdump_other_reply'])
    if p is not None:
        other_fname = p.group(1).strip()
    else:
        other_fname = ''
    if other_fname == params['BOOT_FNAME']:
        score += 3
    else:
        hints += ["special fname wrong:" + other_fname]
    p = re.search(r"FNAME:(.*)\.\r",
        results['dhcpdump_SA_reply'])
    if p is not None:
        sa_fname = p.group(1).strip()
    else:
        sa_fname = ''
    if sa_fname != params['BOOT_FNAME']:
        score += 3
    else:
        hints += ["fname wrong:" + sa_fname]
    try:
        special_tftp = "# " + params['TFTP_STRING']
        tftp_end = results['tftp_string'].split('\r\r\n')[-1]
        assert tftp_end[:len(special_tftp)] == special_tftp
        score += 2
    except:
        hints += ["tftp wrong"]
    return score, hints

def prepare_disks(templates, task_params, global_params):
    d = templates['student-DHCPServer']
    s = """# {}""".format(task_params['TFTP_STRING'])
    d = templates['bootable_usb']
    d.write_append('/mnt/syslinux.cfg', s)
    d = templates['simpleArbiterGW']
    s = """auto lo
iface lo inet loopback

auto ens3
iface ens3 inet dhcp

auto enp0s3
iface enp0s3 inet dhcp

auto ens4
iface ens4 inet static
    address {IP_GW}
    netmask 255.192.0.0

auto enp0s8
iface enp0s8 inet static
    address {IP_GW}
    netmask 255.192.0.0
""".format(IP_GW = task_params['IP_GW'])
    d.write("/etc/network/interfaces", s)
    write_default_config(templates['simpleArbiterGW'], global_params)