vpn_routing_mac 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +37 -0
- data/LICENSE +21 -0
- data/README.md +87 -0
- data/Rakefile +6 -0
- data/bin/console +17 -0
- data/bin/setup +9 -0
- data/config/ip-up +21 -0
- data/exe/vpn-routing +116 -0
- data/lib/vpn_routing_mac/Installer.rb +42 -0
- data/lib/vpn_routing_mac/application.rb +119 -0
- data/lib/vpn_routing_mac/command_executor.rb +26 -0
- data/lib/vpn_routing_mac/config.rb +191 -0
- data/lib/vpn_routing_mac/version.rb +3 -0
- data/lib/vpn_routing_mac.rb +4 -0
- data/vpn_routing_mac.gemspec +28 -0
- metadata +80 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e9ecb73a4d58f0057830bf9e6f67249908bd9d7f3651f562304b84c65a5782e1
|
4
|
+
data.tar.gz: 21cbf564773aa16289eeea22ceaf772748d47f3f3848fef2497c1e353398ba27
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c99b84e0e3764f87a34dcbcdc1fffdf50956d07893b29bbb13dbb980b9688439cb651904dc7aa52e9d3087e9c6d2dbb7bc300f2df90818bae0aec46c0ad2fe23
|
7
|
+
data.tar.gz: fd5ff97f2d0ddd639e08657e525d91ca8b738bdbade9c833bf3393b881d343d2f108ed5b1424aa5f09cc2a2e9fb1601d995f6116857c4dab9822c0065803140a
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
system
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
vpn_routing_mac (0.1.0)
|
5
|
+
thor (~> 1.1.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
diff-lcs (1.5.0)
|
11
|
+
rake (12.3.3)
|
12
|
+
rspec (3.10.0)
|
13
|
+
rspec-core (~> 3.10.0)
|
14
|
+
rspec-expectations (~> 3.10.0)
|
15
|
+
rspec-mocks (~> 3.10.0)
|
16
|
+
rspec-core (3.10.1)
|
17
|
+
rspec-support (~> 3.10.0)
|
18
|
+
rspec-expectations (3.10.1)
|
19
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
20
|
+
rspec-support (~> 3.10.0)
|
21
|
+
rspec-mocks (3.10.2)
|
22
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
23
|
+
rspec-support (~> 3.10.0)
|
24
|
+
rspec-support (3.10.3)
|
25
|
+
thor (1.1.0)
|
26
|
+
|
27
|
+
PLATFORMS
|
28
|
+
ruby
|
29
|
+
universal-darwin-19
|
30
|
+
|
31
|
+
DEPENDENCIES
|
32
|
+
rake (~> 12.0)
|
33
|
+
rspec (~> 3.0)
|
34
|
+
vpn_routing_mac!
|
35
|
+
|
36
|
+
BUNDLED WITH
|
37
|
+
2.3.3
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2021 Yuji Kuroko
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# VpnRoutingMac
|
2
|
+
|
3
|
+
## License
|
4
|
+
|
5
|
+
MIT
|
6
|
+
|
7
|
+
## Requirement
|
8
|
+
|
9
|
+
macOS catalina or later
|
10
|
+
|
11
|
+
## Description
|
12
|
+
|
13
|
+
VpnRoutingMac is vpn routing tool for macOS.
|
14
|
+
When using a VPN, we will connect via the VPN all packet.
|
15
|
+
VpnRoutingMac can routing part of endpoint using /etc/ppp/ip-up.
|
16
|
+
|
17
|
+
VpnRoutingMacはmacOS向けのVPNルーティングツールです。
|
18
|
+
通常、VPNを繋ぐとすべてのパケットがVPNを通過するようになります。
|
19
|
+
VpnRoutingMacを使うと、/etc/ppp/ip-upの機能を利用して一部の通信のみVPNを通すことができます。
|
20
|
+
必要な通信のみVPNを通し、Google MeetやZoomはVPNを介さないようにすることで、
|
21
|
+
快適なネット環境およびVPNへの負荷軽減を行うことができます。
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
```bash
|
26
|
+
sudo /usr/bin/gem install bundler -v 2.3.3
|
27
|
+
sudo /usr/bin/gem install vpn_routing_mac
|
28
|
+
sudo vpn-routing install
|
29
|
+
```
|
30
|
+
|
31
|
+
## Uninstall
|
32
|
+
|
33
|
+
```bash
|
34
|
+
sudo vpn-routing uninstall
|
35
|
+
```
|
36
|
+
|
37
|
+
## Usage
|
38
|
+
|
39
|
+
### new VPN config
|
40
|
+
|
41
|
+
1. you connect VPN.
|
42
|
+
2. see `sudo cat ~/.vpn-routing/recent.log` in the IP address.
|
43
|
+
3. `sudo vpn-routing create-config --dir #{CONFIG_NAME} --gateway-ip=#{IP_ADDRESS}
|
44
|
+
verbose option is show executed command.
|
45
|
+
### routing config
|
46
|
+
|
47
|
+
```
|
48
|
+
# If you have only one VPN config, you can omit the dir option.
|
49
|
+
# verbose option is show executed command.
|
50
|
+
|
51
|
+
# add routing ip with save the config.
|
52
|
+
sudo vpn-routing add-ip 192.168.1.1/32 --permanent --dir=#{CONFIG_NAME} --verbose
|
53
|
+
|
54
|
+
# delete routing ip. (without save the config)
|
55
|
+
sudo vpn-routing delete-ip 192.168.1.1/32 --dir=#{CONFIG_NAME} --verbose
|
56
|
+
|
57
|
+
# add routing domain with save the config.
|
58
|
+
sudo vpn-routing add-domain example.test --permanent --dir=#{CONFIG_NAME} --verbose
|
59
|
+
|
60
|
+
# delete routing ip. (without save the config)
|
61
|
+
sudo vpn-routing delete-domain example.test --dir=#{CONFIG_NAME} --verbose
|
62
|
+
|
63
|
+
# open config directory using Finder
|
64
|
+
vpn-routing edit
|
65
|
+
|
66
|
+
# reload config. (Isn't delete routing)
|
67
|
+
vpn-routing reload
|
68
|
+
```
|
69
|
+
|
70
|
+
### config directory
|
71
|
+
|
72
|
+
```
|
73
|
+
.vpn-routing/
|
74
|
+
├── config
|
75
|
+
│ └── CONFIG_DIR_NAME
|
76
|
+
│ ├── config.yml
|
77
|
+
│ ├── domains
|
78
|
+
│ │ └── cli.txt
|
79
|
+
│ └── ip_addresses
|
80
|
+
│ └── cli.txt
|
81
|
+
└── recent.log
|
82
|
+
```
|
83
|
+
|
84
|
+
## Contributing
|
85
|
+
|
86
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Yuji-Kuroko/vpn_routing_mac.
|
87
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "vpn_routing_mac"
|
5
|
+
require "vpn_routing_mac/application"
|
6
|
+
|
7
|
+
VpnRoutingMac::Application.setup
|
8
|
+
|
9
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
10
|
+
# with your gem easier. You can also use a different console, if you like.
|
11
|
+
|
12
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
13
|
+
# require "pry"
|
14
|
+
# Pry.start
|
15
|
+
|
16
|
+
require "irb"
|
17
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/config/ip-up
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
#
|
3
|
+
# @see http://www.ytsuboi.org/wp/archives/2178
|
4
|
+
#
|
5
|
+
# $1 interface-name
|
6
|
+
# $2 tty-device
|
7
|
+
# $3 speed
|
8
|
+
# $4 local-IP-address
|
9
|
+
# $5 remote-IP-address
|
10
|
+
# $6 ipparam
|
11
|
+
|
12
|
+
cd $(dirname $(realpath $0))/..
|
13
|
+
|
14
|
+
exe/vpn-routing ip-up\
|
15
|
+
--interface-name="$1"\
|
16
|
+
--tty-device="$2"\
|
17
|
+
--speed="$3"\
|
18
|
+
--local-ip="$4"\
|
19
|
+
--remote-ip="$5"\
|
20
|
+
--ipparam="$6" >/tmp/ppp.log 2>&1\
|
21
|
+
--verbose
|
data/exe/vpn-routing
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative "../lib/vpn_routing_mac/application"
|
4
|
+
VpnRoutingMac::Application.setup
|
5
|
+
require "thor"
|
6
|
+
|
7
|
+
class VpnRoutingCLI < Thor
|
8
|
+
class RequireSudoError < StandardError; end
|
9
|
+
class DoNotRequireSudoError < StandardError; end
|
10
|
+
|
11
|
+
desc "ip-up", "called by ip-up"
|
12
|
+
option :interface_name, required: true
|
13
|
+
option :remote_ip, required: true
|
14
|
+
option :tty_device
|
15
|
+
option :speed
|
16
|
+
option :local_ip
|
17
|
+
option :ipparam
|
18
|
+
option :verbose, type: :boolean, default: false
|
19
|
+
def ip_up
|
20
|
+
require_sudo!
|
21
|
+
dup_options = options.dup
|
22
|
+
CommandExecutor.instance.verbose = dup_options.delete("verbose")
|
23
|
+
VpnRoutingMac::Application.cmd_ip_up(**dup_options.transform_keys(&:to_sym))
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "reload", "reload configs"
|
27
|
+
option :verbose, type: :boolean, default: false
|
28
|
+
def reload
|
29
|
+
require_sudo!
|
30
|
+
dup_options = options.dup
|
31
|
+
CommandExecutor.instance.verbose = dup_options.delete("verbose")
|
32
|
+
VpnRoutingMac::Application.cmd_reload
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "add_domain", "add domain of routing"
|
36
|
+
option :permanent, type: :boolean, default: false
|
37
|
+
option :comment, type: :string, default: ""
|
38
|
+
option :dir, type: :string, default: nil
|
39
|
+
option :verbose, type: :boolean, default: false
|
40
|
+
def add_domain(domain)
|
41
|
+
require_sudo!
|
42
|
+
dup_options = options.dup
|
43
|
+
CommandExecutor.instance.verbose = dup_options.delete("verbose")
|
44
|
+
VpnRoutingMac::Application.cmd_add_domain(domain, **dup_options.transform_keys(&:to_sym))
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "delete_domain", "delete domain of routing (temporary)"
|
48
|
+
option :dir, type: :string, default: nil
|
49
|
+
option :verbose, type: :boolean, default: false
|
50
|
+
def delete_domain(domain)
|
51
|
+
require_sudo!
|
52
|
+
dup_options = options.dup
|
53
|
+
CommandExecutor.instance.verbose = dup_options.delete("verbose")
|
54
|
+
VpnRoutingMac::Application.cmd_delete_domain(domain, **dup_options.transform_keys(&:to_sym))
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "add_ip", "add ip of routing"
|
58
|
+
option :permanent, type: :boolean, default: false
|
59
|
+
option :comment, type: :string, default: ""
|
60
|
+
option :dir, type: :string, default: nil
|
61
|
+
option :verbose, type: :boolean, default: false
|
62
|
+
def add_ip(ip)
|
63
|
+
require_sudo!
|
64
|
+
dup_options = options.dup
|
65
|
+
CommandExecutor.instance.verbose = dup_options.delete("verbose")
|
66
|
+
VpnRoutingMac::Application.cmd_add_ip(ip, **dup_options.transform_keys(&:to_sym))
|
67
|
+
end
|
68
|
+
|
69
|
+
desc "delete_ip", "delete ip of routing (temporary)"
|
70
|
+
option :dir, type: :string, default: nil
|
71
|
+
option :verbose, type: :boolean, default: false
|
72
|
+
def delete_ip(domain)
|
73
|
+
require_sudo!
|
74
|
+
dup_options = options.dup
|
75
|
+
CommandExecutor.instance.verbose = dup_options.delete("verbose")
|
76
|
+
VpnRoutingMac::Application.cmd_delete_ip(domain, **dup_options.transform_keys(&:to_sym))
|
77
|
+
end
|
78
|
+
|
79
|
+
desc "edit", "open config directory in Finder"
|
80
|
+
def edit
|
81
|
+
do_not_require_sudo!
|
82
|
+
`open -a "Finder" "#{VpnRoutingMac::Config.config_dir}"`
|
83
|
+
end
|
84
|
+
|
85
|
+
desc "install", "setup /etc/ppp/ip-up"
|
86
|
+
def install
|
87
|
+
require_sudo!
|
88
|
+
VpnRoutingMac::Application.cmd_install
|
89
|
+
end
|
90
|
+
|
91
|
+
desc "uninstall", "delete /etc/ppp/ip-up"
|
92
|
+
def uninstall
|
93
|
+
require_sudo!
|
94
|
+
VpnRoutingMac::Application.cmd_uninstall
|
95
|
+
end
|
96
|
+
|
97
|
+
desc "create_config", "create new vpn config"
|
98
|
+
option :dir, type: :string, required: true
|
99
|
+
option :gateway_ip, type: :string, required: true
|
100
|
+
def create_config
|
101
|
+
do_not_require_sudo!
|
102
|
+
VpnRoutingMac::Application.cmd_create_config(**options.transform_keys(&:to_sym))
|
103
|
+
end
|
104
|
+
|
105
|
+
no_tasks do
|
106
|
+
def require_sudo!
|
107
|
+
raise RequireSudoError.new("Error: require sudo") if `/usr/bin/whoami`.strip != "root"
|
108
|
+
end
|
109
|
+
|
110
|
+
def do_not_require_sudo!
|
111
|
+
raise DoNotRequireSudoError.new("Error: Do not require sudo") if `/usr/bin/whoami`.strip == "root"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
VpnRoutingCLI.start(ARGV)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module VpnRoutingMac
|
2
|
+
class Installer
|
3
|
+
# required: sudo
|
4
|
+
def install
|
5
|
+
if ip_up_path.exist?
|
6
|
+
backup_ip_up! if ip_up_path.exist?
|
7
|
+
ip_up_path.delete
|
8
|
+
end
|
9
|
+
|
10
|
+
FileUtils.ln_s(project_ip_up_path, ip_up_path)
|
11
|
+
|
12
|
+
VpnRoutingMac::Config.etc_config_dir.unlink if VpnRoutingMac::Config.etc_config_dir.exist?
|
13
|
+
FileUtils.ln_s(VpnRoutingMac::Config.home_config_dir, VpnRoutingMac::Config.etc_config_dir)
|
14
|
+
end
|
15
|
+
|
16
|
+
# required: sudo
|
17
|
+
def uninstall
|
18
|
+
if ip_up_path.exist?
|
19
|
+
backup_ip_up!
|
20
|
+
ip_up_path.delete
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def project_ip_up_path
|
25
|
+
VpnRoutingMac::Application.project_root.join("config/ip-up")
|
26
|
+
end
|
27
|
+
|
28
|
+
def ip_up_dir_path
|
29
|
+
Pathname.new("/etc/ppp")
|
30
|
+
end
|
31
|
+
|
32
|
+
def ip_up_path
|
33
|
+
ip_up_dir_path.join("ip-up")
|
34
|
+
end
|
35
|
+
|
36
|
+
def backup_ip_up!
|
37
|
+
backup_dir_path = ip_up_dir_path.join(Time.now.strftime("backup.%Y%m%d%H%M%S"))
|
38
|
+
backup_dir_path.mkdir
|
39
|
+
FileUtils.cp(ip_up_path, backup_dir_path)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module VpnRoutingMac
|
2
|
+
class Application
|
3
|
+
class DefaultGatewayNotFound; end
|
4
|
+
|
5
|
+
class << self
|
6
|
+
# load
|
7
|
+
def setup
|
8
|
+
require "bundler/setup"
|
9
|
+
require "yaml"
|
10
|
+
project_root.glob("lib/**/*.rb").each do |path|
|
11
|
+
require path
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def project_root
|
16
|
+
Pathname.new("#{__dir__}/../..")
|
17
|
+
end
|
18
|
+
|
19
|
+
def set_default_gateway!
|
20
|
+
cmd_active_interface_name = <<~CMD
|
21
|
+
/sbin/ifconfig | /usr/bin/grep -o '^en[0-9]*' | /usr/bin/xargs -n1 -I{} /bin/zsh -c "/sbin/ifconfig {} | /usr/bin/grep 'inet ' > /dev/null && echo {}"
|
22
|
+
CMD
|
23
|
+
active_interface_name = CommandExecutor.instance.execute!(cmd_active_interface_name, exception: false).strip
|
24
|
+
raise DefaultGatewayNotFound if active_interface_name == ""
|
25
|
+
|
26
|
+
cmd = "/sbin/route change default -interface #{active_interface_name}"
|
27
|
+
CommandExecutor.instance.execute!(cmd)
|
28
|
+
end
|
29
|
+
|
30
|
+
def recent_vpn_log_path
|
31
|
+
VpnRoutingMac::Config.etc_config_dir.join("recent.log")
|
32
|
+
end
|
33
|
+
|
34
|
+
def cmd_ip_up(remote_ip:, interface_name:, local_ip:, tty_device:, speed:, ipparam:)
|
35
|
+
# logging vpn info
|
36
|
+
recent_vpn_log_path.open("a") do |f|
|
37
|
+
f.puts("#{Time.now}:#{interface_name}:#{remote_ip}")
|
38
|
+
end
|
39
|
+
|
40
|
+
config = VpnRoutingMac::Config.load_with_ip(remote_ip)
|
41
|
+
config.interface = interface_name
|
42
|
+
config.route_all!
|
43
|
+
|
44
|
+
set_default_gateway!
|
45
|
+
end
|
46
|
+
|
47
|
+
def cmd_reload
|
48
|
+
VpnRoutingMac::Config.active_configs.each do |config|
|
49
|
+
config.route_all!
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def cmd_add_domain(domain, permanent:, comment:, dir: nil)
|
54
|
+
config = config_with_dir(dir)
|
55
|
+
ip_addresses = VpnRoutingMac::Config.search_ip_with_domain(domain)
|
56
|
+
ip_addresses.each do |ip|
|
57
|
+
config.route!(ip)
|
58
|
+
puts "added: #{ip}"
|
59
|
+
end
|
60
|
+
|
61
|
+
if permanent
|
62
|
+
config.save_domain!(domain, comment)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def cmd_delete_domain(domain, dir: nil)
|
67
|
+
config = config_with_dir(dir)
|
68
|
+
ip_addresses = VpnRoutingMac::Config.search_ip_with_domain(domain)
|
69
|
+
ip_addresses.each do |ip|
|
70
|
+
config.delete_route!(ip)
|
71
|
+
puts "deleted: #{ip}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def cmd_add_ip(ip, permanent:, comment:, dir: nil)
|
76
|
+
config = config_with_dir(dir)
|
77
|
+
config.route!(ip)
|
78
|
+
puts "added: #{ip}"
|
79
|
+
|
80
|
+
if permanent
|
81
|
+
config.save_ip_address!(ip, comment)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def cmd_delete_ip(ip, dir: nil)
|
86
|
+
config = config_with_dir(dir)
|
87
|
+
config.delete_route!(ip)
|
88
|
+
puts "deleted: #{ip}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def config_with_dir(dir)
|
92
|
+
config = if dir
|
93
|
+
VpnRoutingMac::Config.find_with_directory_name(dir)
|
94
|
+
else
|
95
|
+
all_configs = VpnRoutingMac::Config.all_configs
|
96
|
+
raise "required: --dir option" if all_configs.count >= 2
|
97
|
+
|
98
|
+
# 1 directory only
|
99
|
+
all_configs.first
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def cmd_create_config(dir:, gateway_ip:)
|
104
|
+
new_dir_path = VpnRoutingMac::Config.create_config_directory(dir: dir, gateway_ip: gateway_ip)
|
105
|
+
puts "created: #{new_dir_path.to_s}"
|
106
|
+
end
|
107
|
+
|
108
|
+
# setup /etc/ppp/ip-up
|
109
|
+
def cmd_install
|
110
|
+
VpnRoutingMac::Installer.new.install
|
111
|
+
end
|
112
|
+
|
113
|
+
# delete /etc/ppp/ip-up
|
114
|
+
def cmd_uninstall
|
115
|
+
VpnRoutingMac::Installer.new.uninstall
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "singleton"
|
2
|
+
|
3
|
+
class CommandExecutor
|
4
|
+
include Singleton
|
5
|
+
attr_accessor :verbose
|
6
|
+
|
7
|
+
class CommandFailed < StandardError; end
|
8
|
+
|
9
|
+
def instance
|
10
|
+
@verbose = false
|
11
|
+
end
|
12
|
+
|
13
|
+
# execute command
|
14
|
+
#
|
15
|
+
# @param [String] command execute command
|
16
|
+
# @param [Boolean] verbose log for the command
|
17
|
+
# @param [Boolean] exception raise exception if the command failed
|
18
|
+
def execute!(command, verbose: @verbose, exception: true)
|
19
|
+
puts "cmd: #{command}" if verbose
|
20
|
+
ret, err, status = Open3.capture3(command)
|
21
|
+
|
22
|
+
raise CommandFailed.new(err) if exception && !status.success?
|
23
|
+
|
24
|
+
ret
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require "open3"
|
2
|
+
|
3
|
+
module VpnRoutingMac
|
4
|
+
class Config
|
5
|
+
class InterfaceNotFoundError < StandardError; end
|
6
|
+
class DirectoryNameNotFoundError < StandardError; end
|
7
|
+
|
8
|
+
attr_reader :gateway_ip
|
9
|
+
attr_reader :dir_path
|
10
|
+
attr_accessor :interface
|
11
|
+
|
12
|
+
# @return [VpnRoutingMac::Config, nil]
|
13
|
+
def self.load_with_ip(ip_address)
|
14
|
+
all_configs.find { |a| a.gateway_ip == ip_address }
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [VpnRoutingMac::Config]
|
18
|
+
def self.find_with_directory_name(dirname)
|
19
|
+
config = all_configs.find { |a| a.directory_name == dirname }
|
20
|
+
raise DirectoryNameNotFoundError.new(dirname) if config.nil?
|
21
|
+
|
22
|
+
config
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# configs in active connection
|
27
|
+
# @return [Array<VpnRoutingMac::Config>]
|
28
|
+
def self.active_configs
|
29
|
+
all_configs.select(&:active_connection?)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Array<VpnRoutingMac::Config>]
|
33
|
+
def self.all_configs
|
34
|
+
config_files = config_dir.glob("config/*/config.yml")
|
35
|
+
|
36
|
+
return [] if config_files.count == 0
|
37
|
+
|
38
|
+
config_files.map { |config_file|
|
39
|
+
params = YAML.load_file(config_file)
|
40
|
+
self.new(params, dir_path: config_file.dirname)
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.config_dir
|
45
|
+
if ENV.include?("HOME")
|
46
|
+
home_config_dir
|
47
|
+
else
|
48
|
+
etc_config_dir
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.home_config_dir
|
53
|
+
Pathname.new("#{ENV.fetch("HOME")}/.vpn-routing")
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.etc_config_dir
|
57
|
+
Pathname.new("/etc/ppp/config")
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [Pathname] created dir path
|
61
|
+
def self.create_config_directory(dir:, gateway_ip:)
|
62
|
+
new_config_dir = home_config_dir.join("config/#{dir}")
|
63
|
+
new_config_dir.mkpath
|
64
|
+
new_config_dir.join("config.yml").write({
|
65
|
+
gateway_ip: gateway_ip
|
66
|
+
})
|
67
|
+
new_config_dir.join("ip_addresses").mkdir
|
68
|
+
new_config_dir.join("domains").mkdir
|
69
|
+
|
70
|
+
new_config_dir
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [Array<String>]
|
74
|
+
def self.search_ip_with_domain(domain)
|
75
|
+
CommandExecutor.instance.execute!("/usr/bin/dig +short #{domain}").lines.map(&:strip).select { |a|
|
76
|
+
/\A\d+\.\d+\.\d+\.\d+\z/.match?(a)
|
77
|
+
}.map { |ip| "#{ip}/32" }
|
78
|
+
end
|
79
|
+
|
80
|
+
# ---
|
81
|
+
|
82
|
+
def initialize(params, dir_path: nil, interface: nil)
|
83
|
+
@gateway_ip = params["gateway_ip"]
|
84
|
+
@interface = interface
|
85
|
+
@dir_path = dir_path
|
86
|
+
end
|
87
|
+
|
88
|
+
def directory_name
|
89
|
+
@dir_path.basename.to_s
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return [Array<String>] ["example.test"]
|
93
|
+
def domains
|
94
|
+
file_paths = dir_path.glob("domains/*")
|
95
|
+
return [] if file_paths.count == 0
|
96
|
+
|
97
|
+
# e.g.
|
98
|
+
# example.test # The example domain
|
99
|
+
file_paths.map { |f| f.read.lines.map { |a| a.strip.split("#").first } }.flatten
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [Array<String>] ["192.168.1.1/32"]
|
103
|
+
def ip_addresses
|
104
|
+
file_paths = dir_path.glob("ip_addresses/*")
|
105
|
+
return [] if file_paths.count == 0
|
106
|
+
|
107
|
+
# e.g.
|
108
|
+
# 192.168.1.1/32 # local ip
|
109
|
+
file_paths.map { |f| f.read.lines.map { |a| a.strip.split("#").first } }.flatten
|
110
|
+
end
|
111
|
+
|
112
|
+
# #domains + #ip_addresses
|
113
|
+
#
|
114
|
+
# @return [Array<String>] ["192.168.1.1/32"]
|
115
|
+
def all_ip_addresses
|
116
|
+
(domains.map { |domain|
|
117
|
+
self.class.search_ip_with_domain(domain)
|
118
|
+
}.flatten + ip_addresses).uniq
|
119
|
+
end
|
120
|
+
|
121
|
+
# set routing
|
122
|
+
# require: MacOS catalina or later.
|
123
|
+
#
|
124
|
+
# @param [String] ip_address "192.168.191.0/32"
|
125
|
+
def route!(ip_address, interface: nil)
|
126
|
+
interface ||= self.interface
|
127
|
+
|
128
|
+
raise InterfaceNotFoundError.new(interface) unless interface_exist?(interface)
|
129
|
+
|
130
|
+
CommandExecutor.instance.execute!("/sbin/route add -net #{ip_address} -interface #{interface}")
|
131
|
+
end
|
132
|
+
|
133
|
+
def route_all!
|
134
|
+
all_ip_addresses.map(&:to_s).map(&:strip).select { |a| %r;\A\d+\.\d+\.\d+\.\d+(/\d+)?\z;.match?(a) }.each do |ip_address|
|
135
|
+
route!(ip_address)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def delete_route!(ip_address, interface: nil)
|
140
|
+
interface ||= self.interface
|
141
|
+
|
142
|
+
raise InterfaceNotFoundError.new(interface) unless interface_exist?(interface)
|
143
|
+
|
144
|
+
CommandExecutor.instance.execute!("/sbin/route delete -net #{ip_address} -interface #{interface}")
|
145
|
+
end
|
146
|
+
|
147
|
+
def interface(gateway_ip = @gateway_ip)
|
148
|
+
@interface ||= fetch_interface(gateway_ip)
|
149
|
+
end
|
150
|
+
|
151
|
+
# @param [String] gateway_ip 192.168.1.1
|
152
|
+
def fetch_interface(gateway_ip)
|
153
|
+
all_interfaces.find { |a|
|
154
|
+
CommandExecutor.instance.execute!("/sbin/ifconfig #{a} | grep #{gateway_ip}") rescue false
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
def active_connection?(gateway_ip = @gateway_ip)
|
159
|
+
fetch_interface(gateway_ip)
|
160
|
+
end
|
161
|
+
|
162
|
+
def all_interfaces
|
163
|
+
@all_interfaces ||= CommandExecutor.instance.execute!("/sbin/ifconfig -l").strip.split(" ")
|
164
|
+
end
|
165
|
+
|
166
|
+
def interface_exist?(interface)
|
167
|
+
all_interfaces.include?(interface)
|
168
|
+
end
|
169
|
+
|
170
|
+
def save_ip_address!(ip_address, comment)
|
171
|
+
cli_config_path = @dir_path.join("ip_addresses/cli.txt")
|
172
|
+
cli_config_path.open("a") do |f|
|
173
|
+
f.puts "#{ip_address} # #{comment}"
|
174
|
+
end
|
175
|
+
|
176
|
+
user = ENV.fetch("USER")
|
177
|
+
|
178
|
+
CommandExecutor.instance.execute!("chown #{user} #{cli_config_path.to_s}")
|
179
|
+
end
|
180
|
+
|
181
|
+
def save_domain!(domain, comment)
|
182
|
+
cli_config_path = @dir_path.join("domains/cli.txt")
|
183
|
+
cli_config_path.open("a") do |f|
|
184
|
+
f.puts "#{domain} # #{comment}"
|
185
|
+
end
|
186
|
+
|
187
|
+
user = ENV.fetch("USER")
|
188
|
+
CommandExecutor.instance.execute!("chown #{user} #{cli_config_path.to_s}")
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative 'lib/vpn_routing_mac/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "vpn_routing_mac"
|
5
|
+
spec.version = VpnRoutingMac::VERSION
|
6
|
+
spec.authors = ["Yuji Kuroko"]
|
7
|
+
spec.email = ["patye.roifure+gem@gmail.com"]
|
8
|
+
|
9
|
+
spec.summary = %q{Mac VPN routing setting tool.}
|
10
|
+
spec.description = %q{Mac VPN routing setting tool.}
|
11
|
+
spec.homepage = "https://github.com/Yuji-Kuroko/vpn_routing_mac"
|
12
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
13
|
+
|
14
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
15
|
+
spec.metadata["source_code_uri"] = "https://github.com/Yuji-Kuroko/vpn_routing_mac"
|
16
|
+
spec.metadata["changelog_uri"] = "https://github.com/Yuji-Kuroko/vpn_routing_mac"
|
17
|
+
|
18
|
+
# Specify which files should be added to the gem when it is released.
|
19
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
20
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
21
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
22
|
+
end
|
23
|
+
spec.bindir = "exe"
|
24
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
25
|
+
spec.require_paths = ["lib"]
|
26
|
+
|
27
|
+
spec.add_dependency "thor", "~>1.1.0"
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vpn_routing_mac
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yuji Kuroko
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-12-31 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.1.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.1.0
|
27
|
+
description: Mac VPN routing setting tool.
|
28
|
+
email:
|
29
|
+
- patye.roifure+gem@gmail.com
|
30
|
+
executables:
|
31
|
+
- vpn-routing
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- ".gitignore"
|
36
|
+
- ".rspec"
|
37
|
+
- ".ruby-version"
|
38
|
+
- ".travis.yml"
|
39
|
+
- Gemfile
|
40
|
+
- Gemfile.lock
|
41
|
+
- LICENSE
|
42
|
+
- README.md
|
43
|
+
- Rakefile
|
44
|
+
- bin/console
|
45
|
+
- bin/setup
|
46
|
+
- config/ip-up
|
47
|
+
- exe/vpn-routing
|
48
|
+
- lib/vpn_routing_mac.rb
|
49
|
+
- lib/vpn_routing_mac/Installer.rb
|
50
|
+
- lib/vpn_routing_mac/application.rb
|
51
|
+
- lib/vpn_routing_mac/command_executor.rb
|
52
|
+
- lib/vpn_routing_mac/config.rb
|
53
|
+
- lib/vpn_routing_mac/version.rb
|
54
|
+
- vpn_routing_mac.gemspec
|
55
|
+
homepage: https://github.com/Yuji-Kuroko/vpn_routing_mac
|
56
|
+
licenses: []
|
57
|
+
metadata:
|
58
|
+
homepage_uri: https://github.com/Yuji-Kuroko/vpn_routing_mac
|
59
|
+
source_code_uri: https://github.com/Yuji-Kuroko/vpn_routing_mac
|
60
|
+
changelog_uri: https://github.com/Yuji-Kuroko/vpn_routing_mac
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 2.3.0
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
requirements: []
|
76
|
+
rubygems_version: 3.0.3
|
77
|
+
signing_key:
|
78
|
+
specification_version: 4
|
79
|
+
summary: Mac VPN routing setting tool.
|
80
|
+
test_files: []
|