modules/admin-full: add 6to4 configuration support
[project/luci.git] / modules / admin-full / luasrc / model / cbi / admin_network / ifaces.lua
1 --[[
2 LuCI - Lua Configuration Interface
3
4 Copyright 2008 Steven Barth <steven@midlink.org>
5 Copyright 2008 Jo-Philipp Wich <xm@subsignal.org>
6
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
10
11 http://www.apache.org/licenses/LICENSE-2.0
12
13 $Id$
14 ]]--
15
16 local fs = require "nixio.fs"
17 local ut = require "luci.util"
18 local nw = require "luci.model.network"
19 local fw = require "luci.model.firewall"
20
21 arg[1] = arg[1] or ""
22
23 local has_3g = fs.access("/usr/bin/gcom")
24 local has_pptp = fs.access("/usr/sbin/pptp")
25 local has_pppd = fs.access("/usr/sbin/pppd")
26 local has_pppoe = fs.glob("/usr/lib/pppd/*/rp-pppoe.so")()
27 local has_pppoa = fs.glob("/usr/lib/pppd/*/pppoatm.so")()
28 local has_ipv6 = fs.access("/proc/net/ipv6_route")
29 local has_6in4 = fs.access("/lib/network/6in4.sh")
30 local has_6to4 = fs.access("/lib/network/6to4.sh")
31
32 m = Map("network", translate("Interfaces") .. " - " .. arg[1]:upper(), translate("On this page you can configure the network interfaces. You can bridge several interfaces by ticking the \"bridge interfaces\" field and enter the names of several network interfaces separated by spaces. You can also use <abbr title=\"Virtual Local Area Network\">VLAN</abbr> notation <samp>INTERFACE.VLANNR</samp> (<abbr title=\"for example\">e.g.</abbr>: <samp>eth0.1</samp>)."))
33 m:chain("firewall")
34 m:chain("wireless")
35
36 nw.init(m.uci)
37 fw.init(m.uci)
38
39
40 local net = nw:get_network(arg[1])
41
42 -- redirect to overview page if network does not exist anymore (e.g. after a revert)
43 if not net then
44 luci.http.redirect(luci.dispatcher.build_url("admin/network/network"))
45 return
46 end
47
48 local ifc = net:get_interfaces()[1]
49
50 s = m:section(NamedSection, arg[1], "interface", translate("Common Configuration"))
51 s.addremove = false
52
53 s:tab("general", translate("General Setup"))
54 if has_ipv6 then s:tab("ipv6", translate("IPv6 Setup")) end
55 if has_pppd then s:tab("ppp", translate("PPP Settings")) end
56 if has_pppoa then s:tab("atm", translate("ATM Settings")) end
57 if has_6in4 or has_6to4 then s:tab("tunnel", translate("Tunnel Settings")) end
58 s:tab("physical", translate("Physical Settings"))
59 s:tab("firewall", translate("Firewall Settings"))
60
61 st = s:taboption("general", DummyValue, "__status", translate("Status"))
62 st.template = "admin_network/iface_status"
63 st.network = arg[1]
64
65 --[[
66 back = s:taboption("general", DummyValue, "_overview", translate("Overview"))
67 back.value = ""
68 back.titleref = luci.dispatcher.build_url("admin", "network", "network")
69 ]]
70
71 p = s:taboption("general", ListValue, "proto", translate("Protocol"))
72 p.override_scheme = true
73 p.default = "static"
74 p:value("static", translate("static"))
75 p:value("dhcp", "DHCP")
76 if has_pppd then p:value("ppp", "PPP") end
77 if has_pppoe then p:value("pppoe", "PPPoE") end
78 if has_pppoa then p:value("pppoa", "PPPoA") end
79 if has_3g then p:value("3g", "UMTS/3G") end
80 if has_pptp then p:value("pptp", "PPTP") end
81 if has_6in4 then p:value("6in4", "6in4") end
82 if has_6to4 then p:value("6to4", "6to4") end
83 p:value("none", translate("none"))
84
85 if not ( has_pppd and has_pppoe and has_pppoa and has_3g and has_pptp ) then
86 p.description = translate("You need to install \"comgt\" for UMTS/GPRS, \"ppp-mod-pppoe\" for PPPoE, \"ppp-mod-pppoa\" for PPPoA or \"pptp\" for PPtP support")
87 end
88
89 br = s:taboption("physical", Flag, "type", translate("Bridge interfaces"), translate("creates a bridge over specified interface(s)"))
90 br.enabled = "bridge"
91 br.rmempty = true
92 br:depends("proto", "static")
93 br:depends("proto", "dhcp")
94 br:depends("proto", "none")
95
96 stp = s:taboption("physical", Flag, "stp", translate("Enable <abbr title=\"Spanning Tree Protocol\">STP</abbr>"),
97 translate("Enables the Spanning Tree Protocol on this bridge"))
98 stp:depends("type", "bridge")
99 stp.rmempty = true
100
101 ifname_single = s:taboption("physical", Value, "ifname_single", translate("Interface"))
102 ifname_single.template = "cbi/network_ifacelist"
103 ifname_single.widget = "radio"
104 ifname_single.nobridges = true
105 ifname_single.network = arg[1]
106 ifname_single.rmempty = true
107 ifname_single:depends({ type = "", proto = "static" })
108 ifname_single:depends({ type = "", proto = "dhcp" })
109 ifname_single:depends({ type = "", proto = "pppoe" })
110 ifname_single:depends({ type = "", proto = "pppoa" })
111 ifname_single:depends({ type = "", proto = "none" })
112
113 function ifname_single.cfgvalue(self, s)
114 return self.map.uci:get("network", s, "ifname")
115 end
116
117 function ifname_single.write(self, s, val)
118 local n = nw:get_network(s)
119 if n then
120 local i
121 for _, i in ipairs(n:get_interfaces()) do
122 n:del_interface(i)
123 end
124
125 for i in ut.imatch(val) do
126 n:add_interface(i)
127
128 -- if this is not a bridge, only assign first interface
129 if self.option == "ifname_single" then
130 break
131 end
132 end
133 end
134 end
135
136 ifname_multi = s:taboption("physical", Value, "ifname_multi", translate("Interface"))
137 ifname_multi.template = "cbi/network_ifacelist"
138 ifname_multi.nobridges = true
139 ifname_multi.network = arg[1]
140 ifname_multi.widget = "checkbox"
141 ifname_multi:depends("type", "bridge")
142 ifname_multi.cfgvalue = ifname_single.cfgvalue
143 ifname_multi.write = ifname_single.write
144
145
146 local fwd_to, fwd_from
147
148 fwzone = s:taboption("firewall", Value, "_fwzone",
149 translate("Create / Assign firewall-zone"),
150 translate("Choose the firewall zone you want to assign to this interface. Select <em>unspecified</em> to remove the interface from the associated zone or fill out the <em>create</em> field to define a new zone and attach the interface to it."))
151
152 fwzone.template = "cbi/firewall_zonelist"
153 fwzone.network = arg[1]
154 fwzone.rmempty = false
155
156 function fwzone.cfgvalue(self, section)
157 self.iface = section
158 local z = fw:get_zone_by_network(section)
159 return z and z:name()
160 end
161
162 function fwzone.write(self, section, value)
163 local zone = fw:get_zone(value)
164
165 if not zone and value == '-' then
166 value = m:formvalue(self:cbid(section) .. ".newzone")
167 if value and #value > 0 then
168 zone = fw:add_zone(value)
169 else
170 fw:del_network(section)
171 end
172 end
173
174 if zone then
175 fw:del_network(section)
176 zone:add_network(section)
177 end
178 end
179
180 ipaddr = s:taboption("general", Value, "ipaddr", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Address"))
181 ipaddr.optional = true
182 ipaddr.datatype = "ip4addr"
183 ipaddr:depends("proto", "static")
184
185 nm = s:taboption("general", Value, "netmask", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Netmask"))
186 nm.optional = true
187 nm.datatype = "ip4addr"
188 nm:depends("proto", "static")
189 nm:value("255.255.255.0")
190 nm:value("255.255.0.0")
191 nm:value("255.0.0.0")
192
193 gw = s:taboption("general", Value, "gateway", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Gateway"))
194 gw.optional = true
195 gw.datatype = "ip4addr"
196 gw:depends("proto", "static")
197
198 bcast = s:taboption("general", Value, "bcast", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Broadcast"))
199 bcast.optional = true
200 bcast.datatype = "ip4addr"
201 bcast:depends("proto", "static")
202
203 if has_ipv6 then
204 ip6addr = s:taboption("ipv6", Value, "ip6addr", translate("<abbr title=\"Internet Protocol Version 6\">IPv6</abbr>-Address"), translate("<abbr title=\"Classless Inter-Domain Routing\">CIDR</abbr>-Notation: address/prefix"))
205 ip6addr.optional = true
206 ip6addr.datatype = "ip6addr"
207 ip6addr:depends("proto", "static")
208 ip6addr:depends("proto", "6in4")
209
210 ip6gw = s:taboption("ipv6", Value, "ip6gw", translate("<abbr title=\"Internet Protocol Version 6\">IPv6</abbr>-Gateway"))
211 ip6gw.optional = true
212 ip6gw.datatype = "ip6addr"
213 ip6gw:depends("proto", "static")
214 end
215
216 dns = s:taboption("general", DynamicList, "dns", translate("<abbr title=\"Domain Name System\">DNS</abbr>-Server"),
217 translate("You can specify multiple DNS servers here, press enter to add a new entry. Servers entered here will override " ..
218 "automatically assigned ones."))
219
220 dns.optional = true
221 dns.cast = "string"
222 dns.datatype = "ipaddr"
223 dns:depends({ peerdns = "", proto = "static" })
224 dns:depends({ peerdns = "", proto = "dhcp" })
225 dns:depends({ peerdns = "", proto = "pppoe" })
226 dns:depends({ peerdns = "", proto = "pppoa" })
227 dns:depends({ peerdns = "", proto = "none" })
228
229 mtu = s:taboption("physical", Value, "mtu", "MTU")
230 mtu.optional = true
231 mtu.datatype = "uinteger"
232 mtu.placeholder = 1500
233
234 srv = s:taboption("general", Value, "server", translate("<abbr title=\"Point-to-Point Tunneling Protocol\">PPTP</abbr>-Server"))
235 srv:depends("proto", "pptp")
236 srv.optional = false
237 srv.datatype = "ip4addr"
238
239 if has_6in4 then
240 peer = s:taboption("general", Value, "peeraddr", translate("Server IPv4-Address"))
241 peer.optional = false
242 peer.datatype = "ip4addr"
243 peer:depends("proto", "6in4")
244 end
245
246 if has_6in4 or has_6to4 then
247 ttl = s:taboption("physical", Value, "ttl", translate("TTL"))
248 ttl.default = "64"
249 ttl.optional = true
250 ttl.datatype = "uinteger"
251 ttl:depends("proto", "6in4")
252 ttl:depends("proto", "6to4")
253
254 advi = s:taboption("general", Value, "adv_interface", translate("Advertise IPv6 on network"))
255 advi.widget = "radio"
256 advi.exclude = arg[1]
257 advi.default = "lan"
258 advi.template = "cbi/network_netlist"
259 advi.nocreate = true
260 advi.nobridges = true
261
262 advn = s:taboption("general", Value, "adv_subnet", translate("Advertised network ID"), translate("Allowed range is 1 to FFFF"))
263 advn.default = "1"
264
265 function advn.write(self, section, value)
266 value = tonumber(value, 16) or 1
267
268 if value > 65535 then value = 65535
269 elseif value < 1 then value = 1 end
270
271 Value.write(self, section, "%X" % value)
272 end
273 end
274
275 mac = s:taboption("physical", Value, "macaddr", translate("<abbr title=\"Media Access Control\">MAC</abbr>-Address"))
276 mac:depends("proto", "none")
277 mac:depends("proto", "static")
278 mac:depends("proto", "dhcp")
279 mac.placeholder = ifc and ifc:mac():upper()
280
281 if has_3g then
282 service = s:taboption("general", ListValue, "service", translate("Service type"))
283 service:value("", translate("-- Please choose --"))
284 service:value("umts", "UMTS/GPRS")
285 service:value("cdma", "CDMA")
286 service:value("evdo", "EV-DO")
287 service:depends("proto", "3g")
288 service.rmempty = true
289
290 apn = s:taboption("general", Value, "apn", translate("Access point (APN)"))
291 apn:depends("proto", "3g")
292
293 pincode = s:taboption("general", Value, "pincode",
294 translate("PIN code"),
295 translate("Make sure that you provide the correct pin code here or you might lock your sim card!")
296 )
297 pincode:depends("proto", "3g")
298 end
299
300 if has_6in4 then
301 tunid = s:taboption("general", Value, "tunnelid", translate("HE.net Tunnel ID"))
302 tunid.optional = true
303 tunid.datatype = "uinteger"
304 tunid:depends("proto", "6in4")
305 end
306
307 if has_pppd or has_pppoe or has_pppoa or has_3g or has_pptp or has_6in4 then
308 user = s:taboption("general", Value, "username", translate("Username"))
309 user.rmempty = true
310 user:depends("proto", "pptp")
311 user:depends("proto", "pppoe")
312 user:depends("proto", "pppoa")
313 user:depends("proto", "ppp")
314 user:depends("proto", "3g")
315 user:depends("proto", "6in4")
316
317 pass = s:taboption("general", Value, "password", translate("Password"))
318 pass.rmempty = true
319 pass.password = true
320 pass:depends("proto", "pptp")
321 pass:depends("proto", "pppoe")
322 pass:depends("proto", "pppoa")
323 pass:depends("proto", "ppp")
324 pass:depends("proto", "3g")
325 pass:depends("proto", "6in4")
326 end
327
328 if has_pppd or has_pppoe or has_pppoa or has_3g or has_pptp then
329 ka = s:taboption("ppp", Value, "keepalive",
330 translate("Keep-Alive"),
331 translate("Number of failed connection tests to initiate automatic reconnect")
332 )
333 ka:depends("proto", "pptp")
334 ka:depends("proto", "pppoe")
335 ka:depends("proto", "pppoa")
336 ka:depends("proto", "ppp")
337 ka:depends("proto", "3g")
338
339 demand = s:taboption("ppp", Value, "demand",
340 translate("Automatic Disconnect"),
341 translate("Time (in seconds) after which an unused connection will be closed")
342 )
343 demand.optional = true
344 demand.datatype = "uinteger"
345 demand:depends("proto", "pptp")
346 demand:depends("proto", "pppoe")
347 demand:depends("proto", "pppoa")
348 demand:depends("proto", "ppp")
349 demand:depends("proto", "3g")
350 end
351
352 if has_pppoa then
353 encaps = s:taboption("atm", ListValue, "encaps", translate("PPPoA Encapsulation"))
354 encaps:depends("proto", "pppoa")
355 encaps:value("vc", "VC-Mux")
356 encaps:value("llc", "LLC")
357
358 atmdev = s:taboption("atm", Value, "atmdev", translate("ATM device number"))
359 atmdev:depends("proto", "pppoa")
360 atmdev.default = "0"
361 atmdev.datatype = "uinteger"
362
363 vci = s:taboption("atm", Value, "vci", translate("ATM Virtual Channel Identifier (VCI)"))
364 vci:depends("proto", "pppoa")
365 vci.default = "35"
366 vci.datatype = "uinteger"
367
368 vpi = s:taboption("atm", Value, "vpi", translate("ATM Virtual Path Identifier (VPI)"))
369 vpi:depends("proto", "pppoa")
370 vpi.default = "8"
371 vpi.datatype = "uinteger"
372 end
373
374 if has_pptp or has_pppd or has_pppoe or has_pppoa or has_3g then
375 device = s:taboption("general", Value, "device",
376 translate("Modem device"),
377 translate("The device node of your modem, e.g. /dev/ttyUSB0")
378 )
379 device:depends("proto", "ppp")
380 device:depends("proto", "3g")
381
382 defaultroute = s:taboption("ppp", Flag, "defaultroute",
383 translate("Replace default route"),
384 translate("Let pppd replace the current default route to use the PPP interface after successful connect")
385 )
386 defaultroute:depends("proto", "ppp")
387 defaultroute:depends("proto", "pppoa")
388 defaultroute:depends("proto", "pppoe")
389 defaultroute:depends("proto", "pptp")
390 defaultroute:depends("proto", "3g")
391 defaultroute.rmempty = false
392 function defaultroute.cfgvalue(...)
393 return ( AbstractValue.cfgvalue(...) or '1' )
394 end
395
396 peerdns = s:taboption("ppp", Flag, "peerdns",
397 translate("Use peer DNS"),
398 translate("Configure the local DNS server to use the name servers adverticed by the PPP peer")
399 )
400 peerdns:depends("proto", "ppp")
401 peerdns:depends("proto", "pppoa")
402 peerdns:depends("proto", "pppoe")
403 peerdns:depends("proto", "pptp")
404 peerdns:depends("proto", "3g")
405 peerdns.rmempty = false
406 function peerdns.cfgvalue(...)
407 return ( AbstractValue.cfgvalue(...) or '1' )
408 end
409
410 if has_ipv6 then
411 ipv6 = s:taboption("ppp", Flag, "ipv6", translate("Enable IPv6 on PPP link") )
412 ipv6:depends("proto", "ppp")
413 ipv6:depends("proto", "pppoa")
414 ipv6:depends("proto", "pppoe")
415 ipv6:depends("proto", "pptp")
416 ipv6:depends("proto", "3g")
417 end
418
419 connect = s:taboption("ppp", Value, "connect",
420 translate("Connect script"),
421 translate("Let pppd run this script after establishing the PPP link")
422 )
423 connect:depends("proto", "ppp")
424 connect:depends("proto", "pppoe")
425 connect:depends("proto", "pppoa")
426 connect:depends("proto", "pptp")
427 connect:depends("proto", "3g")
428
429 disconnect = s:taboption("ppp", Value, "disconnect",
430 translate("Disconnect script"),
431 translate("Let pppd run this script before tearing down the PPP link")
432 )
433 disconnect:depends("proto", "ppp")
434 disconnect:depends("proto", "pppoe")
435 disconnect:depends("proto", "pppoa")
436 disconnect:depends("proto", "pptp")
437 disconnect:depends("proto", "3g")
438
439 pppd_options = s:taboption("ppp", Value, "pppd_options",
440 translate("Additional pppd options"),
441 translate("Specify additional command line arguments for pppd here")
442 )
443 pppd_options:depends("proto", "ppp")
444 pppd_options:depends("proto", "pppoa")
445 pppd_options:depends("proto", "pppoe")
446 pppd_options:depends("proto", "pptp")
447 pppd_options:depends("proto", "3g")
448
449 maxwait = s:taboption("ppp", Value, "maxwait",
450 translate("Setup wait time"),
451 translate("Seconds to wait for the modem to become ready before attempting to connect")
452 )
453 maxwait:depends("proto", "3g")
454 maxwait.default = "0"
455 maxwait.optional = true
456 maxwait.datatype = "uinteger"
457 end
458
459 s2 = m:section(TypedSection, "alias", translate("IP-Aliases"))
460 s2.addremove = true
461
462 s2:depends("interface", arg[1])
463 s2.defaults.interface = arg[1]
464
465 s2:tab("general", translate("General Setup"))
466 s2.defaults.proto = "static"
467
468 ip = s2:taboption("general", Value, "ipaddr", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Address"))
469 ip.optional = true
470 ip.datatype = "ip4addr"
471
472 nm = s2:taboption("general", Value, "netmask", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Netmask"))
473 nm.optional = true
474 nm.datatype = "ip4addr"
475 nm:value("255.255.255.0")
476 nm:value("255.255.0.0")
477 nm:value("255.0.0.0")
478
479 gw = s2:taboption("general", Value, "gateway", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Gateway"))
480 gw.optional = true
481 gw.datatype = "ip4addr"
482
483 if has_ipv6 then
484 s2:tab("ipv6", translate("IPv6 Setup"))
485
486 ip6 = s2:taboption("ipv6", Value, "ip6addr", translate("<abbr title=\"Internet Protocol Version 6\">IPv6</abbr>-Address"), translate("<abbr title=\"Classless Inter-Domain Routing\">CIDR</abbr>-Notation: address/prefix"))
487 ip6.optional = true
488 ip6.datatype = "ip6addr"
489
490 gw6 = s2:taboption("ipv6", Value, "ip6gw", translate("<abbr title=\"Internet Protocol Version 6\">IPv6</abbr>-Gateway"))
491 gw6.optional = true
492 gw6.datatype = "ip6addr"
493 end
494
495 s2:tab("advanced", translate("Advanced Settings"))
496
497 bcast = s2:taboption("advanced", Value, "bcast", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Broadcast"))
498 bcast.optional = true
499 bcast.datatype = "ip4addr"
500
501 dns = s2:taboption("advanced", Value, "dns", translate("<abbr title=\"Domain Name System\">DNS</abbr>-Server"))
502 dns.optional = true
503 dns.datatype = "ip4addr"
504
505
506 m2 = Map("dhcp", "", "")
507 function m2.on_parse()
508 local has_section = false
509
510 m2.uci:foreach("dhcp", "dhcp", function(s)
511 if s.interface == arg[1] then
512 has_section = true
513 return false
514 end
515 end)
516
517 if not has_section then
518 m2.uci:section("dhcp", "dhcp", nil, { interface = arg[1], ignore = "1" })
519 m2.uci:save("dhcp")
520 end
521 end
522
523 s = m2:section(TypedSection, "dhcp", translate("DHCP Server"))
524 s.addremove = false
525 s.anonymous = true
526 s:tab("general", translate("General Setup"))
527 s:tab("advanced", translate("Advanced Settings"))
528
529 function s.filter(self, section)
530 return m2.uci:get("dhcp", section, "interface") == arg[1]
531 end
532
533 local ignore = s:taboption("general", Flag, "ignore",
534 translate("Ignore interface"),
535 translate("Disable <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr> for " ..
536 "this interface."))
537
538 ignore.rmempty = false
539
540 local start = s:taboption("general", Value, "start", translate("Start"),
541 translate("Lowest leased address as offset from the network address."))
542 start.optional = true
543 start.datatype = "uinteger"
544 start.default = "100"
545
546 local limit = s:taboption("general", Value, "limit", translate("Limit"),
547 translate("Maximum number of leased addresses."))
548 limit.optional = true
549 limit.datatype = "uinteger"
550 limit.default = "150"
551
552 local ltime = s:taboption("general", Value, "leasetime", translate("Leasetime"),
553 translate("Expiry time of leased addresses, minimum is 2 Minutes (<code>2m</code>)."))
554 ltime.rmempty = true
555 ltime.default = "12h"
556
557 local dd = s:taboption("advanced", Flag, "dynamicdhcp",
558 translate("Dynamic <abbr title=\"Dynamic Host Configuration Protocol\">DHCP</abbr>"),
559 translate("Dynamically allocate DHCP addresses for clients. If disabled, only " ..
560 "clients having static leases will be served."))
561
562 dd.rmempty = false
563 function dd.cfgvalue(self, section)
564 return Flag.cfgvalue(self, section) or "1"
565 end
566
567 s:taboption("advanced", Flag, "force", translate("Force"),
568 translate("Force DHCP on this network even if another server is detected."))
569
570 -- XXX: is this actually useful?
571 --s:taboption("advanced", Value, "name", translate("Name"),
572 -- translate("Define a name for this network."))
573
574 mask = s:taboption("advanced", Value, "netmask",
575 translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Netmask"),
576 translate("Override the netmask sent to clients. Normally it is calculated " ..
577 "from the subnet that is served."))
578
579 mask.optional = true
580 mask.datatype = "ip4addr"
581
582 s:taboption("advanced", DynamicList, "dhcp_option", translate("DHCP-Options"),
583 translate("Define additional DHCP options, for example \"<code>6,192.168.2.1," ..
584 "192.168.2.2</code>\" which advertises different DNS servers to clients."))
585
586 for i, n in ipairs(s.children) do
587 if n ~= ignore then
588 n:depends("ignore", "")
589 end
590 end
591
592 return m, m2