rg_refresh 0.1.2

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b54c12dfa4a5c985cfe527ab51c870b97ed2d0be2ebe9fd9fe2fed9f67649132
4
+ data.tar.gz: 7a4834fb5a821b5ad748ef9cfe6d6f60bb69081c4add0950187c942f6d242ea0
5
+ SHA512:
6
+ metadata.gz: 38176b52ecca576d3e39519020961c6fc24da80c4be79dea040e26d132b53b073d0e87048f512b7d818fe39068c6a881cec5dd22f26830886b0c8dd4242f0163
7
+ data.tar.gz: 67cf55cf0abdaa33df2cd155050edb6f9b74670b511abb4092d1c1f94f89d41e48a4ec7769fb16bb301b2da18a2321e7880f663742b5df1965946507f1362d04
@@ -0,0 +1,9 @@
1
+ /Gemfile.lock
2
+ /.bundle/
3
+ /.yardoc
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.1
5
+ before_install: gem install bundler -v 1.16.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in rg_refresh.gemspec
6
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Mike Pastore
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
13
+ all 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
21
+ THE SOFTWARE.
@@ -0,0 +1,122 @@
1
+ # rg_refresh
2
+
3
+ Execute a VLAN "flop" to allow the AT&T Residential Gateway (RG) to perform
4
+ 802.1x authentication via the Optical Network Terminal (ONT) before falling
5
+ back to the subscriber's "own" router/gateway hardware. The RG is toggled off
6
+ and on as needed via a remote-controlled power outlet (see below).
7
+
8
+ For more information about this procedure, please see brianlan's [original
9
+ document][1] on DSL Reports.
10
+
11
+ ## Requirements
12
+
13
+ * [Netgear gigabit-speed "Smart Managed Plus" switch][2]. The following models
14
+ are known to work:
15
+ * GS108Ev2
16
+ * GS105PE
17
+
18
+ Please submit a PR if you confirm another working model.
19
+ * [MQTT broker][3] (like [this one][9])
20
+ * "Smart" outlet that can be remotely-controlled (i.e. set "on" or "off") via a
21
+ message published to a MQTT bus. For example:
22
+ * SmartThings Hub, Zigbee or Z-Wave outlet, and [MQTT bridge][4]
23
+ * Z-Wave "stick" and outlet and [Home Assistant][5]
24
+ * Z-Wave "stick" and outlet and [MQTT bridge][6] (or [this one][7])
25
+ * Sonoff outlet flashed w/ [Tasmota][8] firmware
26
+ * Host (for the script) with:
27
+ * Ruby interpreter
28
+ * Access to the Netgear management console via HTTP
29
+ * Access to the MQTT broker via TCP/IP
30
+
31
+ > N.B. With a SmartThings- or other cloud-based solution, an Internet
32
+ > connection is required to perform the VLAN flop, so if the operation fails
33
+ > (or is attempted after your router has already lost its DHCP lease), your
34
+ > network may get stuck in an inconsistent state. To recover, run the script
35
+ > and toggle the RG power manually as indicated.
36
+
37
+ ## Installation
38
+
39
+ Follow the [guide][1] to establish the initial network environment, which
40
+ includes copying information from the RG to your own router. You should perform
41
+ the VLAN flop one time through by hand to make sure everything is set up and
42
+ working correctly. Jot down your VLAN IDs and port assignments for later.
43
+
44
+ Next, pick a server where the script will run and install it:
45
+
46
+ ```console
47
+ $ gem install rg_refresh
48
+ ```
49
+
50
+ Create a configuration file and write it somewhere sensible, e.g.
51
+ `/etc/rg_refresh.yml`, using the following template:
52
+
53
+ ```yaml
54
+ ---
55
+ :netgear:
56
+ # Tip: Give your switch a static IP address or DNS name if possible.
57
+ :address: 'http://a.b.c.d'
58
+ :password: password
59
+ :vlans:
60
+ :rg: 2
61
+ :my_router: 3
62
+ :ports_vlans:
63
+ # ONT is on switch port #1 in this example.
64
+ # Put .<vlan> to reference above VLAN assignments.
65
+ # Put ~ to preserve the port's current VLAN assignment; trailing ~ can be
66
+ # omitted if desired.
67
+ :reauth: [.rg, .rg, .my_router]
68
+ :bypass: [.my_router, .rg, .my_router]
69
+ :mqtt:
70
+ :client:
71
+ # This section is passed to MQTT::Client.connect as-is.
72
+ # https://www.rubydoc.info/gems/mqtt/MQTT/Client#instance_attr_details
73
+ :host: localhost
74
+ :port: 1883
75
+ :topic: smartthings/RG/switch
76
+ :messages:
77
+ # Remember to quote YAML-reserved terms like "on" and "off".
78
+ :reauth: 'on'
79
+ :bypass: 'off'
80
+ ```
81
+
82
+ Run the script to make sure it works correctly, passing in the path to the
83
+ configuration file created above. For example:
84
+
85
+ ```console
86
+ $ rg_refresh -c /etc/rg_refresh.yml
87
+ ```
88
+
89
+ Finally, schedule the script to run once a week or so, during off-hours,
90
+ and/or when your router loses its DHCP lease.
91
+
92
+ ## Development
93
+
94
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
95
+ `rake test` to run the tests. You can also run `bin/console` for an interactive
96
+ prompt that will allow you to experiment.
97
+
98
+ To install this gem onto your local machine, run `bundle exec rake install`. To
99
+ release a new version, update the version number in `version.rb`, and then run
100
+ `bundle exec rake release`, which will create a git tag for the version, push
101
+ git commits and tags, and push the `.gem` file to
102
+ [rubygems.org](https://rubygems.org).
103
+
104
+ ## Contributing
105
+
106
+ Bug reports and pull requests are welcome on GitHub at
107
+ https://github.com/mwpastore/rg_refresh.
108
+
109
+ ## License
110
+
111
+ The gem is available as open source under the terms of the [MIT
112
+ License](https://opensource.org/licenses/MIT).
113
+
114
+ [1]: http://www.dslreports.com/forum/r29903721-AT-T-Residential-Gateway-Bypass-True-bridge-mode
115
+ [2]: https://www.netgear.com/business/products/switches/web-managed/gigabit-web-managed-switch.aspx
116
+ [3]: https://github.com/mqtt/mqtt.github.io/wiki/servers
117
+ [4]: https://github.com/stjohnjohnson/smartthings-mqtt-bridge#readme
118
+ [5]: https://www.home-assistant.io/components/mqtt/
119
+ [6]: https://github.com/adpeace/zwave-mqtt-bridge#readme
120
+ [7]: https://github.com/ltoinel/ZWave2MQTT#readme
121
+ [8]: https://github.com/arendst/Sonoff-Tasmota/wiki/MQTT-Overview
122
+ [9]: https://github.com/mcollina/mosca#readme
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ require 'bundler/gem_tasks'
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.libs << 'test'
7
+ t.libs << 'lib'
8
+ t.test_files = FileList['test/**/*_test.rb']
9
+ end
10
+
11
+ task :default=>:test
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'rg_refresh'
5
+
6
+ require 'irb'
7
+ IRB.start(__FILE__)
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ require 'optparse'
4
+ require 'rg_refresh'
5
+ require 'yaml'
6
+
7
+ opts = {}
8
+ optparser = OptionParser.new do |cmd|
9
+ cmd.banner = "usage: #$0 [options]"
10
+
11
+ cmd.on('-c FILE', '--config=FILE', 'Configuration file') do |v|
12
+ opts[:config_file] = v
13
+ end
14
+ end
15
+
16
+ begin
17
+ optparser.parse!
18
+ rescue OptionParser::InvalidOption=>e
19
+ $stderr.puts e
20
+
21
+ abort optparser.help
22
+ end
23
+
24
+ abort optparser.help \
25
+ unless opts[:config_file] && ARGV.empty?
26
+
27
+ config = YAML.load(File.read(opts[:config_file]), :symbolize_names=>true)
28
+
29
+ begin
30
+ puts 'Connecting...'
31
+ $netgear = RgRefresh::Netgear.start(config.fetch(:netgear))
32
+ $rg = RgRefresh::PublishWrapper.new(config.fetch(:mqtt))
33
+
34
+ puts 'Affirming bypass mode (manually power RG *OFF* if necessary)...'
35
+ $netgear.transition_to(:bypass)
36
+ sleep 15
37
+ $rg.transition_to(:bypass)
38
+ sleep 15
39
+
40
+ puts 'Transitioning to re-auth mode (manually power RG *ON* if necessary)...'
41
+ $rg.transition_to(:reauth)
42
+ $netgear.transition_to(:reauth) # point of no return
43
+
44
+ puts 'Waiting for re-auth...'
45
+ sleep 240
46
+
47
+ puts 'Transitioning to bypass mode (manually power RG *OFF* if necessary)...'
48
+ $netgear.transition_to(:bypass)
49
+ sleep 15
50
+ $rg.transition_to(:bypass)
51
+ sleep 15
52
+ ensure
53
+ puts 'Disconnecting...'
54
+ $rg.finish
55
+ $netgear.finish
56
+ end
@@ -0,0 +1,4 @@
1
+ require 'rg_refresh/version'
2
+
3
+ require 'rg_refresh/netgear'
4
+ require 'rg_refresh/publish_wrapper'
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+ require 'http/cookie'
3
+ require 'net/http'
4
+ require 'uri'
5
+
6
+ module RgRefresh
7
+ class Netgear
8
+ LoginRequired = Class.new(StandardError)
9
+
10
+ attr_reader \
11
+ :base_uri, :http, :password, :jar,
12
+ :ports_vlans,
13
+ :hash, :state
14
+
15
+ def initialize(opts)
16
+ @base_uri = URI(opts.fetch(:address))
17
+ @http = Net::HTTP.new(@base_uri.host, @base_uri.port)
18
+ @password = opts.fetch(:password)
19
+ @jar = HTTP::CookieJar.new
20
+
21
+ @ports_vlans = opts.fetch(:ports_vlans).each_value do |arr|
22
+ arr.map! do |vlan|
23
+ if (vlan_name = vlan.to_s[/^\.(.+)/, 1])
24
+ opts[:vlans].fetch(vlan_name.to_sym)
25
+ else
26
+ vlan
27
+ end
28
+ end
29
+ end
30
+
31
+ @hash = nil
32
+ @state = []
33
+ end
34
+
35
+ def self.start(opts)
36
+ new(opts).tap do |netgear|
37
+ netgear.login
38
+ netgear.sync
39
+ end
40
+ end
41
+
42
+ def login
43
+ res = post('/login.cgi', { :password=>password })
44
+
45
+ fail 'Maximum sessions reached' \
46
+ if res.body.include?('Maximum sessions reached')
47
+ end
48
+
49
+ def sync
50
+ res = request('/8021qBasic.cgi')
51
+
52
+ parse_inputs(res.body, /hash/, /port\d+/) do |name, value|
53
+ if name == 'hash'
54
+ @hash = value
55
+ elsif name.sub!(/^port/, '')
56
+ @state[name.to_i] = value.to_i
57
+ end
58
+ end
59
+ end
60
+
61
+ def transition_to(mode)
62
+ arr = ports_vlans.fetch(mode)
63
+
64
+ new_state = state
65
+ .map
66
+ .with_index {|vlan, port| arr[port] || vlan }
67
+
68
+ form_data = new_state
69
+ .map
70
+ .with_index {|vlan, port| ["port#{port}".to_sym, vlan] }
71
+ .to_h
72
+ .merge!(:status=>'Enable', :hash=>hash)
73
+
74
+ post('/8021qBasic.cgi', form_data)
75
+
76
+ sync
77
+
78
+ fail 'Unable to set VLANs' unless state == new_state
79
+ end
80
+
81
+ def finish
82
+ begin
83
+ request('/logout.cgi')
84
+ rescue LoginRequired
85
+ end
86
+
87
+ begin
88
+ http.finish
89
+ rescue IOError
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def request(path, verb=:Get)
96
+ uri = base_uri.dup
97
+ uri.path += path
98
+
99
+ req = Net::HTTP.const_get(verb).new(uri)
100
+
101
+ # HTTP::Cookie's quoting is broken on older Rubies, so we'll do it manually.
102
+ #req['Cookie'] = HTTP::Cookie.cookie_value(jar.cookies(uri))
103
+ if (sid_cookie = jar.cookies(uri).find {|cookie| cookie.name == 'SID' })
104
+ req['Cookie'] = '%s=%s' % [sid_cookie.name, sid_cookie.value]
105
+ end
106
+
107
+ yield req if block_given?
108
+
109
+ res = http.request(req)
110
+ Array(res.get_fields('Set-Cookie')).each do |value|
111
+ jar.parse(value, uri)
112
+ end
113
+
114
+ raise LoginRequired if res.body.include?('RedirectToLoginPage')
115
+
116
+ res
117
+ end
118
+
119
+ def post(path, form_data)
120
+ request(path, :Post) do |req|
121
+ req.form_data = form_data
122
+ end
123
+ end
124
+
125
+ def parse_inputs(data, *pats)
126
+ name_re = /<input.+name=['"]?(#{Regexp.union(pats)})['"]?/
127
+ value_re = /value=['"]?(\d+)['"]?/
128
+
129
+ data.each_line do |line|
130
+ if (name = line[name_re, 1]) && (value = line[value_re, 1])
131
+ yield name, value
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ require 'mqtt'
3
+
4
+ module RgRefresh
5
+ class PublishWrapper
6
+ attr_reader :client, :topic, :messages
7
+
8
+ def initialize(opts)
9
+ @client = MQTT::Client.connect(opts.fetch(:client))
10
+ @topic = opts.fetch(:topic)
11
+ @messages = opts.fetch(:messages)
12
+ end
13
+
14
+ def transition_to(mode)
15
+ client.publish(topic, messages.fetch(mode))
16
+ end
17
+
18
+ def finish
19
+ client.disconnect
20
+ rescue
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: false
2
+ module RgRefresh
3
+ VERSION = '0.1.2'.freeze
4
+ end
@@ -0,0 +1,30 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'rg_refresh/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'rg_refresh'
7
+ spec.version = RgRefresh::VERSION
8
+ spec.authors = ['Mike Pastore']
9
+ spec.email = ['mike@oobak.org']
10
+
11
+ spec.summary = 'Automated refresh script for the AT&T Residential Gateway bypass'
12
+ spec.homepage = 'https://github.com/mwpastore/rg_refresh#readme'
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = %x{git ls-files -z}.split("\x0").reject do |f|
16
+ f.match(%r{^(test|spec|features)/})
17
+ end
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) {|f| File.basename(f) }
20
+ spec.require_paths = %w{lib}
21
+
22
+ spec.required_ruby_version = '~> 2.2'
23
+
24
+ spec.add_runtime_dependency 'http-cookie', '~> 1.0', '>= 1.0.3'
25
+ spec.add_runtime_dependency 'mqtt', '~> 0.5.0'
26
+
27
+ spec.add_development_dependency 'bundler', '~> 1.16'
28
+ spec.add_development_dependency 'rake', '~> 12.0'
29
+ spec.add_development_dependency 'minitest', '~> 5.0'
30
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rg_refresh
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Mike Pastore
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-06-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: http-cookie
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.0.3
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.0.3
33
+ - !ruby/object:Gem::Dependency
34
+ name: mqtt
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.5.0
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 0.5.0
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.16'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.16'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rake
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '12.0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '12.0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: minitest
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '5.0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '5.0'
89
+ description:
90
+ email:
91
+ - mike@oobak.org
92
+ executables:
93
+ - rg_refresh
94
+ extensions: []
95
+ extra_rdoc_files: []
96
+ files:
97
+ - ".gitignore"
98
+ - ".travis.yml"
99
+ - Gemfile
100
+ - LICENSE.txt
101
+ - README.md
102
+ - Rakefile
103
+ - bin/console
104
+ - bin/setup
105
+ - exe/rg_refresh
106
+ - lib/rg_refresh.rb
107
+ - lib/rg_refresh/netgear.rb
108
+ - lib/rg_refresh/publish_wrapper.rb
109
+ - lib/rg_refresh/version.rb
110
+ - rg_refresh.gemspec
111
+ homepage: https://github.com/mwpastore/rg_refresh#readme
112
+ licenses:
113
+ - MIT
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '2.2'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubyforge_project:
131
+ rubygems_version: 2.7.6
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: Automated refresh script for the AT&T Residential Gateway bypass
135
+ test_files: []