netd_mngr 0.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a1ec00680bcc40cc8eb18f09b108a04c26c7a7f0cbee12659bf4b503636bd724
4
+ data.tar.gz: 6c9b2fb762ef9fa0f5a204207df63eccb86a41c2f8ce018c418c19d89cab5938
5
+ SHA512:
6
+ metadata.gz: 5cae0e347c02a7c1e265a3e1fe4d059b4a8e2dbb58debab4c8ab7dbe80532c205d74b4b6d2bef036e91c930d63ed9fdc6caee9f8272414e463d542d68193e6ff
7
+ data.tar.gz: '0817719f7dcb0ff40f0d7f869b488b20e07ad3590814c2c34710a29ac54310e601a7d4f4c0f7481062bfae069068f0cbec9fe8196a84bab5095889cc0ac2ead1'
data/bin/netc ADDED
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'netd_core'
5
+ require 'logger'
6
+ require 'zlib'
7
+ require 'base64'
8
+ require 'json'
9
+
10
+ require 'dry/cli'
11
+ require 'awesome_print'
12
+
13
+ module NetC
14
+ # convention for dry/cli
15
+ module Command
16
+ # required to implement a cli command
17
+ extend Dry::CLI::Registry
18
+
19
+ # install the service file
20
+ class Install < Dry::CLI::Command
21
+ desc 'Install service file to systemd user directory'
22
+ def call(**)
23
+ # base64(zlib(netd.service))
24
+ service_file_data = 'eJwljLEKgzAURfd8RZaueUJnl2KHLl1EOohDjFcNvMby8pT69xW7He65nLZJUT' \
25
+ 'tTIQeJH41LKpsMsYwNbA/aYoAdF7FPaGVMW/+nzty/CLV60ZLWLNTHRLL2u73M5HgJninPXkAT3qegqy' \
26
+ 'tccf4SdDhSj5TVM3fm5ZNiuO3lgNGvrO7ITlDzAyzzNxM='
27
+
28
+ # reverse the encoding process for the systemd service file
29
+ service_file = Zlib::Inflate.inflate(Base64.decode64(service_file_data))
30
+ # path to install the file to
31
+ fname = "#{Dir.home}/.config/systemd/user/netd.service"
32
+ # write the decoded data to the file
33
+ File.write(fname, service_file)
34
+
35
+ # print install info
36
+ puts "Wrote service file to #{fname}"
37
+ puts "To enable the service, execute the following:\n"
38
+ puts 'systemctl --user enable netd.service'
39
+ puts 'systemctl --user start netd.service'
40
+ end
41
+ end
42
+
43
+ # list current forwards
44
+ class List < Dry::CLI::Command
45
+ # list current forwards
46
+ desc 'List the currently registered port forwards'
47
+ def call(**)
48
+ # open connection to server and
49
+ # dispatch the list command
50
+ NetDSvr.connect_to_netd do |socket|
51
+ socket.puts 'list|'
52
+ puts JSON.generate(NetDSvr.parse_list(socket))
53
+ end
54
+ end
55
+ end
56
+
57
+ module PortFwd
58
+ # Local port forward
59
+ class Local < Dry::CLI::Command
60
+ desc 'Create a new local port forward'
61
+ option :host, type: :String, default: '127.0.0.1', desc: 'Host address to bind to', required: true
62
+ option :bind, type: :String, default: '127.0.0.1:8000', desc: 'Host address to bind to in <ip>:<port> format'
63
+ option :remote, type: :String, desc: 'Remote address to connect to'
64
+ option :del, default: false, desc: 'Delete the specified forward'
65
+
66
+ def call(host:, bind:, remote:, del:, **)
67
+ # decode the bind host host_port pair
68
+ bind_addr, bind_port = bind.split(':')
69
+ # decode the remote host host_port pair
70
+ remote_addr, remote_port = remote.split(':')
71
+ # connect to server and send local forward request
72
+ NetDSvr.connect_to_netd do |socket|
73
+ if !del
74
+ socket.puts NetD::OperationRequest.local_port_forward(
75
+ host, bind_port.to_i, bind_addr, remote_port.to_i, remote_addr
76
+ )
77
+ else
78
+ socket.puts NetD::OperationRequest.delete_local_port_forward(host, remote_port.to_i, remote_addr)
79
+ end
80
+ puts socket.readline
81
+ end
82
+ end
83
+ end
84
+
85
+ # Remote port forward
86
+ class Remote < Dry::CLI::Command
87
+ desc 'Create a new remote port forward'
88
+ option :host, type: :String, default: '127.0.0.1', desc: 'Host address to bind to', required: true
89
+ option :bind, type: :String, default: '127.0.0.1:8000', desc: 'Remote address to bind to in <ip>:<port> format'
90
+ option :local, type: :String, desc: 'Local address to connect to'
91
+ option :del, default: false, desc: 'Delete the specified forward'
92
+
93
+ def call(host:, bind:, local:, del:, **)
94
+ # decode the remote bind host host_port pair
95
+ bind_addr, bind_port = bind.split(':')
96
+ # decode the local host host_port pair
97
+ local_addr, local_port = local.split(':')
98
+ # connect to server and send remote forward request
99
+ NetDSvr.connect_to_netd do |socket|
100
+ if !del
101
+ socket.puts NetD::OperationRequest.remote_port_forward(
102
+ host, bind_port.to_i, bind_addr, local_port.to_i, local_addr
103
+ )
104
+ else
105
+ socket.puts NetD::OperationRequest.delete_remote_port_forward(host, local_port.to_i, local_addr)
106
+ end
107
+ puts socket.readline
108
+ end
109
+ end
110
+ end
111
+ end
112
+ register 'install', Install
113
+ register 'list', List
114
+ register 'pfwd', aliases: ['p'] do |prefix|
115
+ prefix.register 'local', PortFwd::Local
116
+ prefix.register 'remote', PortFwd::Remote
117
+ end
118
+ end
119
+ end
120
+
121
+ def main
122
+ Dry::CLI.new(NetC::Command).call
123
+ end
124
+
125
+ main
data/bin/netd ADDED
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'netd_core'
5
+ require 'logger'
6
+ require 'socket'
7
+
8
+ def detect_already_running(socket_path, logger)
9
+ # if the file does not exist, the server
10
+ # cannot be running
11
+ return unless File.exist?(socket_path)
12
+
13
+ # attempt to detect crash
14
+ logger.warn('Socket path exists, detecting crash')
15
+ # if the connection does not except,
16
+ # a server is running
17
+ begin
18
+ UNIXSocket.open(socket_path).close
19
+ # connection refused means server is not running
20
+ # so clean-up the old socket file
21
+ rescue Errno::ECONNREFUSED
22
+ logger.info('crash detected, cleaning up')
23
+ File.unlink(socket_path)
24
+ return
25
+ end
26
+ logger.fatal('Server appears to be running already')
27
+ end
28
+
29
+ def main
30
+ # setup logger
31
+ logger = Logger.new("#{Dir.home}/.local/share/netd.log")
32
+ logger.level = Logger::INFO
33
+
34
+ # setup and validate the unix socket path
35
+ socket_path = "#{Dir.home}/.local/run/"
36
+ Dir.mkdir(socket_path) unless Dir.exist?(socket_path)
37
+ socket_path += 'netd.socket'
38
+
39
+ detect_already_running(socket_path, logger)
40
+
41
+ # setup the server
42
+ server = NetDSvr.new(socket_path, logger)
43
+ server.server_main
44
+ end
45
+
46
+ main
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/ssh'
4
+ require 'socket'
5
+
6
+ # netd namespace
7
+ module NetD
8
+ # encapsulates a currently running network operation
9
+ # Base Class
10
+ class NetworkOperation
11
+ attr_reader :request
12
+ attr_accessor :thread
13
+
14
+ def initialize(_request); end
15
+
16
+ def to_s
17
+ @request.values.join('|')
18
+ end
19
+
20
+ def close
21
+ @thread.thread_variable_set('stop', true)
22
+ end
23
+ end
24
+
25
+ # encapsulates a local port_forward
26
+ class LocalPortForward < NetworkOperation
27
+ def initialize(request)
28
+ super
29
+ @request = request
30
+ @thread = Thread.new(request) do |req|
31
+ stop = false
32
+ Net::SSH.start(req[:host], nil, keys_only: true) do |ssh|
33
+ ssh.forward.local(req[:bind_addr], req[:bind_port], req[:remote_addr], req[:remote_port])
34
+ ssh.loop(0.1) { true unless stop }
35
+ ssh.forward.cancel_local(req[:bind_port], req[:bind_addr])
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ # encapsulates a remote port_forward
42
+ class RemotePortForward < NetworkOperation
43
+ def initialize(request)
44
+ super
45
+ @request = request
46
+ @thread = Thread.new(request) do |req|
47
+ stop = false
48
+ Net::SSH.start(req[:host], nil, keys_only: true) do |ssh|
49
+ ssh.forward.remote(req[:bind_port], req[:bind_addr], req[:local_port], req[:local_addr])
50
+ ssh.loop(0.1) { true unless stop }
51
+ ssh.forward.cancel_remote(req[:bind_port], req[:bind_addr])
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ def test_forward
58
+ data = 'HELLO'
59
+ # create the server
60
+ TCPServer.open('127.0.0.1', 2224) do |svr|
61
+ # create the client
62
+ TCPSocket.open('127.0.0.1', 2222) do |c|
63
+ # accept the connection
64
+ s = svr.accept
65
+ # send a hello
66
+ c.puts data
67
+ # validate the data sent through
68
+ raise 'Forward test failed!!!' unless s.readline(chomp: true) == data
69
+ end
70
+ end
71
+ sleep(1) # let the threads complete
72
+ end
73
+
74
+ def test_setup_port_forwards
75
+ lpfwd = LocalPortForward.new({ 'request': 'lpfwd',
76
+ 'host': 'localhost',
77
+ 'bind_port': 2222, 'bind_addr': 'localhost',
78
+ 'remote_port': 2223, 'remote_addr': 'localhost' })
79
+ rpfwd = RemotePortForward.new({ 'request': 'rpfwd',
80
+ 'host': 'localhost',
81
+ 'bind_port': 2224, 'bind_addr': 'localhost',
82
+ 'local_port': 2223, 'local_addr': 'localhost' })
83
+ sleep(1) # let the threads finish setting up
84
+ [lpfwd, rpfwd]
85
+ end
86
+
87
+ def test
88
+ puts 'testing forwards'
89
+ # setup the ssh-agent connector
90
+ Net::SSH::Authentication::Agent.connect(nil, nil, '/home/shellspawn/.1password/agent.sock')
91
+ # create the port forwards
92
+ lpfwd, rpfwd = test_setup_port_forwards
93
+ puts lpfwd, rpfwd
94
+ # send some data through the tunnel
95
+ test_forward
96
+ # close both forwards
97
+ [lpfwd, rpfwd].map(&:close)
98
+ puts 'all cleaned up'
99
+ end
100
+ end
101
+
102
+ # if ran independently, run tests
103
+ test if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ # parse requests from a pipe delimited line
4
+ module NetD
5
+ # encapsulate a request
6
+ class OperationRequest
7
+ LOCAL_PORT_FORWARD = 'lpfwd'
8
+ REMOTE_PORT_FORWARD = 'rpfwd'
9
+ DELETE_LOCAL_PORT_FORWARD = 'delete_lpfwd'
10
+ DELETE_REMOTE_PORT_FORWARD = 'delete_rpfwd'
11
+ LIST_FORWARDS = 'list'
12
+
13
+ def initialize(request_str)
14
+ malformed_request unless request_str.include? '|'
15
+
16
+ blocks = request_str.split('|')
17
+ @command = blocks[0]
18
+ @args = blocks[1..] if blocks.length > 1
19
+ end
20
+
21
+ def parse
22
+ case @command
23
+ when LOCAL_PORT_FORWARD
24
+ pfwd_fmt(@command, 'local')
25
+ when REMOTE_PORT_FORWARD
26
+ pfwd_fmt(@command, 'remote')
27
+ when DELETE_LOCAL_PORT_FORWARD
28
+ del_pfwd_fmt(@command, 'local')
29
+ when DELETE_REMOTE_PORT_FORWARD
30
+ del_pfwd_fmt(@command, 'remote')
31
+ when LIST_FORWARDS
32
+ { 'request': LIST_FORWARDS }
33
+ else
34
+ raise 'wat'
35
+ end
36
+ end
37
+
38
+ def pfwd_fmt(command, direction)
39
+ malformed_request unless @args.length == 5
40
+ lorr = direction == 'remote' ? 'local' : 'remote'
41
+ {
42
+ 'request': command,
43
+ 'host': @args[0],
44
+ 'bind_addr': @args[1],
45
+ 'bind_port': @args[2].to_i,
46
+ "#{lorr}_addr": @args[3],
47
+ "#{lorr}_port": @args[4].to_i
48
+ }
49
+ end
50
+
51
+ def del_pfwd_fmt(command, direction)
52
+ lorr = direction == 'remote' ? 'local' : 'remote'
53
+ {
54
+ 'request': command,
55
+ 'host': @args[0],
56
+ "#{lorr}_addr": @args[1],
57
+ "#{lorr}_port": @args[2].to_i
58
+ }
59
+ end
60
+
61
+ def self.local_port_forward(host, bind_port, bind_addr, remote_port, remote_addr)
62
+ "#{LOCAL_PORT_FORWARD}|#{host}|#{bind_addr}|#{bind_port}|#{remote_addr}|#{remote_port}"
63
+ end
64
+
65
+ def self.remote_port_forward(host, bind_port, bind_addr, local_port, local_addr)
66
+ "#{REMOTE_PORT_FORWARD}|#{host}|#{bind_addr}|#{bind_port}|#{local_addr}|#{local_port}"
67
+ end
68
+
69
+ def self.delete_local_port_forward(host, remote_port, remote_addr)
70
+ "#{DELETE_LOCAL_PORT_FORWARD}|#{host}|#{remote_addr}|#{remote_port}"
71
+ end
72
+
73
+ def self.delete_remote_port_forward(host, local_port, local_addr)
74
+ "#{DELETE_REMOTE_PORT_FORWARD}|#{host}|#{local_addr}|#{local_port}"
75
+ end
76
+
77
+ def malformed_request
78
+ raise 'malformed request'
79
+ end
80
+ end
81
+ end
data/lib/netd_core.rb ADDED
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'netd_core/request'
4
+ require 'netd_core/netop'
5
+
6
+ # the server class of netd
7
+ class NetDSvr
8
+ # the server class of netd
9
+ def initialize(socket_path, logger)
10
+ @path = socket_path
11
+ @net_ops = []
12
+ @logger = logger
13
+ end
14
+
15
+ # standard connection to the netd.socket unix server
16
+ def self.connect_to_netd(&block)
17
+ # open the socket and execute the block
18
+ UNIXSocket.open("#{Dir.home}/.local/run/netd.socket", &block)
19
+ # if failed, print error
20
+ rescue Errno::ECONNREFUSED => e
21
+ puts "NetD server does not appear to be running #{e}"
22
+ end
23
+
24
+ # if local then the field is 'remote' if remote the command requires 'local'
25
+ def self.get_direction(command)
26
+ command == NetD::OperationRequest::LOCAL_PORT_FORWARD ? 'remote' : 'local'
27
+ end
28
+
29
+ def self.parse_line(line)
30
+ tokens = line.split('|')
31
+ # common to both commands
32
+ req = { 'request': tokens[0], 'host': tokens[1], 'bind_addr': tokens[2], 'bind_port': tokens[3] }
33
+ # detect last line
34
+ return if tokens[0] == 'OKAY'
35
+
36
+ # add the fields to the output
37
+ direction = NetDSvr.get_direction(tokens[0])
38
+ req["#{direction}_addr"] = tokens[4]
39
+ req["#{direction}_port"] = tokens[5]
40
+ req
41
+ end
42
+
43
+ def self.parse_list(sock)
44
+ # parse the first line (number of entries)
45
+ entries = []
46
+ number_of_lines = sock.readline.chomp[0..-2].to_i
47
+ number_of_lines.times do
48
+ # pretty print the hash result from parse_line
49
+ entries << NetDSvr.parse_line(sock.readline.chomp)
50
+ end
51
+ entries
52
+ end
53
+
54
+ def current_net_ops
55
+ # get a list of the commands used
56
+ # to dispatch the network operations
57
+ @net_ops.map(&:request).map { |v| v.values.join('|') }
58
+ end
59
+
60
+ def del_request(request_args)
61
+ @net_ops.each do |n|
62
+ case request_args[:request]
63
+ when NetD::OperationRequest::DELETE_LOCAL_PORT_FORWARD
64
+ if (n.request[:remote_addr] == request_args[:remote_addr]) && (n.request[:remote_port] == request_args[:remote_port])
65
+ n.close
66
+ @net_ops.delete(n)
67
+ return true
68
+ end
69
+ when NetD::OperationRequest::DELETE_REMOTE_PORT_FORWARD
70
+ if (n.request[:local_addr] == request_args[:local_addr]) && (n.request[:local_port] == request_args[:local_port])
71
+ n.close
72
+ @net_ops.delete(n)
73
+ return true
74
+ end
75
+ end
76
+ end
77
+ false
78
+ end
79
+
80
+ def dispatch_command(request_args, server_socket)
81
+ # switch on the request type, dispatch the request,
82
+ # and add the object to the tracking list
83
+ case request_args[:request]
84
+ when NetD::OperationRequest::LOCAL_PORT_FORWARD
85
+ @net_ops << NetD::LocalPortForward.new(request_args)
86
+ when NetD::OperationRequest::REMOTE_PORT_FORWARD
87
+ @net_ops << NetD::RemotePortForward.new(request_args)
88
+ when NetD::OperationRequest::DELETE_LOCAL_PORT_FORWARD
89
+ del_request(request_args)
90
+ when NetD::OperationRequest::DELETE_REMOTE_PORT_FORWARD
91
+ del_request(request_args)
92
+ when NetD::OperationRequest::LIST_FORWARDS
93
+ server_socket.puts("#{current_net_ops.length}|\n#{current_net_ops.join("\n")}")
94
+ else
95
+ raise 'how did you get here?'
96
+ end
97
+ end
98
+
99
+ def server_main
100
+ UNIXServer.open(@path) do |serv|
101
+ # accept, dispatch, respond for all connections
102
+ loop do
103
+ # accept new connection
104
+ server_socket = serv.accept
105
+ # read command, parse it, and dispatch it
106
+ dispatch_command(NetD::OperationRequest.new(server_socket.readline).parse, server_socket)
107
+ # respond with success
108
+ server_socket.puts 'OKAY|'
109
+ rescue EOFError, Errno::EPIPE, RuntimeError => e
110
+ # if connection fails or bad data is sent, report error
111
+ @logger.error("Malformed Request: #{e.message}\n#{e.backtrace} ")
112
+ server_socket.puts 'ERR|' unless server_socket.closed?
113
+ end
114
+ end
115
+ end
116
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: netd_mngr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - shellspawn
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-01-29 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: cnetd is a small userspace server that allows for cnet to request port
14
+ forwards and other services
15
+ email: shellspawn@protonmail.com
16
+ executables:
17
+ - netd
18
+ - netc
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - bin/netc
23
+ - bin/netd
24
+ - lib/netd_core.rb
25
+ - lib/netd_core/netop.rb
26
+ - lib/netd_core/request.rb
27
+ homepage:
28
+ licenses:
29
+ - MIT
30
+ metadata: {}
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 3.0.0
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubygems_version: 3.2.33
47
+ signing_key:
48
+ specification_version: 4
49
+ summary: Server used for background port fwds
50
+ test_files: []