init
This commit is contained in:
commit
4fd46b4f0e
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
/.bundle/
|
||||||
|
/.yardoc
|
||||||
|
/_yardoc/
|
||||||
|
/coverage/
|
||||||
|
/doc/
|
||||||
|
/pkg/
|
||||||
|
/spec/reports/
|
||||||
|
/tmp/
|
6
Gemfile
Normal file
6
Gemfile
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
# Specify your gem's dependencies in knot.gemspec
|
||||||
|
gemspec
|
||||||
|
|
||||||
|
gem "rake", "~> 12.0"
|
36
README.md
Normal file
36
README.md
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# Knot
|
||||||
|
|
||||||
|
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/knot`. To experiment with that code, run `bin/console` for an interactive prompt.
|
||||||
|
|
||||||
|
TODO: Delete this and the text above, and describe your gem
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Add this line to your application's Gemfile:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gem 'knot'
|
||||||
|
```
|
||||||
|
|
||||||
|
And then execute:
|
||||||
|
|
||||||
|
$ bundle install
|
||||||
|
|
||||||
|
Or install it yourself as:
|
||||||
|
|
||||||
|
$ gem install knot
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
TODO: Write usage instructions here
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
||||||
|
|
||||||
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/knot.
|
||||||
|
|
31
knot-ruby.gemspec
Normal file
31
knot-ruby.gemspec
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
require_relative 'lib/knot/version'
|
||||||
|
|
||||||
|
Gem::Specification.new do |spec|
|
||||||
|
spec.name = "knot"
|
||||||
|
spec.version = Knot::VERSION
|
||||||
|
spec.authors = ['Denis Knauf']
|
||||||
|
spec.email = ['gems+knot@denkn.at']
|
||||||
|
spec.licenses = ["LGPL-3.0"]
|
||||||
|
|
||||||
|
spec.summary = %q{Provides interface to knot-server.}
|
||||||
|
spec.description = %q{Implements knot-protocol to provide an interface to knot-DNS-server}
|
||||||
|
spec.homepage = 'https://git.denkn.at/deac/knot'
|
||||||
|
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
||||||
|
|
||||||
|
#spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
||||||
|
|
||||||
|
spec.metadata["homepage_uri"] = spec.homepage
|
||||||
|
spec.metadata["source_code_uri"] = spec.homepage
|
||||||
|
spec.metadata["changelog_uri"] = spec.homepage
|
||||||
|
|
||||||
|
spec.add_dependency 'iounpack', '~> 0'
|
||||||
|
|
||||||
|
# Specify which files should be added to the gem when it is released.
|
||||||
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
||||||
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
||||||
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
||||||
|
end
|
||||||
|
spec.bindir = "bin"
|
||||||
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
||||||
|
spec.require_paths = ["lib"]
|
||||||
|
end
|
4
lib/knot.rb
Normal file
4
lib/knot.rb
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
require 'knot/version'
|
||||||
|
require 'knot/errors'
|
||||||
|
require 'knot/protocol'
|
||||||
|
require 'knot/interface'
|
166
lib/knot/errors.rb
Normal file
166
lib/knot/errors.rb
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
module Knot
|
||||||
|
class Error < ::Exception
|
||||||
|
attr_reader :Errno
|
||||||
|
attr_reader :Key
|
||||||
|
attr_reader :Errstr
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module Knot::Errors
|
||||||
|
@num2exc = {}
|
||||||
|
@key2exc = {}
|
||||||
|
@err2exc = {}
|
||||||
|
class << self
|
||||||
|
attr_reader :num2exc, :key2exc, :err2exc
|
||||||
|
|
||||||
|
def array_arguments_with_typecheck *args
|
||||||
|
l = args.length
|
||||||
|
lambda do |a|
|
||||||
|
l == a.length and
|
||||||
|
args.zip(a).all? {|t,v| t === v }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_missing key
|
||||||
|
self[key]
|
||||||
|
end
|
||||||
|
|
||||||
|
def [] key_or_errno
|
||||||
|
case key_or_errno
|
||||||
|
when Integer
|
||||||
|
@num2exc[key_or_errno]
|
||||||
|
when Exception
|
||||||
|
key_or_errno
|
||||||
|
when Symbol
|
||||||
|
@key2exc[key_or_errno]
|
||||||
|
else
|
||||||
|
raise ArgumentError, "Invalid type. Expect Integer/Knot::Error/Symbol"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
[
|
||||||
|
[:EOK, 0, "OK"],
|
||||||
|
# Directly mapped error codes.
|
||||||
|
[:ENOMEM, -Errno::ENOMEM::Errno, "not enough memory" ],
|
||||||
|
[:EINVAL, -Errno::EINVAL::Errno, "invalid parameter" ],
|
||||||
|
[:ENOTSUP, -Errno::ENOTSUP::Errno, "operation not supported" ],
|
||||||
|
[:EBUSY, -Errno::EBUSY::Errno, "requested resource is busy" ],
|
||||||
|
[:EAGAIN, -Errno::EAGAIN::Errno, "OS lacked necessary resources" ],
|
||||||
|
[:EACCES, -Errno::EACCES::Errno, "operation not permitted" ],
|
||||||
|
[:ECONNREFUSED, -Errno::ECONNREFUSED::Errno, "connection refused" ],
|
||||||
|
[:EISCONN, -Errno::EISCONN::Errno, "already connected" ],
|
||||||
|
[:EADDRINUSE, -Errno::EADDRINUSE::Errno, "address already in use" ],
|
||||||
|
[:ENOENT, -Errno::ENOENT::Errno, "not exists" ],
|
||||||
|
[:EEXIST, -Errno::EEXIST::Errno, "already exists" ],
|
||||||
|
[:ERANGE, -Errno::ERANGE::Errno, "value is out of range" ],
|
||||||
|
[:EADDRNOTAVAIL, -Errno::EADDRNOTAVAIL::Errno, "address is not available" ],
|
||||||
|
|
||||||
|
# General errors.
|
||||||
|
[:ERROR, -1000, "failed"],
|
||||||
|
[:EPARSEFAIL, "parser failed"],
|
||||||
|
[:ESEMCHECK, "semantic check"],
|
||||||
|
[:EUPTODATE, "zone is up-to-date"],
|
||||||
|
[:EFEWDATA, "not enough data to parse"],
|
||||||
|
[:ESPACE, "not enough space provided"],
|
||||||
|
[:EMALF, "malformed data"],
|
||||||
|
[:ENSEC3PAR, "missing or wrong NSEC3PARAM record"],
|
||||||
|
[:ENSEC3CHAIN, "missing or wrong NSEC3 chain in the zone"],
|
||||||
|
[:EOUTOFZONE, "name does not belong to the zone"],
|
||||||
|
[:EZONEINVAL, "invalid zone file"],
|
||||||
|
[:ENOZONE, "no such zone found"],
|
||||||
|
[:ENONODE, "no such node in zone found"],
|
||||||
|
[:ENORECORD, "no such record in zone found"],
|
||||||
|
[:EISRECORD, "such record already exists in zone"],
|
||||||
|
[:ENOMASTER, "no usable master"],
|
||||||
|
[:EPREREQ, "UPDATE prerequisity not met"],
|
||||||
|
[:ETTL, "TTL mismatch"],
|
||||||
|
[:ENOXFR, "transfer was not sent"],
|
||||||
|
[:EDENIED, "not allowed"],
|
||||||
|
[:ECONN, "connection reset"],
|
||||||
|
[:ETIMEOUT, "connection timeout"],
|
||||||
|
[:ENODIFF, "cannot create zone diff"],
|
||||||
|
[:ENOTSIG, "expected a TSIG or SIG(0)"],
|
||||||
|
[:ELIMIT, "exceeded response rate limit"],
|
||||||
|
[:EZONESIZE, "zone size exceeded"],
|
||||||
|
[:EOF, "end of file"],
|
||||||
|
[:ESYSTEM, "system error"],
|
||||||
|
[:EFILE, "file error"],
|
||||||
|
[:ESOAINVAL, "SOA mismatch"],
|
||||||
|
[:ETRAIL, "trailing data"],
|
||||||
|
[:EPROCESSING, "processing error"],
|
||||||
|
|
||||||
|
# Control states.
|
||||||
|
[:CTL_ESTOP, "stopping server"],
|
||||||
|
# Network errors.
|
||||||
|
[:NET_EADDR, "bad address or host name"],
|
||||||
|
[:NET_ESOCKET, "can't create socket"],
|
||||||
|
[:NET_ECONNECT, "can't connect"],
|
||||||
|
[:NET_ESEND, "can't send data"],
|
||||||
|
[:NET_ERECV, "can't receive data"],
|
||||||
|
[:NET_ETIMEOUT, "network timeout"],
|
||||||
|
# Encoding errors.
|
||||||
|
[:BASE64_ESIZE, "invalid base64 string length"],
|
||||||
|
[:BASE64_ECHAR, "invalid base64 character"],
|
||||||
|
[:BASE32HEX_ESIZE, "invalid base32hex string length"],
|
||||||
|
[:BASE32HEX_ECHAR, "invalid base32hex character"],
|
||||||
|
# TSIG errors.
|
||||||
|
[:KNOT_TSIG_EBADSIG, "failed to verify TSIG"],
|
||||||
|
[:KNOT_TSIG_EBADKEY, "TSIG key not recognized or invalid"],
|
||||||
|
[:KNOT_TSIG_EBADTIME, "TSIG out of time window"],
|
||||||
|
[:KNOT_TSIG_EBADTRUNC, "TSIG bad truncation"],
|
||||||
|
# DNSSEC errors.
|
||||||
|
[:DNSSEC_ENOKEY, "no keys for signing"],
|
||||||
|
[:DNSSEC_EMISSINGKEYTYPE, "missing active KSK or ZSK"],
|
||||||
|
# Yparser errors.
|
||||||
|
[:YP_ECHAR_TAB, "tabulator character is not allowed"],
|
||||||
|
[:YP_EINVAL_ITEM, "invalid item"],
|
||||||
|
[:YP_EINVAL_ID, "invalid identifier"],
|
||||||
|
[:YP_EINVAL_DATA, "invalid value"],
|
||||||
|
[:YP_EINVAL_INDENT, "invalid indentation"],
|
||||||
|
[:YP_ENOTSUP_DATA, "value not supported"],
|
||||||
|
[:YP_ENOTSUP_ID, "identifier not supported"],
|
||||||
|
[:YP_ENODATA, "missing value"],
|
||||||
|
[:YP_ENOID, "missing identifier"],
|
||||||
|
# Configuration errors.
|
||||||
|
[:CONF_ENOTINIT, "config DB not initialized"],
|
||||||
|
[:CONF_EVERSION, "invalid config DB version"],
|
||||||
|
[:CONF_EREDEFINE, "duplicate identifier"],
|
||||||
|
# Transaction errors.
|
||||||
|
[:TXN_EEXISTS, "too many transactions"],
|
||||||
|
[:TXN_ENOTEXISTS, "no active transaction"],
|
||||||
|
# DNSSEC errors.
|
||||||
|
[:INVALID_PUBLIC_KEY, "invalid public key"],
|
||||||
|
[:INVALID_PRIVATE_KEY, "invalid private key"],
|
||||||
|
[:INVALID_KEY_ALGORITHM, "invalid key algorithm"],
|
||||||
|
[:INVALID_KEY_SIZE, "invalid key size"],
|
||||||
|
[:INVALID_KEY_ID, "invalid key ID"],
|
||||||
|
[:INVALID_KEY_NAME, "invalid key name"],
|
||||||
|
[:NO_PUBLIC_KEY, "no public key"],
|
||||||
|
[:NO_PRIVATE_KEY, "no private key"],
|
||||||
|
].each do |v|
|
||||||
|
e = nil
|
||||||
|
case v
|
||||||
|
when array_arguments_with_typecheck( Symbol, String)
|
||||||
|
v, e = v
|
||||||
|
when array_arguments_with_typecheck( String, String)
|
||||||
|
v, e = v
|
||||||
|
v = v.to_sym
|
||||||
|
when array_arguments_with_typecheck( Symbol, Integer, String)
|
||||||
|
v, i, e = v
|
||||||
|
when array_arguments_with_typecheck( String, Integer, String)
|
||||||
|
v, i, e = v
|
||||||
|
v = v.to_sym
|
||||||
|
else
|
||||||
|
raise ArgumentError, "[Symbol, String] | [Symbol, Int, String] expected, not #{v}"
|
||||||
|
end
|
||||||
|
cl = Class.new Exception
|
||||||
|
cl.const_set :Key, v
|
||||||
|
cl.const_set :Errno, i
|
||||||
|
cl.const_set :Errstr, e
|
||||||
|
const_set v, cl
|
||||||
|
@num2exc[i] = @key2exc[v] = @err2exc[e] = cl
|
||||||
|
i += 1
|
||||||
|
end
|
||||||
|
end
|
159
lib/knot/interface.rb
Normal file
159
lib/knot/interface.rb
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
require_relative 'protocol'
|
||||||
|
require_relative 'errors'
|
||||||
|
|
||||||
|
module Knot
|
||||||
|
end
|
||||||
|
|
||||||
|
class Knot::Zone
|
||||||
|
attr_reader :protocol, :zone
|
||||||
|
def initialize zone, protocol = nil
|
||||||
|
@protocol = protocol || Protocol.new
|
||||||
|
@zone, @transaction_opened = zone, 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def begin
|
||||||
|
@transaction_opened += 1
|
||||||
|
@protocol.call command: 'zone-begin', zone: @zone if 1 == @transaction_opened
|
||||||
|
end
|
||||||
|
|
||||||
|
def commit
|
||||||
|
@protocol.call command: 'zone-commit', zone: @zone if 1 == @transaction_opened
|
||||||
|
@transaction_opened -= 1 if 0 < @transaction_opened
|
||||||
|
end
|
||||||
|
|
||||||
|
def abort
|
||||||
|
@protocol.call command: 'zone-abort', zone: @zone
|
||||||
|
@transaction_opened = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def transaction
|
||||||
|
self.begin
|
||||||
|
yield self
|
||||||
|
rescue Object
|
||||||
|
self.abort
|
||||||
|
raise
|
||||||
|
ensure
|
||||||
|
self.commit unless $!
|
||||||
|
end
|
||||||
|
|
||||||
|
# zone operation
|
||||||
|
|
||||||
|
def check() @protocol.call command: 'zone-check', zone: @zone end
|
||||||
|
def reload() @protocol.call command: 'zone-reload', zone: @zone end
|
||||||
|
def refresh() @protocol.call command: 'zone-refresh', zone: @zone end
|
||||||
|
def notify() @protocol.call command: 'zone-notify', zone: @zone end
|
||||||
|
def retransfer() @protocol.call command: 'zone-retransfer', zone: @zone end
|
||||||
|
def sign() @protocol.call command: 'zone-sign', zone: @zone end
|
||||||
|
def freeze() @protocol.call command: 'zone-freeze', zone: @zone end
|
||||||
|
def thaw() @protocol.call command: 'zone-thaw', zone: @zone end
|
||||||
|
def status( filter = nil) @protocol.call command: 'zone-status', zone: @zone, filter: filter end
|
||||||
|
|
||||||
|
def stats( modul = nil, counter = nil)
|
||||||
|
@protocol.call command: 'zone-stats', zone: @zone, module: modul, counter: counter
|
||||||
|
end
|
||||||
|
|
||||||
|
# zone manipulation
|
||||||
|
|
||||||
|
def read() @protocol.call command: 'zone-read', zone: @zone end
|
||||||
|
def diff() @protocol.call command: 'zone-diff', zone: @zone end
|
||||||
|
|
||||||
|
# setting record
|
||||||
|
# if data is nil, it will be unset.
|
||||||
|
def set owner, ttl = nil, type, data
|
||||||
|
@protocol.call command: data.nil? ? 'zone-unset' : 'zone-set',
|
||||||
|
zone: @zone, owner: owner, ttl: ttl, type: type, data: data
|
||||||
|
rescue Knot::Errors::EISRECORD, Knot::Errors::ENONODE, Knot::Errors::ENOENT
|
||||||
|
end
|
||||||
|
alias []= set
|
||||||
|
|
||||||
|
def unset owner, type = nil, data = nil
|
||||||
|
@protocol.call command: 'zone-unset',
|
||||||
|
zone: @zone, owner: owner, type: type, data: data
|
||||||
|
rescue Knot::Errors::ENONODE, Knot::Errors::ENOENT
|
||||||
|
end
|
||||||
|
alias delete unset
|
||||||
|
|
||||||
|
def get owner = nil, type = nil
|
||||||
|
@protocol.call command: 'zone-get',
|
||||||
|
zone: @zone, owner: owner, type: type
|
||||||
|
rescue Knot::Errors::ENONODE, Knot::Errors::ENOENT
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
alias [] get
|
||||||
|
end
|
||||||
|
|
||||||
|
class Knot::Conf
|
||||||
|
def initialize protocol = nil
|
||||||
|
@protocol = protocol || Protocol.new
|
||||||
|
@transaction_opened = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def begin
|
||||||
|
@transaction_opened += 1
|
||||||
|
@protocol.call command: 'conf-begin' if 1 == @transaction_opened
|
||||||
|
end
|
||||||
|
|
||||||
|
def commit
|
||||||
|
@protocol.call command: 'conf-commit' if 1 == @transaction_opened
|
||||||
|
@transaction_opened -= 1 if 0 < @transaction_opened
|
||||||
|
end
|
||||||
|
|
||||||
|
def abort
|
||||||
|
@protocol.call command: 'conf-abort'
|
||||||
|
@transaction_opened = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def transaction
|
||||||
|
self.begin
|
||||||
|
yield self
|
||||||
|
rescue Object
|
||||||
|
self.abort
|
||||||
|
raise
|
||||||
|
ensure
|
||||||
|
self.commit
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_item k
|
||||||
|
case k
|
||||||
|
when k
|
||||||
|
case k.keys.sort
|
||||||
|
when %w[section], %w[id section], %w[item section], %w[id item section] then k
|
||||||
|
else raise ArgumentError, "Invalid Item-format"
|
||||||
|
end
|
||||||
|
|
||||||
|
when Array
|
||||||
|
case k.length
|
||||||
|
when 1 then {section: k[0]}
|
||||||
|
when 2 then {section: k[0], item: k[1]}
|
||||||
|
when 3 then {section: k[0], id: k[1], item: k[2]}
|
||||||
|
else raise ArgumentError, "Invalid Item-format"
|
||||||
|
end
|
||||||
|
|
||||||
|
when /\A
|
||||||
|
(?<section> [a-z0-9_-]+ )
|
||||||
|
(?: \[ (?<id> [a-z0-9_.-]+) \] )?
|
||||||
|
(?: \. (?<item>[a-z0-9_-]+) )?
|
||||||
|
\z/xi
|
||||||
|
$~.named_captures.delete_if {|_,v| v.nil? }
|
||||||
|
else raise ArgumentError, "Invalid Item-format"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def set item, value
|
||||||
|
@protocol.call parse_item( item).update( command: 'conf-set', data: value)
|
||||||
|
end
|
||||||
|
alias [] set
|
||||||
|
|
||||||
|
def unset item, value = nil
|
||||||
|
@protocol.call parse_item( item).update( command: 'conf-unset', data: value)
|
||||||
|
end
|
||||||
|
alias delete unset
|
||||||
|
|
||||||
|
def list item = nil
|
||||||
|
@protocol.call (item ? parse_item( item) : {}).update( command: 'conf-list')
|
||||||
|
end
|
||||||
|
|
||||||
|
def read item = nil
|
||||||
|
@protocol.call (item ? parse_item( item) : {}).update( command: 'conf-read')
|
||||||
|
end
|
||||||
|
end
|
188
lib/knot/protocol.rb
Normal file
188
lib/knot/protocol.rb
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
require 'iounpack'
|
||||||
|
require_relative 'errors'
|
||||||
|
|
||||||
|
module Knot
|
||||||
|
class Protocol
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module Knot::Protocol::Type
|
||||||
|
def [] code
|
||||||
|
case code
|
||||||
|
when 0, End then return End
|
||||||
|
when 1, Data then return Data
|
||||||
|
when 2, Extra then return Extra
|
||||||
|
when 3, Block then return Block
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Base
|
||||||
|
def self.expect_data?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
def expect_data?() self.class.expect_data? end
|
||||||
|
def code() self.class.code end
|
||||||
|
end
|
||||||
|
class End < Base
|
||||||
|
def self.code() 0 end
|
||||||
|
end
|
||||||
|
class Data < Base
|
||||||
|
def code() 1 end
|
||||||
|
def expect_data?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
attr_reader :data
|
||||||
|
def initialize data
|
||||||
|
@data = data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
class Extra < Base
|
||||||
|
def code() 2 end
|
||||||
|
def expect_data?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
attr_reader :data
|
||||||
|
def initialize data
|
||||||
|
@data = data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
class Block < Base
|
||||||
|
def code() 3 end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module Knot::Protocol::Idx
|
||||||
|
Idx = [
|
||||||
|
:command, # 10, :CMD, # Control command name.
|
||||||
|
:flags, # 11, :FLAGS, # Control command flags.
|
||||||
|
:error, # 12, :ERROR, # Error message.
|
||||||
|
:section, # 13, :SECTION, # Configuration section name.
|
||||||
|
:item, # 14, :ITEM, # Configuration item name.
|
||||||
|
:id, # 15, :ID, # Congiguration item identifier.
|
||||||
|
:zone, # 16, :ZONE, # Zone name.
|
||||||
|
:owner, # 17, :OWNER, # Zone record owner
|
||||||
|
:ttl, # 18, :TTL, # Zone record TTL.
|
||||||
|
:type, # 19, :TYPE, # Zone record type name.
|
||||||
|
:data, # 1a, :DATA, # Configuration item/zone record data.
|
||||||
|
:filter, # 1b, :FILTER, # An option or a filter for output data processing.
|
||||||
|
]
|
||||||
|
Name = {}
|
||||||
|
Code = {}
|
||||||
|
Idx.each_with_index do |v, i|
|
||||||
|
Code[0x10+i] = v
|
||||||
|
Name[v] = i
|
||||||
|
end
|
||||||
|
def self.[] k
|
||||||
|
case k
|
||||||
|
when Symbol
|
||||||
|
Name[k] or raise Knot::Errors::EINVAL, "Unknown Idx: #{k}"
|
||||||
|
when Integer
|
||||||
|
Idx[k] or raise Knot::Errors::EINVAL, "Unknown Idx: #{k}"
|
||||||
|
else
|
||||||
|
raise ArgumentError, "Unknown Idx-Type: #{k}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Knot::Protocol
|
||||||
|
attr_reader :sock, :conf, :zones
|
||||||
|
attr_accessor :debug
|
||||||
|
|
||||||
|
def initialize path_or_sock = nil
|
||||||
|
case path_or_sock
|
||||||
|
when String, Pathname
|
||||||
|
@sock = UNIXSocket.new path_or_sock.to_s
|
||||||
|
when Socket
|
||||||
|
@sock = path_or_sock
|
||||||
|
when nil
|
||||||
|
@sock = UNIXSocket.new '/run/knot/knot.sock'
|
||||||
|
end
|
||||||
|
@debug = false
|
||||||
|
@conf = Knot::Conf.new self
|
||||||
|
@zones = Hash.new {|h, zone| h[zone] = Knot::Zone.new zone, self }
|
||||||
|
end
|
||||||
|
|
||||||
|
def snd sock: nil, **data
|
||||||
|
rsock = sock || @sock
|
||||||
|
s = ''
|
||||||
|
sock = StringIO.new s
|
||||||
|
sock.write [1].pack( 'c')
|
||||||
|
data[:flags] ||= ''
|
||||||
|
Idx::Idx.each_with_index do |n, i|
|
||||||
|
v = data[n]&.to_s
|
||||||
|
sock.write [0x10+i, v.size, v].pack( 'c na*') if v
|
||||||
|
end
|
||||||
|
sock.write [3].pack( 'c')
|
||||||
|
sock.flush
|
||||||
|
STDERR.puts( {data: data, _: s}.inspect) if @debug
|
||||||
|
rsock.write s
|
||||||
|
rsock.flush
|
||||||
|
end
|
||||||
|
|
||||||
|
class RecordIO
|
||||||
|
attr_reader :str
|
||||||
|
|
||||||
|
def initialize sock, str = nil
|
||||||
|
@str, @sock = str || '', sock
|
||||||
|
end
|
||||||
|
|
||||||
|
def unpack pattern
|
||||||
|
IOUnpack.new(pattern).unpack self
|
||||||
|
end
|
||||||
|
|
||||||
|
def unpack1 pattern
|
||||||
|
IOUnpack.new(pattern).unpack1 self
|
||||||
|
end
|
||||||
|
|
||||||
|
def read n
|
||||||
|
s = @sock.read n
|
||||||
|
@str.insert -1, s
|
||||||
|
s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def rcv sock: nil
|
||||||
|
ret, r = [], nil
|
||||||
|
sock = sock || @sock
|
||||||
|
sock = RecordIO.new sock if @debug
|
||||||
|
loop do
|
||||||
|
t = sock.unpack1 'c'
|
||||||
|
case t
|
||||||
|
when 0, 3
|
||||||
|
return ret
|
||||||
|
when 1, 2
|
||||||
|
type = t
|
||||||
|
ret.push( r = {})
|
||||||
|
else
|
||||||
|
raise Knot::Errors::EINVAL, "Missing Type before: #{t}" if ret.empty?
|
||||||
|
i = Idx::Idx[t - 0x10] or raise Knot::Errors::EINVAL, "Unknown index: #{t-0x10}"
|
||||||
|
l = sock.unpack1 'n'
|
||||||
|
r[i] = sock.read( l)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
STDERR.puts( {rcvd: ret, read: sock.str}.inspect) if @debug
|
||||||
|
ret
|
||||||
|
end
|
||||||
|
|
||||||
|
def call sock: nil, **data
|
||||||
|
snd sock: sock, **data
|
||||||
|
rcv( sock: sock).each do |r|
|
||||||
|
if r[:error]
|
||||||
|
if e = Knot::Errors.err2exc[r[:error]]
|
||||||
|
raise e, r[:error]
|
||||||
|
end
|
||||||
|
raise Knot::Error, r[:error]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def zone( zone) @zones[zone.to_s.to_sym] end
|
||||||
|
|
||||||
|
def conf_set( **opts) call opts.update( command: 'conf-set') end
|
||||||
|
def conf_unset( **opts) call opts.update( command: 'conf-set') end
|
||||||
|
|
||||||
|
def zone_set( **opts) call opts.update( command: 'zone-set') end
|
||||||
|
def zone_unset( **opts) call opts.update( command: 'zone-unset') end
|
||||||
|
def zone_get( **opts) call opts.update( command: 'zone-get') end
|
||||||
|
end
|
3
lib/knot/version.rb
Normal file
3
lib/knot/version.rb
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module Knot
|
||||||
|
VERSION = "0.1.0"
|
||||||
|
end
|
Loading…
Reference in a new issue