diff --git a/.github/workflows/public-galaxy.yml b/.github/workflows/public-galaxy.yml new file mode 100644 index 0000000..a825ffa --- /dev/null +++ b/.github/workflows/public-galaxy.yml @@ -0,0 +1,21 @@ +name: Public to Ansible Galaxy + +on: + push: + branches: [ "role" ] + paths: + - .github/** + - domain-routing-openwrt/** + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v4 + - name: galaxy + uses: robertdebock/galaxy-action@1.2.0 + with: + galaxy_api_key: ${{ secrets.galaxy_api_key }} + path: domain-routing-openwrt + git_branch: role diff --git a/domain-routing-openwrt/README.md b/domain-routing-openwrt/README.md new file mode 100644 index 0000000..9d15fe2 --- /dev/null +++ b/domain-routing-openwrt/README.md @@ -0,0 +1,104 @@ +Domain routing OpenWrt +========= + +Configuring domain routing on Openwrt router. + + +Role Variables +-------------- + +Lists +``` + country: russia-inside|russia-outside|ukraine + list_domains: true|falase + + list_subnet: false|true + list_ip: false|true + list_community: false|true +``` + +Tunnel +``` + tunnel: wg|openvpn|singbox|tun2socks +``` + +DoH or DoT +``` + dns_encrypt: false|dnscrypt|stubby +``` + +Nano package +``` + nano: true|false +``` + +Acces from wg network to router +``` + wg_access: false|true +``` + +If wireguard is used: +``` + wg_server_address: wg-server-host + wg_private_key: privatekey-client + wg_public_key: publickey-client + #wg_preshared_key: presharedkey-client + wg_client_port: 51820 + wg_client_address: ip-client + wg_access_network: wg-network +``` + +Dependencies +------------ + +[gekmihesg.openwrt](https://github.com/gekmihesg/ansible-openwrt) + + +Example Playbook +---------------- + +The inventory file must contain the group `[openwrt]` where your router will be located. + + +Wireguard, only domains, stubby, Russia, acces from wg network, host 192.168.1.1 +``` +- hosts: 192.168.1.1 + remote_user: root + + roles: + - domain-routing-openwrt + + vars: + tunnel: wg + dns_encrypt: stubby + country: russia-inside + wg_access: true + + wg_server_address: wg-server-host + wg_private_key: privatekey-client + wg_public_key: publickey-client + wg_preshared_key: presharedkey-client + wg_listen_port: 51820 + wg_client_port: 51820 + wg_client_address: ip-client + wg_access_network: wg-network +``` + +Sing-box, stubby, Russia +``` +- hosts: 192.168.1.1 + remote_user: root + + roles: + - domain-routing-openwrt + + vars: + tunnel: singbox + dns_encrypt: stubby + country: russia-inside +``` + +License +------- + +GNU General Public License v3.0 \ No newline at end of file diff --git a/domain-routing-openwrt/defaults/main.yml b/domain-routing-openwrt/defaults/main.yml new file mode 100644 index 0000000..c16bdb5 --- /dev/null +++ b/domain-routing-openwrt/defaults/main.yml @@ -0,0 +1,21 @@ +--- + list_domains: true + list_subnet: false + list_ip: false + list_community: false + tunnel: singbox + dns_encrypt: false + country: russia-inside + nano: true + wg_access: false + + wg_listen_port: 51820 + + # wg_server_address: wg-server-host + # wg_private_key: privatekey-client + # wg_public_key: publickey-client + # wg_preshared_key: presharedkey-client + # wg_listen_port: 51820 + # wg_client_port: 51820 + # wg_client_address: ip-client + # wg_access_network: wg-network \ No newline at end of file diff --git a/domain-routing-openwrt/handlers/main.yml b/domain-routing-openwrt/handlers/main.yml new file mode 100644 index 0000000..04dd6a9 --- /dev/null +++ b/domain-routing-openwrt/handlers/main.yml @@ -0,0 +1,26 @@ +--- + - name: Restart network + service: + name: network + state: restarted + + - name: Restart firewall + service: + name: firewall + state: restarted + + - name: Run getdomains script + service: + name: getdomains + state: restarted + + - name: Restart dnscrypt-proxy + service: + name: dnscrypt-proxy + state: restarted + enabled: yes + + - name: Restart dnsmasq + service: + name: dnsmasq + state: restarted \ No newline at end of file diff --git a/domain-routing-openwrt/meta/main.yml b/domain-routing-openwrt/meta/main.yml new file mode 100644 index 0000000..b7e6a85 --- /dev/null +++ b/domain-routing-openwrt/meta/main.yml @@ -0,0 +1,19 @@ +galaxy_info: + author: itdog + description: Configuring domain routing on Openwrt router + issue_tracker_url: https://github.com/itdoginfo/domain-routing-openwrt/issues + license: GPL-3.0 + min_ansible_version: 2.10.8 + platforms: + - name: OpenWrt + galaxy_tags: + - openwrt + - dnsmasq + - ipset + - wireguard + - sing-box + - openvpn + - bypass + - routing +dependencies: + - role: gekmihesg.openwrt \ No newline at end of file diff --git a/domain-routing-openwrt/tasks/main.yml b/domain-routing-openwrt/tasks/main.yml new file mode 100644 index 0000000..18a2b6b --- /dev/null +++ b/domain-routing-openwrt/tasks/main.yml @@ -0,0 +1,663 @@ +--- + +# Dnsmasq version check + + - name: Get dnsmasq version + shell: opkg list-installed | grep dnsmasq-full | awk '{print $3}' + register: dnsmasqfull_version + + - name: debug + debug: + var: ansible_distribution_major_version + +# Packages installation + + - name: install wg + opkg: + name: "{{ item }}" + state: present + loop: + - kmod-wireguard + - wireguard-tools + when: tunnel == "wg" + + - name: install openvpn + opkg: + name: "{{ item }}" + state: present + loop: + - openvpn-openssl + when: tunnel == "openvpn" + + - name: install singbox + opkg: + name: "{{ item }}" + state: present + loop: + - sing-box + when: tunnel == "singbox" and ansible_distribution_major_version >= "23" + + - name: install curl + opkg: + name: "{{ item }}" + state: present + loop: + - curl + + - name: install nano + opkg: + name: "{{ item }}" + state: present + loop: + - nano + when: nano + + - name: install ipset + opkg: + name: ipset + state: present + when: ansible_distribution_major_version < "22" + + - name: install dnsmasq-full (23) + shell: opkg update && cd /tmp/ && opkg download dnsmasq-full && opkg remove dnsmasq && opkg install dnsmasq-full --cache /tmp/ && [ -f /etc/config/dhcp-opkg ] && cp etc/config/dhcp /etc/config/dhcp-old && mv /etc/config/dhcp-opkg /etc/config/dhcp + when: ansible_distribution_major_version >= "23" and list_domains and not dnsmasqfull_version.stdout + ignore_errors: true + +# Getdomains script configure + + - name: getdomains script copy + template: + src: "openwrt-getdomains.j2" + dest: "/etc/init.d/getdomains" + mode: a+x + trim_blocks: false + notify: + - Run getdomains script + + - name: create simplink in rc.d + file: + src: "/etc/init.d/getdomains" + dest: "/etc/rc.d/S99getdomains" + state: link + notify: + - Run getdomains script + + - name: check string in crontab + shell: grep "getdomains" /etc/crontabs/root + register: check_cron + ignore_errors: true + + - name: add script to cron + lineinfile: + path: /etc/crontabs/root + create: yes + line: "0 4 * * * /etc/init.d/getdomains start" + when: check_cron.stdout == "" + + - name: enable and start crontab + service: + name: cron + state: started + enabled: yes + +# Configure route table + + - name: Route for vpn table + template: + src: "openwrt-30-vpnroute.j2" + dest: "/etc/hotplug.d/iface/30-vpnroute" + mode: 0644 + + - name: Check string in rt_tables + shell: grep "99 vpn" /etc/iproute2/rt_tables + register: check_rt_tables + ignore_errors: true + + - name: add route table + lineinfile: + path: /etc/iproute2/rt_tables + line: "99 vpn" + when: check_rt_tables.stdout == "" + notify: + - Restart network + +# Configure WG + + - name: add wg interface + uci: + command: add + config: network + type: interface + name: wg0 + when: tunnel == "wg" + + - name: configure wg interface + uci: + command: set + key: network.wg0 + value: + proto: wireguard + private_key: "{{ wg_private_key }}" + listen_port: "{{ wg_listen_port }}" + addresses: + - "{{ wg_client_address }}" + when: tunnel == "wg" + notify: + - Restart network + + - name: set wg client without wg_preshared_key + uci: + command: section + config: network + type: wireguard_wg0 + find_by: + name: wg0_client + value: + public_key: "{{ wg_public_key }}" + route_allowed_ips: 0 + persistent_keepalive: 25 + endpoint_host: "{{ wg_server_address }}" + allowed_ips: 0.0.0.0/0 + endpoint_port: "{{ wg_client_port }}" + when: wg_preshared_key is undefined and tunnel == "wg" + notify: + - Restart network + + - name: set wg client with wg_preshared_key + uci: + command: section + config: network + type: wireguard_wg0 + find_by: + name: wg0_client + value: + public_key: "{{ wg_public_key }}" + preshared_key: "{{ wg_preshared_key }}" + route_allowed_ips: 0 + persistent_keepalive: 25 + endpoint_host: "{{ wg_server_address }}" + allowed_ips: 0.0.0.0/0 + endpoint_port: "{{ wg_client_port }}" + when: wg_preshared_key is defined and tunnel == "wg" + + - name: set WG firewall zone + uci: + command: section + config: firewall + type: zone + find_by: + name: wg + value: + forward: REJECT + output: ACCEPT + name: wg + input: REJECT + masq: 1 + mtu_fix: 1 + network: wg0 + family: ipv4 + when: tunnel == "wg" + + - name: add WG forwarding + uci: + command: section + config: firewall + type: forwarding + find_by: + name: wg-lan + value: + dest: wg + src: lan + family: ipv4 + when: tunnel == "wg" + +# Configure Sing-box + + - name: set sing-box firewall zone. Only >=22 + uci: + command: section + config: firewall + type: zone + find_by: + name: tun + value: + forward: ACCEPT + output: ACCEPT + name: tun + input: ACCEPT + masq: 1 + mtu_fix: 1 + device: tun0 + family: ipv4 + when: tunnel == "singbox" + failed_when: ansible_distribution_major_version < "22" + notify: + - Restart firewall + + - name: template for sing-box.json + template: + src: "sing-box-json.j2" + dest: "/etc/sing-box/config.json" + mode: 0644 + when: tunnel == "singbox" + failed_when: ansible_distribution_major_version < "22" + + - name: template for config/sing-box + template: + src: "config-sing-box.j2" + dest: "/etc/config/sing-box" + mode: 0600 + when: tunnel == "singbox" + failed_when: ansible_distribution_major_version < "22" + +# Configure OpenVPN, tun2socks + + - name: set {{ tunnel }} firewall zone + uci: + command: section + config: firewall + type: zone + find_by: + name: tun + value: + forward: REJECT + output: ACCEPT + name: tun + input: REJECT + masq: 1 + mtu_fix: 1 + device: tun0 + family: ipv4 + when: tunnel == "openvpn" or tunnel == "tun2socks" + notify: + - Restart firewall + + - name: add {{ tunnel }} forwarding + uci: + command: section + config: firewall + type: forwarding + find_by: + name: lan-tun + value: + dest: tun + src: lan + family: ipv4 + when: tunnel == "openvpn" or tunnel == "tun2socks" or tunnel == "singbox" + notify: + - Restart firewall + +# Configure network + + - name: set rule mark0x1 + uci: + command: section + config: network + type: rule + find_by: + name: mark0x1 + value: + mark: "0x1" + priority: 100 + lookup: vpn + + - name: set disable dns for wan + uci: + command: set + key: network.wan + value: + peerdns: 0 + when: ansible_distribution_major_version < "22" + + - name: uci commit firewall + uci: + command: commit + config: firewall + notify: + - Restart firewall + + - name: uci commit network + uci: + command: commit + config: network + notify: + - Restart network + +# Configure firewall + + - name: add ipset for subnet (<22) + uci: + command: section + config: firewall + type: ipset + find_by: + name: vpn_subnets + value: + match: dst_net + storage: hash + loadfile: /tmp/lst/subnet.lst + when: ansible_distribution_major_version < "22" and list_subnet + + - name: add ipset for ip (<22) + uci: + command: section + config: firewall + type: ipset + find_by: + name: vpn_ip + value: + match: dst_net + storage: hash + loadfile: /tmp/lst/ip.lst + hashsize: 9900000 + maxelem: 9900000 + when: ansible_distribution_major_version < "22" and list_ip + + - name: add ipset for community (<22) + uci: + command: section + config: firewall + type: ipset + find_by: + name: vpn_community + value: + match: dst_net + storage: hash + loadfile: /tmp/lst/community.lst + hashsize: 9900000 + maxelem: 9900000 + when: ansible_distribution_major_version < "22" and list_community + + - name: add nfset for subnet (22) + uci: + command: section + config: firewall + type: ipset + find_by: + name: vpn_subnets + value: + match: dst_net + loadfile: /tmp/lst/subnet.lst + when: ansible_distribution_major_version >= "22" and list_subnet + + - name: add nfset for ip (22) + uci: + command: section + config: firewall + type: ipset + find_by: + name: vpn_ip + value: + match: dst_net + loadfile: /tmp/lst/ip.lst + when: ansible_distribution_major_version >= "22" and list_ip + + - name: add nfset for community (22) + uci: + command: section + config: firewall + type: ipset + find_by: + name: vpn_community + value: + match: dst_net + loadfile: /tmp/lst/community.lst + when: ansible_distribution_major_version >= "22" and list_community + + - name: add ipset for domains (<22). If failed, repeat playbook. If failed is repeated check dnsmasq-full. + uci: + command: section + config: firewall + type: ipset + find_by: + name: vpn_domains + value: + match: dst_net + storage: hash + when: ansible_distribution_major_version < "22" and list_domains + + - name: add nfset for domains (>=22). If failed, repeat playbook. If failed is repeated check dnsmasq-full. + uci: + command: section + config: firewall + type: ipset + find_by: + name: vpn_domains + value: + match: dst_net + when: ansible_distribution_major_version >= "22" and list_domains + + - name: add mark rule vpn_subnet + uci: + command: section + config: firewall + type: rule + find_by: + name: mark_subnet + value: + src: lan + dest: "*" + proto: all + ipset: vpn_subnets + set_mark: "0x1" + target: MARK + family: ipv4 + when: list_subnet + + - name: add mark rule vpn_ip + uci: + command: section + config: firewall + type: rule + find_by: + name: mark_ip + value: + src: lan + dest: "*" + proto: all + ipset: vpn_ip + set_mark: "0x1" + target: MARK + family: ipv4 + when: list_ip + + - name: add mark rule vpn_community + uci: + command: section + config: firewall + type: rule + find_by: + name: mark_community + value: + src: lan + dest: "*" + proto: all + ipset: vpn_community + set_mark: "0x1" + target: MARK + family: ipv4 + when: list_community + + - name: add mark rule vpn_domains + uci: + command: section + config: firewall + type: rule + find_by: + name: mark_domains + value: + src: lan + dest: "*" + proto: all + ipset: vpn_domains + set_mark: "0x1" + target: MARK + family: ipv4 + when: (ansible_distribution_major_version < "22" and list_domains) or (ansible_distribution_major_version >= "22" and list_domains) + + - name: wg access route + uci: + command: section + config: network + type: route + find_by: + name: wg_access_route + value: + interface: wg0 + target: "{{ wg_access_network }}" + when: wg_access + + - name: set WG firewall zone + uci: + command: section + config: firewall + type: zone + find_by: + name: wg + value: + input: ACCEPT + when: wg_access + +# Remove unused rules and ipset + - name: Remove ipset for ip + uci: + command: absent + config: firewall + type: ipset + find_by: + name: vpn_ip + when: not list_ip + + - name: Remove rule for ip + uci: + command: absent + config: firewall + type: rule + find_by: + name: mark_ip + when: not list_ip + + - name: Remove ipset for subnet + uci: + command: absent + config: firewall + type: ipset + find_by: + name: vpn_subnets + when: not list_subnet + + - name: Remove rule for subnet + uci: + command: absent + config: firewall + type: rule + find_by: + name: mark_subnet + when: not list_subnet + + - name: Remove ipset for community + uci: + command: absent + config: firewall + type: ipset + find_by: + name: vpn_community + when: not list_community + + - name: Remove rule for community + uci: + command: absent + config: firewall + type: rule + find_by: + name: mark_community + when: not list_community + + - name: Remove ipset for domains + uci: + command: absent + config: firewall + type: ipset + find_by: + name: vpn_domains + when: not list_domains + + - name: Remove rule for domains + uci: + command: absent + config: firewall + type: rule + find_by: + name: mark_domains + when: not list_domains + +# Configure DNS resolver + + - name: install dnscrypt-proxy2 + opkg: + name: dnscrypt-proxy2 + state: present + when: dns_encrypt == "dnscrypt" + + - name: check string in dnscrypt-proxy.toml + shell: grep "# server_names" /etc/dnscrypt-proxy2/dnscrypt-proxy.toml + register: check_server_names + ignore_errors: true + when: dns_encrypt == "dnscrypt" + + - name: dnscrypt2 enable exact servers + lineinfile: + path: /etc/dnscrypt-proxy2/dnscrypt-proxy.toml + regexp: "# server_names =" + line: "server_names = ['google', 'cloudflare', 'scaleway-fr', 'yandex']" + when: dns_encrypt == "dnscrypt" and check_server_names.stdout + notify: + - Restart dnscrypt-proxy + + - name: edit dhcp config. add localhost server + lineinfile: + path: /etc/config/dhcp + firstmatch: "true" + insertafter: "option leasefile '/tmp/dhcp.leases'" + line: "{{ item }}" + with_items: + - " list server '127.0.0.53#53'" + - " option noresolv '1'" + notify: + - Restart dnsmasq + when: dns_encrypt == "dnscrypt" + + - name: install stubby + opkg: + name: stubby + state: present + when: dns_encrypt == "stubby" + + - name: edit dhcp config. add localhost server + lineinfile: + path: /etc/config/dhcp + firstmatch: "true" + insertafter: "option leasefile '/tmp/dhcp.leases'" + line: "{{ item }}" + with_items: + - " list server '127.0.0.1#5453'" + - " option noresolv '1'" + notify: + - Restart dnsmasq + when: dns_encrypt == "stubby" + +# Commit + + - name: uci commit firewall + uci: + command: commit + config: firewall + notify: + - Restart firewall + + - name: uci commit network + uci: + command: commit + config: network + notify: + - Restart network \ No newline at end of file diff --git a/domain-routing-openwrt/templates/config-sing-box.j2 b/domain-routing-openwrt/templates/config-sing-box.j2 new file mode 100644 index 0000000..57b19e8 --- /dev/null +++ b/domain-routing-openwrt/templates/config-sing-box.j2 @@ -0,0 +1,5 @@ +config sing-box 'main' + option enabled '1' + option user 'root' + option conffile '/etc/sing-box/config.json' + option workdir '/usr/share/sing-box' diff --git a/domain-routing-openwrt/templates/openwrt-30-vpnroute.j2 b/domain-routing-openwrt/templates/openwrt-30-vpnroute.j2 new file mode 100644 index 0000000..5f17acd --- /dev/null +++ b/domain-routing-openwrt/templates/openwrt-30-vpnroute.j2 @@ -0,0 +1,8 @@ +#!/bin/sh + +{% if tunnel == "wg" %} +ip route add table vpn default dev wg0 +{% elif (tunnel == "openvpn") or (tunnel == "singbox") or (tunnel == "tun2socks") %} +sleep 10 +ip route add table vpn default dev tun0 +{% endif %} diff --git a/domain-routing-openwrt/templates/openwrt-getdomains.j2 b/domain-routing-openwrt/templates/openwrt-getdomains.j2 new file mode 100644 index 0000000..1331f57 --- /dev/null +++ b/domain-routing-openwrt/templates/openwrt-getdomains.j2 @@ -0,0 +1,70 @@ +#!/bin/sh /etc/rc.common + +START=99 + +start () { + {% if ansible_distribution_major_version >= "22" and country == "russia-inside" %} + DOMAINS=https://raw.githubusercontent.com/itdoginfo/allow-domains/main/Russia/inside-dnsmasq-nfset.lst + {% endif %} + {% if ansible_distribution_major_version >= "22" and country == "russia-outside" %} + DOMAINS=https://raw.githubusercontent.com/itdoginfo/allow-domains/main/Russia/outside-dnsmasq-nfset.lst + {% endif %} + {% if ansible_distribution_major_version >= "22" and country == "ukraine" %} + DOMAINS=https://raw.githubusercontent.com/itdoginfo/allow-domains/main/Ukraine/inside-dnsmasq-nfset.lst + {% endif %} + {% if ansible_distribution_major_version < "22" and country == "russia-inside" %} + DOMAINS=https://raw.githubusercontent.com/itdoginfo/allow-domains/main/Russia/inside-dnsmasq-ipset.lst + {% endif %} + {% if ansible_distribution_major_version < "22" and country == "russia-outside" %} + DOMAINS=https://raw.githubusercontent.com/itdoginfo/allow-domains/main/Russia/outside-dnsmasq-ipset.lst + {% endif %} + {% if ansible_distribution_major_version < "22" and country == "ukraine" %} + DOMAINS=https://raw.githubusercontent.com/itdoginfo/allow-domains/main/Ukraine/inside-dnsmasq-ipset.lst + {% endif %} + count=0 + while true; do + if curl -m 3 github.com; then + curl -f $DOMAINS --output /tmp/dnsmasq.d/domains.lst + break + else + echo "GitHub is not available. Check the internet availability [$count]" + count=$((count+1)) + fi + done + + if dnsmasq --conf-file=/tmp/dnsmasq.d/domains.lst --test 2>&1 | grep -q "syntax check OK"; then + /etc/init.d/dnsmasq restart + fi + + {% if ansible_distribution_major_version >= "22" and (list_ip or list_community) %} + echo "Flush sets" + nft flush ruleset + {% endif %} + + {% if list_subnet or list_ip or list_community %} + dir=/tmp/lst + mkdir -p $dir + + count=0 + while true; do + if curl -m 3 https://antifilter.download/; then + {% if list_subnet %} + curl -f -z $dir/subnet.lst https://antifilter.download/list/subnet.lst --output $dir/subnet.lst + {% endif %} + {% if list_ip %} + curl -f -z $dir/ip.lst https://antifilter.download/list/ip.lst --output $dir/ip.lst + {% endif %} + {% if list_community %} + curl -f -z $dir/community.lst https://community.antifilter.download/list/community.lst --output $dir/community.lst + {% endif %} + break + else + echo "antifilter.download is not available. Check the internet availability [$count]" + count=$((count+1)) + fi + done + + echo "Firewall restart" + /etc/init.d/firewall restart + {% endif %} +} \ No newline at end of file diff --git a/domain-routing-openwrt/templates/sing-box-json.j2 b/domain-routing-openwrt/templates/sing-box-json.j2 new file mode 100644 index 0000000..fe6d3f3 --- /dev/null +++ b/domain-routing-openwrt/templates/sing-box-json.j2 @@ -0,0 +1,28 @@ +{ + "log": { + "level": "debug" + }, + "inbounds": [ + { + "type": "tun", + "interface_name": "tun0", + "domain_strategy": "ipv4_only", + "inet4_address": "172.16.250.1/30", + "auto_route": false, + "strict_route": false, + "sniff": true + } + ], + "outbounds": [ + { + "type": "$TYPE", + "server": "$HOST", + "server_port": $PORT, + "method": "$METHOD", + "password": "$PASS" + } + ], + "route": { + "auto_detect_interface": true + } + } \ No newline at end of file diff --git a/domain-routing-openwrt/tests/inventory b/domain-routing-openwrt/tests/inventory new file mode 100644 index 0000000..0ec060b --- /dev/null +++ b/domain-routing-openwrt/tests/inventory @@ -0,0 +1,2 @@ +[openwrt] +192.168.56.23 diff --git a/domain-routing-openwrt/tests/test.yml b/domain-routing-openwrt/tests/test.yml new file mode 100644 index 0000000..764b458 --- /dev/null +++ b/domain-routing-openwrt/tests/test.yml @@ -0,0 +1,6 @@ +--- +- hosts: openwrt + remote_user: root + + roles: + - domain-routing-openwrt