├── README.md
├── cpihole
└── Dockerfile
├── docker-compose.yml
├── example.env
├── sniproxyvpn
├── Dockerfile
└── start.sh
└── webui
├── Dockerfile
└── code
├── add-domain.php
├── assets
├── app.css
└── loading.gif
├── domains.php
├── footer.php
├── header.php
├── index.php
├── init.php
└── vpnconnection.php
/README.md:
--------------------------------------------------------------------------------
1 | # SmartGW
2 |
3 | SmartGW is a VPN Gateway/Proxy that allows you to route HTTP/HTTPS traffic for specific internet domains to go through VPN tunnel while keeping the other traffic goes through your ISP gateway.
4 |
5 | **Example:**
6 | - HTTP/HTTPS for **netflix.com** (from all devices in your network) --> Will go through your **VPN tunnel**.
7 | - HTTP/HTTPS for **youtube.com** (from all devices in your network) --> Will go through your **ISP gateway**.
8 |
9 | **This can help you to:**
10 | - Access Geo-restricted content.
11 | - Access any blocked domains.
12 | - Use it with all your devices (Laptop, Mobiles, SmartTV...etc).
13 | - Utilize your full ISP network speed to access any site that you don't want it to go through the VPN tunnel.
14 | - Work with Pi-Hole for Network-wide Ad Blocking
15 | - More browsing privacy?.
16 |
17 | ## Features
18 | * Works with all devices in local network (PC, Laptop, Mobiles, SmartTV...etc).
19 | * No need to change any settings in your devices, install SmartGW and configure the DNS in your internet router.
20 | * Simple GUI to check SmartGW status and to add/remove any domain.
21 | * Automatically connect to the fastest low latency VPN servers in a given country.
22 | * Auto retry and auto-failover to next best VPN server if the connection dies.
23 |
24 | ## How difficult it's?
25 | The setup is straightforward, you need a Linux server, and a NordVPN VPN subscription.
26 |
27 | ## What Do I need to start?
28 | 1. Linux server (any old laptop or single-board such us Raspberry Pi will work).
29 | 2. NordVPN VPN subscription.
30 | 3. Static IP in your local network.
31 | 4. Docker engine with basic know how.
32 |
33 | ## Instructions
34 | 1. Install Linux.
35 | 2. Set a static IP in your server.
36 | 3. Install Docker.
37 | 4. Download SmartGW source code.
38 | 5. Rename example.env to .env and change the variables
39 | ```
40 | #Your NordVPN Username
41 | VPN_USERNAME=yourUsername@nordvpn.com
42 | #Your NordVPN Password
43 | VPN_PASSWORD=yourNordvpnPassword
44 | #VPN country
45 | VPN_OPTIONS=us
46 | #Local network CIDR network
47 | CIDR_NETWORK=192.168.1.0/24
48 | #No need to change this
49 | VPN_DNS1=103.86.96.100
50 | #No need to change this
51 | VPN_DNS2=103.86.99.100
52 | #Pi-Hole web admin password
53 | PIHOLE_PASSWORD=pihole
54 | ```
55 | 6. Run SmartGW docker-compose
56 | ``` bash
57 | docker-compose up
58 | ```
59 | 7. Open your browser and type your SmartGW IP (port 8080) (http://Your-Server-IP:8080/) and start adding your domains (e.g., yahoo.com).
60 | 8. Define SmartGW IP address as the only DNS entry in the router.
61 | ```
62 | Login to your router’s configuration page and find the DHCP/DNS settings.
63 | Note: make sure you adjust this setting under your LAN settings and not the WAN.
64 | ```
65 | 
66 |
67 | Enjoy!.
68 |
69 | ## Screenshots
70 | 
71 | 
72 | .
73 |
--------------------------------------------------------------------------------
/cpihole/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM pihole/pihole:latest
2 |
3 |
4 | RUN apt-get update && apt-get install -y \
5 | inotify-tools \
6 | && mkdir -p /etc/dnsmasq.d \
7 | && touch /etc/dnsmasq.d/smartgw.conf \
8 | && mkdir -p /etc/services.d/inotifywait \
9 | && echo -e '#!/usr/bin/with-contenv bash' "\nwhile true\n\
10 | do\n\
11 | inotifywait -e create -e modify /etc/dnsmasq.d/smartgw.conf\n\
12 | pkill pihole-FTL\n\
13 | done\n" > /inotifywait.sh \
14 | && chmod 755 /inotifywait.sh \
15 | && echo -e '#!/usr/bin/with-contenv bash' "\ns6-echo 'Starting inotifywait'\n/inotifywait.sh" > /etc/services.d/inotifywait/run \
16 | && echo -e '#!/usr/bin/with-contenv bash' "\ns6-echo 'Stopping inotifywait'\nkillall -9 inotifywait.sh inotifywait " > /etc/services.d/inotifywait/finish \
17 | && chmod 755 /etc/services.d/inotifywait/run \
18 | && chmod 755 /etc/services.d/inotifywait/finish
19 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | # More info at https://github.com/pi-hole/docker-pi-hole/ and https://docs.pi-hole.net/
4 | services:
5 | # pihole:
6 | # container_name: pihole
7 | # image: pihole/pihole:latest
8 | # ports:
9 | # - "53:53/tcp"
10 | # - "53:53/udp"
11 | # - "67:67/udp"
12 | # - "800:80/tcp"
13 | # - "4430:443/tcp"
14 | # environment:
15 | # TZ: 'America/Chicago'
16 | # WEBPASSWORD: ${PIHOLE_PASSWORD}
17 | # # Volumes store your data between container upgrades
18 | # volumes:
19 | # - etcpihole:/etc/pihole/
20 | # - etcdnsmasqd:/etc/dnsmasq.d/
21 | # dns:
22 | # - 127.0.0.1
23 | # - 1.1.1.1
24 | # # Recommended but not required (DHCP needs NET_ADMIN)
25 | # # https://github.com/pi-hole/docker-pi-hole#note-on-capabilities
26 | # cap_add:
27 | # - NET_ADMIN
28 | # restart: unless-stopped
29 | cpihole:
30 | build:
31 | context: ./cpihole
32 | container_name: cpihole
33 | ports:
34 | - "53:53/tcp"
35 | - "53:53/udp"
36 | - "67:67/udp"
37 | - "8081:80/tcp"
38 | - "4431:443/tcp"
39 | environment:
40 | TZ: 'America/Chicago'
41 | WEBPASSWORD: ${PIHOLE_PASSWORD}
42 | # Volumes store your data between container upgrades
43 | volumes:
44 | - etcpihole:/etc/pihole/
45 | - etcdnsmasqd:/etc/dnsmasq.d/
46 | dns:
47 | - 127.0.0.1
48 | - 1.1.1.1
49 | cap_add:
50 | - NET_ADMIN
51 | restart: unless-stopped
52 | webui:
53 | build:
54 | context: ./webui
55 | container_name: webui
56 | privileged: true
57 | ports:
58 | - "8080:8080/tcp"
59 | environment:
60 | SERVER_IP: ${SERVER_IP}
61 | volumes:
62 | - etcdnsmasqd:/etc/dnsmasq.d/
63 | restart: unless-stopped
64 | sniproxyvpn:
65 | build:
66 | context: ./sniproxyvpn
67 | container_name: sniproxyvpn
68 | privileged: true
69 | ports:
70 | - "80:80/tcp"
71 | - "443:443/tcp"
72 | environment:
73 | VPN_USERNAME: ${VPN_USERNAME}
74 | VPN_PASSWORD: ${VPN_PASSWORD}
75 | VPN_OPTIONS: ${VPN_OPTIONS}
76 | CIDR_NETWORK: ${CIDR_NETWORK}
77 | VPN_DNS1: ${VPN_DNS1}
78 | VPN_DNS2: ${VPN_DNS2}
79 | restart: unless-stopped
80 | volumes:
81 | etcpihole:
82 | driver: local
83 | etcdnsmasqd:
84 | driver: local
--------------------------------------------------------------------------------
/example.env:
--------------------------------------------------------------------------------
1 | #Rename this file to .env and change the below variables
2 | VPN_USERNAME=sdasdas@example.com
3 | VPN_PASSWORD=sadasdas
4 | VPN_OPTIONS=us
5 | CIDR_NETWORK=192.168.1.0/24
6 | VPN_DNS1=103.86.96.100
7 | VPN_DNS2=103.86.99.100
8 | PIHOLE_PASSWORD=pihole
9 |
--------------------------------------------------------------------------------
/sniproxyvpn/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:edge
2 | MAINTAINER Ahmad https://github.com/mrahmadt/
3 |
4 | EXPOSE 80
5 | EXPOSE 443
6 |
7 | ENV VPN_USERNAME="username"
8 | ENV VPN_PASSWORD="password"
9 | ENV VPN_OPTIONS="options"
10 | ENV CIDR_NETWORK="192.168.1.0/24"
11 | ENV VPN_DNS1="103.86.96.100"
12 | ENV VPN_DNS2="103.86.99.100"
13 |
14 |
15 | RUN apk --update upgrade &&\
16 | apk add runit &&\
17 | rm -rf /var/cache/apk/* &&\
18 | mkdir -p /etc/service &&\
19 | mkdir /initser
20 |
21 | WORKDIR /initser
22 |
23 |
24 | #################
25 | #### dnsmasq ####
26 | #################
27 | RUN apk add --update dnsmasq &&\
28 | rm -rf /var/cache/apk/* &&\
29 | echo -e "localise-queries\n\
30 | no-resolv\n\
31 | cache-size=10000\n\
32 | local-ttl=2\n\
33 | server=${VPN_DNS1}\n\
34 | server=${VPN_DNS2}\n\
35 | domain-needed\n\
36 | bogus-priv\n\
37 | local-service\n\
38 | bind-interfaces\n\
39 | user=root\n\
40 | conf-dir=/etc/dnsmasq.d/,*.conf\n\
41 | " > /etc/dnsmasq.conf && \
42 | mkdir /etc/service/dnsmasq &&\
43 | echo -e '#!/bin/sh' "\nexec dnsmasq -C /etc/dnsmasq.conf --keep-in-foreground" > /etc/service/dnsmasq/run &&\
44 | chmod 755 /etc/service/dnsmasq/run
45 |
46 | COPY start.sh /initser
47 | RUN chmod 755 /initser/start.sh
48 |
49 |
50 | ####################
51 | ##### SNIPROXY #####
52 | ####################
53 | RUN apk add --update sniproxy && \
54 | rm -rf /var/cache/apk/* && \
55 | echo -e "user daemon\n\
56 | pidfile /var/run/sniproxy.pid\n\
57 | listen 80 {\n\
58 | proto http\n\
59 | }\n\
60 | listen 443 {\n\
61 | proto tls\n\
62 | }\n\
63 | table {\n\
64 | .* *\n\
65 | }\n\
66 | error_log {\n\
67 | filename /var/log/sniproxy-errors.log\n\
68 | priority debug\n\
69 | }\n\
70 | access_log {\n\
71 | filename /var/log/sniproxy-access.log\n\
72 | }\n\
73 | resolver {\n\
74 | nameserver 127.0.0.1\n\
75 | mode ipv4_only\n\
76 | }\n\
77 | " > /etc/sniproxy/sniproxy.conf &&\
78 | mkdir /etc/service/sniproxy &&\
79 | touch /var/log/sniproxy-errors.log &&\
80 | touch /var/log/sniproxy-access.log &&\
81 | chmod 777 /var/log/sniproxy-errors.log &&\
82 | chmod 777 /var/log/sniproxy-access.log &&\
83 | echo -e '#!/bin/sh' "\nexec sniproxy -f -c /etc/sniproxy/sniproxy.conf" > /etc/service/sniproxy/run &&\
84 | chmod 755 /etc/service/sniproxy/run
85 |
86 | ####################
87 | ##### PYTHON3 ######
88 | ####################
89 | RUN set -x && apk add --no-cache python3 && \
90 | python3 -m ensurepip && \
91 | rm -r /usr/lib/python*/ensurepip && \
92 | pip3 install --upgrade pip setuptools && \
93 | if [ ! -e /usr/bin/pip ]; then ln -s pip3 /usr/bin/pip ; fi && \
94 | if [[ ! -e /usr/bin/python ]]; then ln -sf /usr/bin/python3 /usr/bin/python; fi && \
95 | rm -r /root/.cache
96 |
97 |
98 | ####################
99 | ##### openpyn ######
100 | ####################
101 |
102 | RUN apk add --update openvpn unzip wget sudo iputils expect && \
103 | rm -rf /var/cache/apk/* && \
104 | python3 -m pip install --upgrade openpyn && \
105 | echo -e '#!/usr/bin/expect -f' > /initser/setup_openpyn.sh &&\
106 | echo -e "\n\n\
107 | set username [lindex \$argv 0]\n\
108 | set password [lindex \$argv 1]\n\
109 | \n\
110 | set timeout -1\n\
111 | spawn openpyn --init\n\
112 | match_max 100000\n\
113 | expect \"*\"\n\
114 | expect \"Enter your username\"\n" >> /initser/setup_openpyn.sh &&\
115 | echo -e 'send -- "$username\\r"' "\n" >> /initser/setup_openpyn.sh &&\
116 | echo -e 'expect "Enter the password"' "\n" >> /initser/setup_openpyn.sh &&\
117 | echo -e 'send -- "$password\\r"' "\n" >> /initser/setup_openpyn.sh &&\
118 | echo -e 'expect "*"' "\n" >> /initser/setup_openpyn.sh &&\
119 | echo -e 'send -- "\\r"' "\n" >> /initser/setup_openpyn.sh &&\
120 | echo -e "expect eof\n\
121 | #OK\n\
122 | " >> /initser/setup_openpyn.sh &&\
123 | chmod 755 /initser/setup_openpyn.sh &&\
124 | mkdir /etc/service/openpyn &&\
125 | echo -e '#!/bin/sh' "\nexec openpyn \$VPN_OPTIONS" > /etc/service/openpyn/run &&\
126 | chmod 755 /etc/service/openpyn/run
127 |
128 | # Final cleanup
129 | RUN rm -rf /var/cache/apk/* /tmp/* /var/tmp/*
130 |
131 | CMD ["/initser/start.sh"]
132 |
--------------------------------------------------------------------------------
/sniproxyvpn/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 |
4 | #copied from https://github.com/dperson/openvpn-client/blob/master/openvpn.sh
5 | ### vpnportforward: setup vpn port forwarding
6 | # Arguments:
7 | # port) forwarded port
8 | # Return: configured NAT rule
9 | vpnportforward() { local port="$1" protocol="${2:-tcp}"
10 | ip6tables -t nat -A OUTPUT -p $protocol --dport $port -j DNAT \
11 | --to-destination ::11:$port 2>/dev/null
12 | iptables -t nat -A OUTPUT -p $protocol --dport $port -j DNAT \
13 | --to-destination 127.0.0.11:$port
14 | echo "Setup forwarded port: $port $protocol"
15 | }
16 |
17 | ### return_route: add a route back to your network, so that return traffic works
18 | # Arguments:
19 | # network) a CIDR specified network range
20 | # Return: configured return route
21 | add_route6() {
22 | local network="$1" gw="$(ip -6 route | awk '/default/{print $3}')"
23 | ip -6 route | grep -q "$network" || ip -6 route add to $network via $gw dev eth0
24 | ip6tables -A OUTPUT --destination $network -j ACCEPT 2>/dev/null
25 | }
26 |
27 | ### return_route: add a route back to your network, so that return traffic works
28 | # Arguments:
29 | # network) a CIDR specified network range
30 | # Return: configured return route
31 | add_route() {
32 | local network="$1" gw="$(ip route |awk '/default/ {print $3}')"
33 | ip route | grep -q "$network" || ip route add to $network via $gw dev eth0
34 | iptables -A OUTPUT --destination $network -j ACCEPT
35 | }
36 |
37 |
38 | [[ -z "$VPN_USERNAME" ]] && { echo "nordvpn username is empty" ; exit 1; }
39 | [[ -z "$VPN_PASSWORD" ]] && { echo "nordvpn password is empty" ; exit 1; }
40 | [[ -z "$VPN_OPTIONS" ]] && { echo "openpyn options is empty" ; exit 1; }
41 | [[ -z "$CIDR_NETWORK" ]] && { echo "my_network is empty CIDR network (IE 192.168.1.0/24) to allow replies once the VPN is up" ; exit 1; }
42 |
43 | [[ "${CIDR6_NETWORK:-""}" ]] && add_route6 "$CIDR6_NETWORK"
44 | [[ "${CIDR_NETWORK:-""}" ]] && add_route "$CIDR_NETWORK"
45 |
46 |
47 |
48 | CONTAINER_ALREADY_STARTED="CONTAINER_ALREADY_STARTED"
49 | if [ ! -e $CONTAINER_ALREADY_STARTED ]; then
50 | touch $CONTAINER_ALREADY_STARTED
51 | echo "-- First container startup --"
52 | /initser/setup_openpyn.sh "$VPN_USERNAME" "$VPN_PASSWORD"
53 | else
54 | echo "-- Not first container startup --"
55 | fi
56 |
57 | exec /sbin/runsvdir -P /etc/service
58 |
59 | # --skip-dns-patch Skips DNS patching, leaves /etc/resolv.conf untouched.
60 | # -f, --force-fw-rules Enforce firewall rules to drop traffic when tunnel breaks , force disable DNS traffic going to any other interface
61 | # --allow INTERNALLY_ALLOWED [INTERNALLY_ALLOWED ...] To be used with "f" to allow ports but ONLY to INTERNAL IP RANGE. for example: you can use your PC as SSH, HTTP server for local devices (i.e. 192.168.1.* range) by "openpyn us --allow 22 80"
62 |
--------------------------------------------------------------------------------
/webui/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM php:7.3-alpine
2 | MAINTAINER Ahmad https://github.com/mrahmadt/
3 |
4 | ENV SERVER_IP="192.168.1.100"
5 |
6 | STOPSIGNAL SIGINT
7 |
8 | RUN mkdir -p /var/www/html \
9 | #&& mkdir -p /etc/dnsmasq.d \
10 | #&& touch /etc/dnsmasq.d/smartgw.conf \
11 | #&& chown -R www-data:www-data /etc/dnsmasq.d \
12 | && chown -R www-data:www-data /var/www/html
13 |
14 | WORKDIR /var/www/html
15 |
16 | RUN set -x \
17 | && apk add --no-cache --virtual .build-deps \
18 | sqlite-dev \
19 | && docker-php-ext-install pdo_sqlite \
20 | && apk del .build-deps
21 |
22 | COPY code /var/www/html/
23 | #USER www-data
24 |
25 | EXPOSE 8080
26 |
27 | CMD [ "php", "-S", "[::]:8080", "-t", "/var/www/html" ]
--------------------------------------------------------------------------------
/webui/code/add-domain.php:
--------------------------------------------------------------------------------
1 | lastErrorMsg();
36 | exit;
37 | }
38 |
39 |
40 | $stmt = $db->prepare('INSERT INTO domains (domain,ipaddress) VALUES(:domain,:ipaddress)');
41 |
42 | foreach($domains as $domain){
43 | $stmt->bindValue(':domain', $domain[0]);
44 | $stmt->bindValue(':ipaddress', $domain[1]);
45 | $stmt->execute();
46 | }
47 | $db->close();
48 | UpdateDNSMasqDomains();
49 | header('Location: domains.php');
50 | exit;
51 | }
52 |
53 |
54 |
55 | if(isset($_GET['bulk']) && $_GET['bulk']=='1'){
56 | addbulkForm();
57 | }
58 | addSimpleForm();
59 |
60 |
61 | function addSimpleForm(){
62 | require_once 'header.php'
63 | ?>
64 | Add Domain Bulk
65 |
72 |
82 |
83 |
90 | exec('DELETE FROM domains WHERE id=' . addslashes($_GET['delete']));
6 | $db->close();
7 | exit;
8 | }
9 |
10 | if(isset($_GET['action']) && ($_GET['action']=='UpdateDNSMasqDomains') ){
11 | UpdateDNSMasqDomains();
12 | header('Location: domains.php');
13 | exit;
14 | }
15 |
16 |
17 | $limit = ( isset( $_GET['limit'] ) && is_numeric($_GET['limit']) ) ? $_GET['limit'] : 100;
18 | $page = ( isset( $_GET['page'] ) && is_numeric($_GET['page'])) ? $_GET['page'] : 1;
19 | $links = ( isset( $_GET['links'] ) && is_numeric($_GET['links'])) ? $_GET['links'] : 7;
20 |
21 | $Paginator = new Paginator(DATABASE_FILE,'SELECT COUNT(id) AS total FROM domains');
22 | $result = $Paginator->getData('SELECT * FROM domains ORDER BY id DESC',$limit,$page);
23 |
24 | require_once 'header.php';
25 | ?>
26 |
27 |
28 |
29 |
30 |
31 | Domain |
32 | IP |
33 | Action |
34 |
35 |
36 |
37 | data as $row){
39 | $domain = strip_tags($row['domain']);
40 | $ipaddress = strip_tags($row['ipaddress']);
41 | $id = $row['id'];
42 | ?>
43 |
44 | |
45 | |
46 | Delete |
47 |
48 |
49 |
50 |
51 |
52 | createLinks( $links, 'pagination justify-content-center' ); ?>
53 | Important note: To avoid restarting the DNS service too often, domains are not automatically removed from DNS service. Click on below button once you finish to remove the domains from your DNS service.
54 | Restart my DNS
55 |
56 |
--------------------------------------------------------------------------------
/webui/code/footer.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |