1#   Copyright 2020 - The Android Open Source Project
2#
3#   Licensed under the Apache License, Version 2.0 (the "License");
4#   you may not use this file except in compliance with the License.
5#   You may obtain a copy of the License at
6#
7#       http://www.apache.org/licenses/LICENSE-2.0
8#
9#   Unless required by applicable law or agreed to in writing, software
10#   distributed under the License is distributed on an "AS IS" BASIS,
11#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12#   See the License for the specific language governing permissions and
13#   limitations under the License.
14
15import re
16import time
17
18from acts import signals
19from acts import utils
20from acts.controllers.openwrt_lib import network_const
21
22
23SERVICE_DNSMASQ = "dnsmasq"
24SERVICE_STUNNEL = "stunnel"
25SERVICE_NETWORK = "network"
26SERVICE_PPTPD = "pptpd"
27SERVICE_FIREWALL = "firewall"
28SERVICE_IPSEC = "ipsec"
29SERVICE_XL2TPD = "xl2tpd"
30SERVICE_ODHCPD = "odhcpd"
31SERVICE_NODOGSPLASH = "nodogsplash"
32PPTP_PACKAGE = "pptpd kmod-nf-nathelper-extra"
33L2TP_PACKAGE = "strongswan-full openssl-util xl2tpd"
34NAT6_PACKAGE = "ip6tables kmod-ipt-nat6"
35CAPTIVE_PORTAL_PACKAGE = "nodogsplash"
36MDNS_PACKAGE = "avahi-utils avahi-daemon-service-http avahi-daemon-service-ssh libavahi-client avahi-dbus-daemon"
37STUNNEL_CONFIG_PATH = "/etc/stunnel/DoTServer.conf"
38HISTORY_CONFIG_PATH = "/etc/dirty_configs"
39PPTPD_OPTION_PATH = "/etc/ppp/options.pptpd"
40XL2TPD_CONFIG_PATH = "/etc/xl2tpd/xl2tpd.conf"
41XL2TPD_OPTION_CONFIG_PATH = "/etc/ppp/options.xl2tpd"
42FIREWALL_CUSTOM_OPTION_PATH = "/etc/firewall.user"
43PPP_CHAP_SECRET_PATH = "/etc/ppp/chap-secrets"
44TCPDUMP_DIR = "/tmp/tcpdump/"
45LOCALHOST = "192.168.1.1"
46DEFAULT_PACKAGE_INSTALL_TIMEOUT = 200
47
48
49class NetworkSettings(object):
50    """Class for network settings.
51
52    Attributes:
53        ssh: ssh connection object.
54        ssh_settings: ssh settings for AccessPoint.
55        service_manager: Object manage service configuration.
56        user: username for ssh.
57        ip: ip address for AccessPoint.
58        log: Logging object for AccessPoint.
59        config: A list to store changes on network settings.
60        firewall_rules_list: A list of firewall rule name list.
61        cleanup_map: A dict for compare oppo functions.
62        l2tp: profile for vpn l2tp server.
63    """
64
65    def __init__(self, ssh, ssh_settings, logger):
66        """Initialize wireless settings.
67
68        Args:
69            ssh: ssh connection object.
70            ssh_settings: ssh settings for AccessPoint.
71            logger: Logging object for AccessPoint.
72        """
73        self.ssh = ssh
74        self.service_manager = ServiceManager(ssh)
75        self.ssh_settings = ssh_settings
76        self.user = self.ssh_settings.username
77        self.ip = self.ssh_settings.hostname
78        self.log = logger
79        self.config = set()
80        self.firewall_rules_list = []
81        self.cleanup_map = {
82            "setup_dns_server": self.remove_dns_server,
83            "setup_vpn_pptp_server": self.remove_vpn_pptp_server,
84            "setup_vpn_l2tp_server": self.remove_vpn_l2tp_server,
85            "disable_ipv6": self.enable_ipv6,
86            "setup_ipv6_bridge": self.remove_ipv6_bridge,
87            "default_dns": self.del_default_dns,
88            "default_v6_dns": self.del_default_v6_dns,
89            "ipv6_prefer_option": self.remove_ipv6_prefer_option,
90            "block_dns_response": self.unblock_dns_response,
91            "setup_mdns": self.remove_mdns,
92            "setup_captive_portal": self.remove_cpative_portal
93        }
94        # This map contains cleanup functions to restore the configuration to
95        # its default state. We write these keys to HISTORY_CONFIG_PATH prior to
96        # making any changes to that subsystem.
97        # This makes it easier to recover after an aborted test.
98        self.update_firewall_rules_list()
99        self.cleanup_network_settings()
100        self.clear_tcpdump()
101
102    def cleanup_network_settings(self):
103        """Reset all changes on Access point."""
104
105        # Detect if any changes that is not clean up.
106        if self.file_exists(HISTORY_CONFIG_PATH):
107            out = self.ssh.run("cat %s" % HISTORY_CONFIG_PATH).stdout
108            if out:
109                self.config = set(out.split("\n"))
110
111        if self.config:
112            temp = self.config.copy()
113            for change in temp:
114                change_list = change.split()
115                if len(change_list) > 1:
116                    self.cleanup_map[change_list[0]](*change_list[1:])
117                else:
118                    self.cleanup_map[change]()
119            self.config = set()
120
121        if self.file_exists(HISTORY_CONFIG_PATH):
122            out = self.ssh.run("cat %s" % HISTORY_CONFIG_PATH).stdout
123            if not out:
124                self.ssh.run("rm %s" % HISTORY_CONFIG_PATH)
125
126    def commit_changes(self):
127        """Apply changes on Access point."""
128        self.ssh.run("uci commit")
129        self.service_manager.restart_services()
130        self.create_config_file("\n".join(self.config),
131                                HISTORY_CONFIG_PATH)
132
133    def package_install(self, package_list):
134        """Install packages on OpenWrtAP via opkg If not installed.
135
136        Args:
137            package_list: package list to install.
138                          e.g. "pptpd kmod-mppe kmod-nf-nathelper-extra"
139        """
140        self.ssh.run("opkg update")
141        for package_name in package_list.split(" "):
142            if not self._package_installed(package_name):
143                self.ssh.run("opkg install %s" % package_name,
144                             timeout=DEFAULT_PACKAGE_INSTALL_TIMEOUT)
145                self.log.info("Package: %s installed." % package_name)
146            else:
147                self.log.info("Package: %s skipped (already installed)." % package_name)
148
149    def package_remove(self, package_list):
150        """Remove packages on OpenWrtAP via opkg If existed.
151
152        Args:
153            package_list: package list to remove.
154        """
155        for package_name in package_list.split(" "):
156            if self._package_installed(package_name):
157                self.ssh.run("opkg remove %s" % package_name)
158                self.log.info("Package: %s removed." % package_name)
159            else:
160                self.log.info("No exist package %s found." % package_name)
161
162    def _package_installed(self, package_name):
163        """Check if target package installed on OpenWrtAP.
164
165        Args:
166            package_name: package name want to check.
167
168        Returns:
169            True if installed.
170        """
171        if self.ssh.run("opkg list-installed %s" % package_name).stdout:
172            return True
173        return False
174
175    def file_exists(self, abs_file_path):
176        """Check if target file exist on specific path on OpenWrt.
177
178        Args:
179            abs_file_path: Absolute path for the file.
180
181        Returns:
182            True if Existed.
183        """
184        path, file_name = abs_file_path.rsplit("/", 1)
185        if self.ssh.run("ls %s | grep %s" % (path, file_name),
186                        ignore_status=True).stdout:
187            return True
188        return False
189
190    def path_exists(self, abs_path):
191        """Check if dir exist on OpenWrt."""
192        try:
193            self.ssh.run("ls %s" % abs_path)
194        except:
195            return False
196        return True
197
198    def count(self, config, key):
199        """Count in uci config.
200
201        Args:
202            config: config or section to research
203            key: keywords to  e.g. rule, domain
204        Returns:
205            Numbers of the count.
206        """
207        count = self.ssh.run("uci show %s | grep =%s" % (config, key),
208                             ignore_status=True).stdout
209        return len(count.split("\n"))
210
211    def create_config_file(self, config, file_path):
212        """Create config file. Overwrite if file already exist.
213
214        Args:
215            config: A string of content of config.
216            file_path: Config's abs_path.
217        """
218        self.ssh.run("echo -e \"%s\" > %s" % (config, file_path))
219
220    def replace_config_option(self, old_option, new_option, file_path):
221        """Replace config option if pattern match.
222
223        If find match pattern with old_option, then replace it with new_option.
224        Else add new_option to the file.
225
226        Args:
227            old_option: the regexp pattern to replace.
228            new_option: the option to add.
229            file_path: Config's abs_path.
230        """
231        config = self.ssh.run("cat %s" % file_path).stdout
232        config, count = re.subn(old_option, new_option, config)
233        if not count:
234            config = "\n".join([config, new_option])
235        self.create_config_file(config, file_path)
236
237    def remove_config_option(self, option, file_path):
238        """Remove option from config file.
239
240        Args:
241            option: Option to remove. Support regular expression.
242            file_path: Config's abs_path.
243        Returns:
244            Boolean for find option to remove.
245        """
246        config = self.ssh.run("cat %s" % file_path).stdout.split("\n")
247        for line in config:
248            count = re.subn(option, "", line)[1]
249            if count > 0:
250                config.remove(line)
251                self.create_config_file("\n".join(config), file_path)
252                return True
253        self.log.warning("No match option to remove.")
254        return False
255
256    def setup_dns_server(self, domain_name):
257        """Setup DNS server on OpenWrtAP.
258
259        Args:
260            domain_name: Local dns domain name.
261        """
262        self.config.add("setup_dns_server")
263        self.log.info("Setup DNS server with domain name %s" % domain_name)
264        self.ssh.run("uci set dhcp.@dnsmasq[0].local='/%s/'" % domain_name)
265        self.ssh.run("uci set dhcp.@dnsmasq[0].domain='%s'" % domain_name)
266        self.add_resource_record(domain_name, self.ip)
267        self.service_manager.need_restart(SERVICE_DNSMASQ)
268        self.commit_changes()
269
270        # Check stunnel package is installed
271        self.package_install("stunnel")
272        self.service_manager.stop(SERVICE_STUNNEL)
273        self.service_manager.disable(SERVICE_STUNNEL)
274
275        # Enable stunnel
276        self.create_stunnel_config()
277        self.ssh.run("stunnel /etc/stunnel/DoTServer.conf")
278
279    def remove_dns_server(self):
280        """Remove DNS server on OpenWrtAP."""
281        if self.file_exists("/var/run/stunnel.pid"):
282            self.ssh.run("kill $(cat /var/run/stunnel.pid)")
283        self.ssh.run("uci set dhcp.@dnsmasq[0].local='/lan/'")
284        self.ssh.run("uci set dhcp.@dnsmasq[0].domain='lan'")
285        self.clear_resource_record()
286        self.service_manager.need_restart(SERVICE_DNSMASQ)
287        self.config.discard("setup_dns_server")
288        self.commit_changes()
289
290    def add_resource_record(self, domain_name, domain_ip):
291        """Add resource record.
292
293        Args:
294            domain_name: A string for domain name.
295            domain_ip: A string for domain ip.
296        """
297        self.ssh.run("uci add dhcp domain")
298        self.ssh.run("uci set dhcp.@domain[-1].name='%s'" % domain_name)
299        self.ssh.run("uci set dhcp.@domain[-1].ip='%s'" % domain_ip)
300        self.service_manager.need_restart(SERVICE_DNSMASQ)
301
302    def del_resource_record(self):
303        """Delete the last resource record."""
304        self.ssh.run("uci delete dhcp.@domain[-1]")
305        self.service_manager.need_restart(SERVICE_DNSMASQ)
306
307    def clear_resource_record(self):
308        """Delete the all resource record."""
309        rr = self.ssh.run("uci show dhcp | grep =domain",
310                          ignore_status=True).stdout
311        if rr:
312            for _ in rr.split("\n"):
313                self.del_resource_record()
314        self.service_manager.need_restart(SERVICE_DNSMASQ)
315
316    def create_stunnel_config(self):
317        """Create config for stunnel service."""
318        stunnel_config = [
319            "pid = /var/run/stunnel.pid",
320            "[dns]",
321            "accept = 853",
322            "connect = 127.0.0.1:53",
323            "cert = /etc/stunnel/fullchain.pem",
324            "key = /etc/stunnel/privkey.pem",
325        ]
326        config_string = "\n".join(stunnel_config)
327        self.create_config_file(config_string, STUNNEL_CONFIG_PATH)
328
329    def setup_vpn_pptp_server(self, local_ip, user, password):
330        """Setup pptp vpn server on OpenWrt.
331
332        Args:
333            local_ip: local pptp server ip address.
334            user: username for pptp user.
335            password: password for pptp user.
336        """
337        #  Install pptp service
338        self.package_install(PPTP_PACKAGE)
339
340        self.config.add("setup_vpn_pptp_server")
341        # Edit /etc/config/pptpd & /etc/ppp/options.pptpd
342        self.setup_pptpd(local_ip, user, password)
343        # Edit /etc/config/firewall & /etc/firewall.user
344        self.setup_firewall_rules_for_pptp()
345        # Enable service
346        self.service_manager.enable(SERVICE_PPTPD)
347        self.service_manager.need_restart(SERVICE_PPTPD)
348        self.service_manager.need_restart(SERVICE_FIREWALL)
349        self.commit_changes()
350
351    def remove_vpn_pptp_server(self):
352        """Remove pptp vpn server on OpenWrt."""
353        # Edit /etc/config/pptpd
354        self.restore_pptpd()
355        # Edit /etc/config/firewall & /etc/firewall.user
356        self.restore_firewall_rules_for_pptp()
357        # Disable service
358        self.service_manager.disable(SERVICE_PPTPD)
359        self.service_manager.need_restart(SERVICE_PPTPD)
360        self.service_manager.need_restart(SERVICE_FIREWALL)
361        self.config.discard("setup_vpn_pptp_server")
362        self.commit_changes()
363
364        self.package_remove(PPTP_PACKAGE)
365        self.ssh.run("rm /etc/ppp/options.pptpd")
366        self.ssh.run("rm /etc/config/pptpd")
367
368    def setup_pptpd(self, local_ip, username, password, ms_dns="8.8.8.8"):
369        """Setup pptpd config for ip addr and account.
370
371        Args:
372            local_ip: vpn server address
373            username: pptp vpn username
374            password: pptp vpn password
375            ms_dns: DNS server
376        """
377        # Calculate remote ip address
378        # e.g. local_ip = 10.10.10.9
379        # remote_ip = 10.10.10.10 -250
380        remote_ip = local_ip.split(".")
381        remote_ip.append(str(int(remote_ip.pop(-1)) + 1))
382        remote_ip = ".".join(remote_ip)
383        # Enable pptp service and set ip addr
384        self.ssh.run("uci set pptpd.pptpd.enabled=1")
385        self.ssh.run("uci set pptpd.pptpd.localip='%s'" % local_ip)
386        self.ssh.run("uci set pptpd.pptpd.remoteip='%s-250'" % remote_ip)
387
388        # Setup pptp service account
389        self.ssh.run("uci set pptpd.@login[0].username='%s'" % username)
390        self.ssh.run("uci set pptpd.@login[0].password='%s'" % password)
391        self.service_manager.need_restart(SERVICE_PPTPD)
392
393        self.replace_config_option(r"#*ms-dns \d+.\d+.\d+.\d+",
394                                   "ms-dns %s" % ms_dns, PPTPD_OPTION_PATH)
395        self.replace_config_option("(#no)*proxyarp",
396                                   "proxyarp", PPTPD_OPTION_PATH)
397
398    def restore_pptpd(self):
399        """Disable pptpd."""
400        self.ssh.run("uci set pptpd.pptpd.enabled=0")
401        self.remove_config_option(r"\S+ pptp-server \S+ \*",
402                                  PPP_CHAP_SECRET_PATH)
403        self.service_manager.need_restart(SERVICE_PPTPD)
404
405    def setup_vpn_l2tp_server(self,
406                              vpn_server_hostname,
407                              vpn_server_address,
408                              vpn_username,
409                              vpn_password,
410                              psk_secret,
411                              server_name,
412                              country,
413                              org):
414        """Setup l2tp vpn server on OpenWrt.
415
416        Args:
417            vpn_server_hostname: vpn server domain name
418            vpn_server_address: vpn server addr
419            vpn_username: vpn account
420            vpn_password: vpn password
421            psk_secret: psk for ipsec
422            server_name: vpn server name for register in OpenWrt
423            country: country code for generate cert keys.
424            org: Organization name for generate cert keys.
425        """
426        self.l2tp = network_const.VpnL2tp(vpn_server_hostname,
427                                          vpn_server_address,
428                                          vpn_username,
429                                          vpn_password,
430                                          psk_secret,
431                                          server_name)
432
433        self.package_install(L2TP_PACKAGE)
434        self.config.add("setup_vpn_l2tp_server")
435
436        # /etc/strongswan.conf: Strongswan configuration file
437        self.setup_strongswan()
438        # /etc/ipsec.conf /etc/ipsec.secrets
439        self.setup_ipsec()
440        # /etc/xl2tpd/xl2tpd.conf & /etc/ppp/options.xl2tpd
441        self.setup_xl2tpd()
442        # /etc/ppp/chap-secrets
443        self.setup_ppp_secret()
444        # /etc/config/firewall & /etc/firewall.user
445        self.setup_firewall_rules_for_l2tp()
446        # setup vpn server local ip
447        self.setup_vpn_local_ip()
448        # generate cert and key for rsa
449        self.generate_vpn_cert_keys(country, org)
450        # restart service
451        self.service_manager.need_restart(SERVICE_IPSEC)
452        self.service_manager.need_restart(SERVICE_XL2TPD)
453        self.service_manager.need_restart(SERVICE_FIREWALL)
454        self.commit_changes()
455
456    def remove_vpn_l2tp_server(self):
457        """Remove l2tp vpn server on OpenWrt."""
458        self.config.discard("setup_vpn_l2tp_server")
459        self.restore_firewall_rules_for_l2tp()
460        self.remove_vpn_local_ip()
461        self.service_manager.need_restart(SERVICE_IPSEC)
462        self.service_manager.need_restart(SERVICE_XL2TPD)
463        self.service_manager.need_restart(SERVICE_FIREWALL)
464        self.commit_changes()
465        self.package_remove(L2TP_PACKAGE)
466        if hasattr(self, "l2tp"):
467            delattr(self, "l2tp")
468
469    def setup_strongswan(self, dns="8.8.8.8"):
470        """Setup strongswan config."""
471        config = [
472            "charon {",
473            "   load_modular = yes",
474            "   plugins {",
475            "       include strongswan.d/charon/*.conf",
476            "   }",
477            "   dns1=%s" % dns,
478            "}"
479        ]
480        self.create_config_file("\n".join(config), "/etc/strongswan.conf")
481
482    def setup_ipsec(self):
483        """Setup ipsec config."""
484        def load_ipsec_config(data, rightsourceip=False):
485            for i in data.keys():
486                config.append(i)
487                for j in data[i].keys():
488                    config.append("\t %s=%s" % (j, data[i][j]))
489                if rightsourceip:
490                    config.append("\t rightsourceip=%s.16/26" % self.l2tp.address.rsplit(".", 1)[0])
491                config.append("")
492
493        config = []
494        load_ipsec_config(network_const.IPSEC_CONF)
495        load_ipsec_config(network_const.IPSEC_L2TP_PSK)
496        load_ipsec_config(network_const.IPSEC_L2TP_RSA)
497        load_ipsec_config(network_const.IPSEC_HYBRID_RSA, True)
498        load_ipsec_config(network_const.IPSEC_XAUTH_PSK, True)
499        load_ipsec_config(network_const.IPSEC_XAUTH_RSA, True)
500        self.create_config_file("\n".join(config), "/etc/ipsec.conf")
501
502        ipsec_secret = []
503        ipsec_secret.append(r": PSK \"%s\"" % self.l2tp.psk_secret)
504        ipsec_secret.append(r": RSA \"%s\"" % "serverKey.der")
505        ipsec_secret.append(r"%s : XAUTH \"%s\"" % (self.l2tp.username,
506                                                    self.l2tp.password))
507        self.create_config_file("\n".join(ipsec_secret), "/etc/ipsec.secrets")
508
509    def setup_xl2tpd(self, ip_range=20):
510        """Setup xl2tpd config."""
511        net_id, host_id = self.l2tp.address.rsplit(".", 1)
512        xl2tpd_conf = list(network_const.XL2TPD_CONF_GLOBAL)
513        xl2tpd_conf.append("auth file = %s" % PPP_CHAP_SECRET_PATH)
514        xl2tpd_conf.extend(network_const.XL2TPD_CONF_INS)
515        xl2tpd_conf.append("ip range = %s.%s-%s.%s" %
516                           (net_id, host_id, net_id,
517                            str(int(host_id)+ip_range)))
518        xl2tpd_conf.append("local ip = %s" % self.l2tp.address)
519        xl2tpd_conf.append("name = %s" % self.l2tp.name)
520        xl2tpd_conf.append("pppoptfile = %s" % XL2TPD_OPTION_CONFIG_PATH)
521
522        self.create_config_file("\n".join(xl2tpd_conf), XL2TPD_CONFIG_PATH)
523        xl2tpd_option = list(network_const.XL2TPD_OPTION)
524        xl2tpd_option.append("name %s" % self.l2tp.name)
525        self.create_config_file("\n".join(xl2tpd_option),
526                                XL2TPD_OPTION_CONFIG_PATH)
527
528    def setup_ppp_secret(self):
529        self.replace_config_option(
530            r"\S+ %s \S+ \*" % self.l2tp.name,
531            "%s %s %s *" % (self.l2tp.username,
532                            self.l2tp.name,
533                            self.l2tp.password),
534            PPP_CHAP_SECRET_PATH)
535
536    def generate_vpn_cert_keys(self, country, org):
537        """Generate cert and keys for vpn server."""
538        rsa = "--type rsa"
539        lifetime = "--lifetime 365"
540        size = "--size 4096"
541
542        self.ssh.run("ipsec pki --gen %s %s --outform der > caKey.der" %
543                     (rsa, size))
544        self.ssh.run("ipsec pki --self --ca %s --in caKey.der %s --dn "
545                     "\"C=%s, O=%s, CN=%s\" --outform der > caCert.der" %
546                     (lifetime, rsa, country, org, self.l2tp.hostname))
547        self.ssh.run("ipsec pki --gen %s %s --outform der > serverKey.der" %
548                     (size, rsa))
549        self.ssh.run("ipsec pki --pub --in serverKey.der %s | ipsec pki "
550                     "--issue %s --cacert caCert.der --cakey caKey.der "
551                     "--dn \"C=%s, O=%s, CN=%s\" --san %s --flag serverAuth"
552                     " --flag ikeIntermediate --outform der > serverCert.der" %
553                     (rsa, lifetime, country, org, self.l2tp.hostname, LOCALHOST))
554        self.ssh.run("ipsec pki --gen %s %s --outform der > clientKey.der" %
555                     (size, rsa))
556        self.ssh.run("ipsec pki --pub --in clientKey.der %s | ipsec pki "
557                     "--issue %s --cacert caCert.der --cakey caKey.der "
558                     "--dn \"C=%s, O=%s, CN=%s@%s\" --outform der > "
559                     "clientCert.der" % (rsa, lifetime, country, org,
560                                         self.l2tp.username, self.l2tp.hostname))
561
562        self.ssh.run(
563            "openssl rsa -inform DER -in clientKey.der"
564            " -out clientKey.pem -outform PEM"
565        )
566        self.ssh.run(
567            "openssl x509 -inform DER -in clientCert.der"
568            " -out clientCert.pem -outform PEM"
569        )
570        self.ssh.run(
571            "openssl x509 -inform DER -in caCert.der"
572            " -out caCert.pem -outform PEM"
573        )
574        self.ssh.run(
575            "openssl pkcs12 -in clientCert.pem -inkey  clientKey.pem"
576            " -certfile caCert.pem -export -out clientPkcs.p12 -passout pass:"
577        )
578
579        self.ssh.run("mv caCert.pem /etc/ipsec.d/cacerts/")
580        self.ssh.run("mv *Cert* /etc/ipsec.d/certs/")
581        self.ssh.run("mv *Key* /etc/ipsec.d/private/")
582        if not self.path_exists("/www/downloads/"):
583            self.ssh.run("mkdir /www/downloads/")
584        self.ssh.run("mv clientPkcs.p12 /www/downloads/")
585        self.ssh.run("chmod 664 /www/downloads/clientPkcs.p12")
586
587    def update_firewall_rules_list(self):
588        """Update rule list in /etc/config/firewall."""
589        new_rules_list = []
590        for i in range(self.count("firewall", "rule")):
591            rule = self.ssh.run("uci get firewall.@rule[%s].name" % i).stdout
592            new_rules_list.append(rule)
593        self.firewall_rules_list = new_rules_list
594
595    def setup_firewall_rules_for_pptp(self):
596        """Setup firewall for vpn pptp server."""
597        self.update_firewall_rules_list()
598        if "pptpd" not in self.firewall_rules_list:
599            self.ssh.run("uci add firewall rule")
600            self.ssh.run("uci set firewall.@rule[-1].name='pptpd'")
601            self.ssh.run("uci set firewall.@rule[-1].target='ACCEPT'")
602            self.ssh.run("uci set firewall.@rule[-1].proto='tcp'")
603            self.ssh.run("uci set firewall.@rule[-1].dest_port='1723'")
604            self.ssh.run("uci set firewall.@rule[-1].family='ipv4'")
605            self.ssh.run("uci set firewall.@rule[-1].src='wan'")
606
607        if "GRP" not in self.firewall_rules_list:
608            self.ssh.run("uci add firewall rule")
609            self.ssh.run("uci set firewall.@rule[-1].name='GRP'")
610            self.ssh.run("uci set firewall.@rule[-1].target='ACCEPT'")
611            self.ssh.run("uci set firewall.@rule[-1].src='wan'")
612            self.ssh.run("uci set firewall.@rule[-1].proto='47'")
613
614        iptable_rules = list(network_const.FIREWALL_RULES_FOR_PPTP)
615        self.add_custom_firewall_rules(iptable_rules)
616        self.service_manager.need_restart(SERVICE_FIREWALL)
617
618    def restore_firewall_rules_for_pptp(self):
619        """Restore firewall for vpn pptp server."""
620        self.update_firewall_rules_list()
621        if "pptpd" in self.firewall_rules_list:
622            self.ssh.run("uci del firewall.@rule[%s]"
623                         % self.firewall_rules_list.index("pptpd"))
624        self.update_firewall_rules_list()
625        if "GRP" in self.firewall_rules_list:
626            self.ssh.run("uci del firewall.@rule[%s]"
627                         % self.firewall_rules_list.index("GRP"))
628        self.remove_custom_firewall_rules()
629        self.service_manager.need_restart(SERVICE_FIREWALL)
630
631    def setup_firewall_rules_for_l2tp(self):
632        """Setup firewall for vpn l2tp server."""
633        self.update_firewall_rules_list()
634        if "ipsec esp" not in self.firewall_rules_list:
635            self.ssh.run("uci add firewall rule")
636            self.ssh.run("uci set firewall.@rule[-1].name='ipsec esp'")
637            self.ssh.run("uci set firewall.@rule[-1].target='ACCEPT'")
638            self.ssh.run("uci set firewall.@rule[-1].proto='esp'")
639            self.ssh.run("uci set firewall.@rule[-1].src='wan'")
640
641        if "ipsec nat-t" not in self.firewall_rules_list:
642            self.ssh.run("uci add firewall rule")
643            self.ssh.run("uci set firewall.@rule[-1].name='ipsec nat-t'")
644            self.ssh.run("uci set firewall.@rule[-1].target='ACCEPT'")
645            self.ssh.run("uci set firewall.@rule[-1].src='wan'")
646            self.ssh.run("uci set firewall.@rule[-1].proto='udp'")
647            self.ssh.run("uci set firewall.@rule[-1].dest_port='4500'")
648
649        if "auth header" not in self.firewall_rules_list:
650            self.ssh.run("uci add firewall rule")
651            self.ssh.run("uci set firewall.@rule[-1].name='auth header'")
652            self.ssh.run("uci set firewall.@rule[-1].target='ACCEPT'")
653            self.ssh.run("uci set firewall.@rule[-1].src='wan'")
654            self.ssh.run("uci set firewall.@rule[-1].proto='ah'")
655
656        net_id = self.l2tp.address.rsplit(".", 1)[0]
657        iptable_rules = list(network_const.FIREWALL_RULES_FOR_L2TP)
658        iptable_rules.append("iptables -A FORWARD -s %s.0/24"
659                             "  -j ACCEPT" % net_id)
660        iptable_rules.append("iptables -t nat -A POSTROUTING"
661                             " -s %s.0/24 -o eth0.2 -j MASQUERADE" % net_id)
662
663        self.add_custom_firewall_rules(iptable_rules)
664        self.service_manager.need_restart(SERVICE_FIREWALL)
665
666    def restore_firewall_rules_for_l2tp(self):
667        """Restore firewall for vpn l2tp server."""
668        self.update_firewall_rules_list()
669        if "ipsec esp" in self.firewall_rules_list:
670            self.ssh.run("uci del firewall.@rule[%s]"
671                         % self.firewall_rules_list.index("ipsec esp"))
672        self.update_firewall_rules_list()
673        if "ipsec nat-t" in self.firewall_rules_list:
674            self.ssh.run("uci del firewall.@rule[%s]"
675                         % self.firewall_rules_list.index("ipsec nat-t"))
676        self.update_firewall_rules_list()
677        if "auth header" in self.firewall_rules_list:
678            self.ssh.run("uci del firewall.@rule[%s]"
679                         % self.firewall_rules_list.index("auth header"))
680        self.remove_custom_firewall_rules()
681        self.service_manager.need_restart(SERVICE_FIREWALL)
682
683    def add_custom_firewall_rules(self, rules):
684        """Backup current custom rules and replace with arguments.
685
686        Args:
687            rules: A list of iptable rules to apply.
688        """
689        backup_file_path = FIREWALL_CUSTOM_OPTION_PATH+".backup"
690        if not self.file_exists(backup_file_path):
691            self.ssh.run("mv %s %s" % (FIREWALL_CUSTOM_OPTION_PATH,
692                                       backup_file_path))
693        for rule in rules:
694            self.ssh.run("echo %s >> %s" % (rule, FIREWALL_CUSTOM_OPTION_PATH))
695
696    def remove_custom_firewall_rules(self):
697        """Clean up and recover custom firewall rules."""
698        backup_file_path = FIREWALL_CUSTOM_OPTION_PATH+".backup"
699        if self.file_exists(backup_file_path):
700            self.ssh.run("mv %s %s" % (backup_file_path,
701                                       FIREWALL_CUSTOM_OPTION_PATH))
702        else:
703            self.log.debug("Did not find %s" % backup_file_path)
704            self.ssh.run("echo "" > %s" % FIREWALL_CUSTOM_OPTION_PATH)
705
706    def disable_pptp_service(self):
707        """Disable pptp service."""
708        self.package_remove(PPTP_PACKAGE)
709
710    def setup_vpn_local_ip(self):
711        """Setup VPN Server local ip on OpenWrt for client ping verify."""
712        self.ssh.run("uci set network.lan2=interface")
713        self.ssh.run("uci set network.lan2.type=bridge")
714        self.ssh.run("uci set network.lan2.ifname=eth1.2")
715        self.ssh.run("uci set network.lan2.proto=static")
716        self.ssh.run("uci set network.lan2.ipaddr=\"%s\"" % self.l2tp.address)
717        self.ssh.run("uci set network.lan2.netmask=255.255.255.0")
718        self.ssh.run("uci set network.lan2=interface")
719        self.service_manager.reload(SERVICE_NETWORK)
720        self.commit_changes()
721
722    def remove_vpn_local_ip(self):
723        """Discard vpn local ip on OpenWrt."""
724        self.ssh.run("uci delete network.lan2")
725        self.service_manager.reload(SERVICE_NETWORK)
726        self.commit_changes()
727
728    def enable_ipv6(self):
729        """Enable ipv6 on OpenWrt."""
730        self.ssh.run("uci set network.lan.ipv6=1")
731        self.ssh.run("uci set network.wan.ipv6=1")
732        self.service_manager.enable("odhcpd")
733        self.service_manager.reload(SERVICE_NETWORK)
734        self.config.discard("disable_ipv6")
735        self.commit_changes()
736
737    def disable_ipv6(self):
738        """Disable ipv6 on OpenWrt."""
739        self.config.add("disable_ipv6")
740        self.ssh.run("uci set network.lan.ipv6=0")
741        self.ssh.run("uci set network.wan.ipv6=0")
742        self.service_manager.disable("odhcpd")
743        self.service_manager.reload(SERVICE_NETWORK)
744        self.commit_changes()
745
746    def setup_ipv6_bridge(self):
747        """Setup ipv6 bridge for client have ability to access network."""
748        self.config.add("setup_ipv6_bridge")
749
750        self.ssh.run("uci set dhcp.lan.dhcpv6=relay")
751        self.ssh.run("uci set dhcp.lan.ra=relay")
752        self.ssh.run("uci set dhcp.lan.ndp=relay")
753
754        self.ssh.run("uci set dhcp.wan6=dhcp")
755        self.ssh.run("uci set dhcp.wan6.dhcpv6=relay")
756        self.ssh.run("uci set dhcp.wan6.ra=relay")
757        self.ssh.run("uci set dhcp.wan6.ndp=relay")
758        self.ssh.run("uci set dhcp.wan6.master=1")
759        self.ssh.run("uci set dhcp.wan6.interface=wan6")
760
761        # Enable service
762        self.service_manager.need_restart(SERVICE_ODHCPD)
763        self.commit_changes()
764
765    def remove_ipv6_bridge(self):
766        """Discard ipv6 bridge on OpenWrt."""
767        if "setup_ipv6_bridge" in self.config:
768            self.config.discard("setup_ipv6_bridge")
769
770            self.ssh.run("uci set dhcp.lan.dhcpv6=server")
771            self.ssh.run("uci set dhcp.lan.ra=server")
772            self.ssh.run("uci delete dhcp.lan.ndp")
773
774            self.ssh.run("uci delete dhcp.wan6")
775
776            self.service_manager.need_restart(SERVICE_ODHCPD)
777            self.commit_changes()
778
779    def _add_dhcp_option(self, args):
780        self.ssh.run("uci add_list dhcp.lan.dhcp_option=\"%s\"" % args)
781
782    def _remove_dhcp_option(self, args):
783        self.ssh.run("uci del_list dhcp.lan.dhcp_option=\"%s\"" % args)
784
785    def add_default_dns(self, addr_list):
786        """Add default dns server for client.
787
788        Args:
789            addr_list: dns ip address for Openwrt client.
790        """
791        self._add_dhcp_option("6,%s" % ",".join(addr_list))
792        self.config.add("default_dns %s" % addr_list)
793        self.service_manager.need_restart(SERVICE_DNSMASQ)
794        self.commit_changes()
795
796    def del_default_dns(self, addr_list):
797        """Remove default dns server for client.
798
799        Args:
800            addr_list: list of dns ip address for Openwrt client.
801        """
802        self._remove_dhcp_option("6,%s" % addr_list)
803        self.config.discard("default_dns %s" % addr_list)
804        self.service_manager.need_restart(SERVICE_DNSMASQ)
805        self.commit_changes()
806
807    def add_default_v6_dns(self, addr_list):
808        """Add default v6 dns server for client.
809
810        Args:
811            addr_list: dns ip address for Openwrt client.
812        """
813        self.ssh.run("uci add_list dhcp.lan.dns=\"%s\"" % addr_list)
814        self.config.add("default_v6_dns %s" % addr_list)
815        self.service_manager.need_restart(SERVICE_ODHCPD)
816        self.commit_changes()
817
818    def del_default_v6_dns(self, addr_list):
819        """Del default v6 dns server for client.
820
821        Args:
822            addr_list: dns ip address for Openwrt client.
823        """
824        self.ssh.run("uci del_list dhcp.lan.dns=\"%s\"" % addr_list)
825        self.config.add("default_v6_dns %s" % addr_list)
826        self.service_manager.need_restart(SERVICE_ODHCPD)
827        self.commit_changes()
828
829    def add_ipv6_prefer_option(self):
830        self._add_dhcp_option("108,1800i")
831        self.config.add("ipv6_prefer_option")
832        self.service_manager.need_restart(SERVICE_DNSMASQ)
833        self.commit_changes()
834
835    def remove_ipv6_prefer_option(self):
836        self._remove_dhcp_option("108,1800i")
837        self.config.discard("ipv6_prefer_option")
838        self.service_manager.need_restart(SERVICE_DNSMASQ)
839        self.commit_changes()
840
841    def start_tcpdump(self, test_name, args="", interface="br-lan"):
842        """"Start tcpdump on OpenWrt.
843
844        Args:
845            test_name: Test name for create tcpdump file name.
846            args: Option args for tcpdump.
847            interface: Interface to logging.
848        Returns:
849            tcpdump_file_name: tcpdump file name on OpenWrt.
850            pid: tcpdump process id.
851        """
852        if not self.path_exists(TCPDUMP_DIR):
853            self.ssh.run("mkdir %s" % TCPDUMP_DIR)
854        tcpdump_file_name = "openwrt_%s_%s.pcap" % (test_name,
855                                                    time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime(time.time())))
856        tcpdump_file_path = "".join([TCPDUMP_DIR, tcpdump_file_name])
857        cmd = "tcpdump -i %s -s0 %s -w %s" % (interface, args, tcpdump_file_path)
858        self.ssh.run_async(cmd)
859        pid = self._get_tcpdump_pid(tcpdump_file_name)
860        if not pid:
861            raise signals.TestFailure("Fail to start tcpdump on OpenWrt.")
862        # Set delay to prevent tcpdump fail to capture target packet.
863        time.sleep(15)
864        return tcpdump_file_name
865
866    def stop_tcpdump(self, tcpdump_file_name, pull_dir=None):
867        """Stop tcpdump on OpenWrt and pull the pcap file.
868
869        Args:
870            tcpdump_file_name: tcpdump file name on OpenWrt.
871            pull_dir: Keep none if no need to pull.
872        Returns:
873            tcpdump abs_path on host.
874        """
875        # Set delay to prevent tcpdump fail to capture target packet.
876        time.sleep(15)
877        pid = self._get_tcpdump_pid(tcpdump_file_name)
878        self.ssh.run("kill -9 %s" % pid, ignore_status=True)
879        if self.path_exists(TCPDUMP_DIR) and pull_dir:
880            tcpdump_path = "".join([TCPDUMP_DIR, tcpdump_file_name])
881            tcpdump_remote_path = "/".join([pull_dir, tcpdump_file_name])
882            tcpdump_local_path = "%s@%s:%s" % (self.user, self.ip, tcpdump_path)
883            utils.exe_cmd("scp %s %s" % (tcpdump_local_path, tcpdump_remote_path))
884
885        if self._get_tcpdump_pid(tcpdump_file_name):
886            raise signals.TestFailure("Failed to stop tcpdump on OpenWrt.")
887        if self.file_exists(tcpdump_path):
888            self.ssh.run("rm -f %s" % tcpdump_path)
889        return tcpdump_remote_path if pull_dir else None
890
891    def clear_tcpdump(self):
892        self.ssh.run("killall tpcdump", ignore_status=True)
893        if self.ssh.run("pgrep tpcdump", ignore_status=True).stdout:
894            raise signals.TestFailure("Failed to clean up tcpdump process.")
895
896    def _get_tcpdump_pid(self, tcpdump_file_name):
897        """Check tcpdump process on OpenWrt."""
898        return self.ssh.run("pgrep -f %s" % (tcpdump_file_name), ignore_status=True).stdout
899
900    def setup_mdns(self):
901        self.config.add("setup_mdns")
902        self.package_install(MDNS_PACKAGE)
903        self.commit_changes()
904
905    def remove_mdns(self):
906        self.config.discard("setup_mdns")
907        self.package_remove(MDNS_PACKAGE)
908        self.commit_changes()
909
910    def block_dns_response(self):
911        self.config.add("block_dns_response")
912        iptable_rules = list(network_const.FIREWALL_RULES_DISABLE_DNS_RESPONSE)
913        self.add_custom_firewall_rules(iptable_rules)
914        self.service_manager.need_restart(SERVICE_FIREWALL)
915        self.commit_changes()
916
917    def unblock_dns_response(self):
918        self.config.discard("block_dns_response")
919        self.remove_custom_firewall_rules()
920        self.service_manager.need_restart(SERVICE_FIREWALL)
921        self.commit_changes()
922
923    def setup_captive_portal(self):
924        self.package_install(CAPTIVE_PORTAL_PACKAGE)
925        self.config.add("setup_captive_portal")
926        self.service_manager.need_restart(SERVICE_NODOGSPLASH)
927        self.commit_changes()
928
929    def remove_cpative_portal(self):
930        self.package_remove(CAPTIVE_PORTAL_PACKAGE)
931        self.config.discard("setup_captive_portal")
932        self.commit_changes()
933
934
935class ServiceManager(object):
936    """Class for service on OpenWrt.
937
938        Attributes:
939        ssh: ssh object for the AP.
940        _need_restart: Record service need to restart.
941    """
942
943    def __init__(self, ssh):
944        self.ssh = ssh
945        self._need_restart = set()
946
947    def enable(self, service_name):
948        """Enable service auto start."""
949        self.ssh.run("/etc/init.d/%s enable" % service_name)
950
951    def disable(self, service_name):
952        """Disable service auto start."""
953        self.ssh.run("/etc/init.d/%s disable" % service_name)
954
955    def restart(self, service_name):
956        """Restart the service."""
957        self.ssh.run("/etc/init.d/%s restart" % service_name)
958
959    def reload(self, service_name):
960        """Restart the service."""
961        self.ssh.run("/etc/init.d/%s reload" % service_name)
962
963    def restart_services(self):
964        """Restart all services need to restart."""
965        for service in self._need_restart:
966            if service == SERVICE_NETWORK:
967                self.reload(service)
968            self.restart(service)
969        self._need_restart = set()
970
971    def stop(self, service_name):
972        """Stop the service."""
973        self.ssh.run("/etc/init.d/%s stop" % service_name)
974
975    def need_restart(self, service_name):
976        self._need_restart.add(service_name)
977