Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rearranged args, added --allow-locally, removed --pings #247

Merged
merged 2 commits into from
Sep 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions openpyn/firewall.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,20 @@ def internally_allow_ports(interfaces_details: List, internally_allowed: List) -
])


# Open all ports for devices in the local network
def internally_allow_all(interfaces_details: List) -> None:
for interface in interfaces_details:
# if interface is active with an IP in it, and not "tun*"
if len(interface) == 3 and "tun" not in interface[0]:
subprocess.call([
"sudo", "iptables",
"-A", "INPUT",
"-i", interface[0],
"-s", interface[2],
"-j", "ACCEPT"
])


#Converts the allwed ports config to a series of iptable rules and applies them
def apply_allowed_port_rules(interfaces_details: List, allowed_ports_config: List) -> bool:

Expand Down
164 changes: 90 additions & 74 deletions openpyn/openpyn.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,38 +45,19 @@ def parse_args(argv: List[str]) -> argparse.Namespace:
(using current data from NordVPN website) with lowest latency from you. Find NordVPN \
servers in a given country or city. Tunnels DNS traffic through the VPN which normally \
(when using OpenVPN with NordVPN) goes through your ISP's DNS (still unencrypted, even if \
you use a third-party DNS servers) and completely compromises Privacy!", allow_abbrev=False)
you use a third-party DNS servers)", allow_abbrev=False)
parser.add_argument(
'-v', '--version', action='version', version="openpyn " + __version__)
parser.add_argument(
'--init', help='Initialise, store/change credentials, download/update VPN config files,\
needs root "sudo" access.', action='store_true')
parser.add_argument(
'-s', '--server', type=str, help='server name, i.e. ca64 or au10')
parser.add_argument(
'--tcp', help='use port TCP-443 instead of the default UDP-1194', action='store_true')
parser.add_argument(
'-c', '--country-code', type=str, help='Specify country code with 2 letters, i.e. au')
# use nargs='?' to make a positional arg optional
parser.add_argument(
'country', nargs='?', help='Country code can also be specified without "-c,"i.e. "openpyn au"')
parser.add_argument(
'-a', '--area', type=str, help='Specify area, city name or state e.g \
"openpyn au -a victoria" or "openpyn au -a \'sydney\'"')
'--init', help='Initialise, store/change credentials, download/update VPN config files,\
needs root "sudo" access.', action='store_true')
parser.add_argument(
'-d', '--daemon', help='Update and start systemd service openpyn.service,\
running it as a background process, to check status "systemctl status openpyn"',
action='store_true')
parser.add_argument(
'-m', '--max-load', type=int, default=70, help='Specify load threshold, \
rejects servers with more load than this, DEFAULT=70')
parser.add_argument(
'-t', '--top-servers', type=int, default=10, help='Specify the number of top \
servers to choose from the NordVPN\'s server list for the given country, these will be \
pinged, DEFAULT=10')
parser.add_argument(
'-p', '--pings', type=str, default="5", help='Specify number of pings \
to be sent to each server to determine quality, DEFAULT=5')
parser.add_argument(
'-k', '--kill', help='Kill any running OpenVPN process, very useful \
to kill openpyn process running in background with "-d" switch', action='store_true')
Expand All @@ -87,45 +68,9 @@ def parse_args(argv: List[str]) -> argparse.Namespace:
parser.add_argument(
'--skip-dns-patch', dest='skip_dns_patch', help='Skips DNS patching,\
leaves /etc/resolv.conf untouched. (Not recommended)', action='store_true')
parser.add_argument(
'-f', '--force-fw-rules', help='Enforce firewall rules to drop traffic when tunnel breaks\
, force disable DNS traffic going to any other interface', action='store_true')
parser.add_argument(
'--allow', dest='internally_allowed', help='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"', nargs='+'),
parser.add_argument(
'--allow-config', dest='internally_allowed_config', help='To be used with "f" to allow a complex \
a complex set of allow port rules. This option requires a path to a JSON file that contains the \
relevent config'
),
parser.add_argument(
'--allow-config-json', dest='internally_allowed_config_json', help='To be used with "f" to allow a complex \
a complex set of allow port rules. This option requires works the same as "--allow-config" option \
but accepts a json object as a string instead'
),
parser.add_argument(
'-l', '--list', dest="list_servers", type=str, nargs='?', default="nope",
help='If no argument given prints all Country Names and Country Codes; \
If country code supplied ("-l us"): Displays all servers in that given\
country with their current load and OpenVPN support status. Works in \
conjunction with (-a | --area, and server types (--p2p, --tor) \
e.g "openpyn -l it --p2p --area milano"')
parser.add_argument(
'--silent', help='Do not try to send notifications. Use if "libnotify" or "gi"\
are not available. Automatically used in systemd service file', action='store_true')
parser.add_argument(
'--p2p', help='Only look for servers with "Peer To Peer" support', action='store_true')
parser.add_argument(
'--dedicated', help='Only look for servers with "Dedicated IP" support', action='store_true')
parser.add_argument(
'--tor', dest='tor_over_vpn', help='Only look for servers with "Tor Over VPN" support', action='store_true')
parser.add_argument(
'--double', dest='double_vpn', help='Only look for servers with "Double VPN" support', action='store_true')
parser.add_argument(
'--anti-ddos', dest='anti_ddos', help='Only look for servers with "Obfuscated" support', action='store_true')
parser.add_argument(
'--netflix', dest='netflix', help='Only look for servers that are optimised for "Netflix"', action='store_true')
parser.add_argument(
'--test', help='Simulation only, do not actually connect to the VPN server', action='store_true')
parser.add_argument(
Expand All @@ -135,20 +80,85 @@ def parse_args(argv: List[str]) -> argparse.Namespace:
options, e.g. openpyn uk -o \'--status /var/log/status.log --log /var/log/log.log\'')
parser.add_argument(
'-loc', '--location', nargs=2, type=float, metavar=('latitude', 'longitude'))
parser.add_argument(

connect_options = parser.add_argument_group("Connect Options",
"Connect To A Specific Server Or Any In A Country; TCP or UDP")
connect_options.add_argument(
'-s', '--server', type=str, help='server name, i.e. ca64 or au10')
connect_options.add_argument(
'-c', '--country-code', type=str, help='Specify country code with 2 letters, i.e. au')
connect_options.add_argument(
'--tcp', help='use port TCP-443 instead of the default UDP-1194', action='store_true')

filter_options = parser.add_argument_group("Filter Options", "Find Specific Types Of Servers")
filter_options.add_argument(
'-a', '--area', type=str, help='Specify area, city name or state e.g \
"openpyn au -a victoria" or "openpyn au -a \'sydney\'"')
filter_options.add_argument(
'-m', '--max-load', type=int, default=70, help='Specify load threshold, \
rejects servers with more load than this, DEFAULT=70')
filter_options.add_argument(
'-t', '--top-servers', type=int, default=10, help='Specify the number of top \
servers to choose from the NordVPN\'s server list for the given country, these will be \
pinged, DEFAULT=10')
filter_options.add_argument(
'--p2p', help='Only look for servers with "Peer To Peer" support', action='store_true')
filter_options.add_argument(
'--dedicated', help='Only look for servers with "Dedicated IP" support', action='store_true')
filter_options.add_argument(
'--tor', dest='tor_over_vpn', help='Only look for servers with "Tor Over VPN" support', action='store_true')
filter_options.add_argument(
'--double', dest='double_vpn', help='Only look for servers with "Double VPN" support', action='store_true')
filter_options.add_argument(
'--anti-ddos', dest='anti_ddos', help='Only look for servers with "Obfuscated" support', action='store_true')
filter_options.add_argument(
'--netflix', dest='netflix', help='Only look for servers that are optimised for "Netflix"', action='store_true')

display_options = parser.add_argument_group("Display Options", "These Only Display Information")
display_options.add_argument(
'-l', '--list', dest="list_servers", type=str, nargs='?', default="nope",
help='If no argument given prints all Country Names and Country Codes; \
If country code supplied ("-l us"): Displays all servers in that given\
country with their current load and OpenVPN support status. Works in \
conjunction with (-a | --area, and server types (--p2p, --tor) \
e.g "openpyn -l it --p2p --area milano"')
display_options.add_argument(
'--status', dest='show_status', help='Show last change in connection status', action='store_true')
parser.add_argument(
display_options.add_argument(
'--stats', dest='show_stats', help='Show openvpn connection stats', action='store_true')

fw_options = parser.add_argument_group("Firewall Options", "Firewall and KillSwitch Options")
fw_options.add_argument(
'-f', '--force-fw-rules', help='Enforce firewall rules to drop traffic when tunnel breaks\
, force disable DNS traffic going to any other interface', action='store_true')
fw_options.add_argument(
'--allow-locally', dest='allow_locally', help='To be used with "-f" to allow input traffic \
on all ports from locally connected / INTERNAL IP RANGEs. for example 192.168.1.* range', action='store_true'),
fw_options.add_argument(
'--allow', dest='internally_allowed', help='To be used with "-f" to allow TCP connections to given 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 -f --allow 22 80"', nargs='+'),
fw_options.add_argument(
'--allow-config', dest='internally_allowed_config', help='To be used with "-f" to allow a complex \
set of port rules. This option requires a path to a JSON file that contains the \
relevent config'
),
fw_options.add_argument(
'--allow-config-json', dest='internally_allowed_config_json', help='To be used with "-f" to allow a complex \
a complex set of allow port rules. This option requires works the same as "--allow-config" option \
but accepts a json object as a string instead'
),

return parser.parse_args(argv[1:])


def main() -> bool:
args = parse_args(sys.argv)
return_code = run(
args.init, args.server, args.country_code, args.country, args.area, args.tcp,
args.daemon, args.max_load, args.top_servers, args.pings,
args.daemon, args.max_load, args.top_servers,
args.kill, args.kill_flush, args.update, args.list_servers,
args.force_fw_rules, args.p2p, args.dedicated, args.double_vpn,
args.force_fw_rules, args.allow_locally, args.p2p, args.dedicated, args.double_vpn,
args.tor_over_vpn, args.anti_ddos, args.netflix, args.test, args.internally_allowed,
args.internally_allowed_config, args.internally_allowed_config_json, args.skip_dns_patch,
args.silent, args.nvram, args.openvpn_options, args.location, args.show_status, args.show_stats)
Expand All @@ -158,8 +168,8 @@ def main() -> bool:
# run openpyn
# pylint: disable=R0911
def run(init: bool, server: str, country_code: str, country: str, area: str, tcp: bool, daemon: bool,
max_load: int, top_servers: int, pings: str, kill: bool, kill_flush: bool, update: bool, list_servers: bool,
force_fw_rules: bool, p2p: bool, dedicated: bool, double_vpn: bool, tor_over_vpn: bool, anti_ddos: bool,
max_load: int, top_servers: int, kill: bool, kill_flush: bool, update: bool, list_servers: bool,
force_fw_rules: bool, allow_locally: bool, p2p: bool, dedicated: bool, double_vpn: bool, tor_over_vpn: bool, anti_ddos: bool,
netflix: bool, test: bool, internally_allowed: List, internally_allowed_config: str, internally_allowed_config_json: dict,
skip_dns_patch: bool, silent: bool, nvram: str, openvpn_options: str, location: float, show_status: bool,
show_stats: bool) -> bool:
Expand Down Expand Up @@ -322,10 +332,10 @@ def run(init: bool, server: str, country_code: str, country: str, area: str, tcp
openpyn_options += " --max-load " + str(max_load)
if top_servers:
openpyn_options += " --top-servers " + str(top_servers)
if pings:
openpyn_options += " --pings " + pings
if force_fw_rules:
openpyn_options += " --force-fw-rules"
if allow_locally:
openpyn_options += " --allow-locally"
if p2p:
openpyn_options += " --p2p"
if dedicated:
Expand Down Expand Up @@ -485,12 +495,13 @@ def run(init: bool, server: str, country_code: str, country: str, area: str, tcp
if not better_servers_list:
logger.critical("There are no servers that satisfy your criteria, please broaden your search.")
return 1
pinged_servers_list = ping_servers(better_servers_list, pings, stats)
pinged_servers_list = ping_servers(better_servers_list, stats)
chosen_servers = choose_best_servers(pinged_servers_list, stats)

# only clear/touch FW Rules if "-f" used, skip if "--test"
if force_fw_rules and not test:
touch_iptables_rules(chosen_servers, port, skip_dns_patch, internally_allowed, internally_allowed_config, internally_allowed_config_json)
touch_iptables_rules(chosen_servers, port, skip_dns_patch, allow_locally,
internally_allowed, internally_allowed_config, internally_allowed_config_json)
# connect to chosen_servers, if one fails go to next
for aserver in chosen_servers:
if stats:
Expand Down Expand Up @@ -546,7 +557,8 @@ def run(init: bool, server: str, country_code: str, country: str, area: str, tcp

# only clear/touch FW Rules if "-f" used, skip if "--test"
if force_fw_rules and not test:
touch_iptables_rules([server], port, skip_dns_patch, internally_allowed, internally_allowed_config, internally_allowed_config_json)
touch_iptables_rules([server], port, skip_dns_patch, allow_locally,
internally_allowed, internally_allowed_config, internally_allowed_config_json)

if nvram:
check_config_files()
Expand Down Expand Up @@ -644,7 +656,8 @@ def load_tun_module():
raise RuntimeError("Cannot open TUN/TAP dev /dev/net/tun: No such file or directory")


def touch_iptables_rules(chosen_servers: List, port: str, skip_dns_patch: bool, internally_allowed: List, internally_allowed_config: str, internally_allowed_config_json: dict):
def touch_iptables_rules(chosen_servers: List, port: str, skip_dns_patch: bool, allow_locally: bool,
internally_allowed: List, internally_allowed_config: str, internally_allowed_config_json: dict):
network_interfaces = get_network_interfaces()
vpn_server_ips = []
firewall.flush_input_output()
Expand All @@ -658,6 +671,9 @@ def touch_iptables_rules(chosen_servers: List, port: str, skip_dns_patch: bool,
elif internally_allowed:
firewall.internally_allow_ports(network_interfaces, internally_allowed)

if allow_locally:
firewall.internally_allow_all(network_interfaces)

for server in chosen_servers:
vpn_server_ips.append(get_vpn_server_ip(server, port))

Expand Down Expand Up @@ -712,7 +728,7 @@ def find_better_servers(country_code: str, area: str, max_load: int, top_servers

# Pings servers with the specified no of "ping",
# Returns a sorted list by ping median average deviation
def ping_servers(better_servers_list: List, pings: str, stats: bool) -> List:
def ping_servers(better_servers_list: List, stats: bool) -> List:
pinged_servers_list = []
ping_supports_option_i = True # older ping command doesn't support "-i"

Expand All @@ -724,9 +740,9 @@ def ping_servers(better_servers_list: List, pings: str, stats: bool) -> List:
logger.warning("Your 'ping' command doesn't support '-i' or '-n', \
falling back to wait of 1 second between pings, pings will be slow")
if ping_supports_option_i == True:
ping_subprocess_command = ["ping", "-n", "-i", ".2", "-c", pings, "dns_placeholder"]
ping_subprocess_command = ["ping", "-n", "-i", ".2", "-c", "5", "dns_placeholder"]
else:
ping_subprocess_command = ["ping", "-c", pings, "dns_placeholder"]
ping_subprocess_command = ["ping", "-c", "5", "dns_placeholder"]

ping_subprocess_list = []

Expand Down Expand Up @@ -1182,7 +1198,7 @@ def run_openvpn(*args):

# "sudo killall openvpn" - the default signal sent is SIGTERM
# SIGTERM signal causes OpenVPN to exit gracefully - OpenVPN exits with 0 status

# logger.debug("RETURN CODE, {}".format(completed.returncode))
if completed.returncode == 0:
raise SystemExit

Expand Down