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