BIND 10 trac1308, updated. 862b0e38047101afef0f6d81ba3f0d74c9a51ea5 Merge branch 'master' into trac1308
BIND 10 source code commits
bind10-changes at lists.isc.org
Tue Dec 6 09:15:55 UTC 2011
The branch, trac1308 has been updated
via 862b0e38047101afef0f6d81ba3f0d74c9a51ea5 (commit)
via cf5e4481b1f1fb00e9897e4cd0527a9a707c4a63 (commit)
via 7a1997b2d44d05c179adecad0bf68c1f7c19935f (commit)
via e580e44a38c8dcf02e6ff40ba5977f18c8200572 (commit)
via 0c53ade72d9589d3d521937652af20f9d7a20f8e (commit)
via cc20ff993da1ddb1c6e8a98370438b45a2be9e0a (commit)
via f9cbe6fb6e0a3d3dc03218429f530e6c01920169 (commit)
via 18128a99fd70d66eb09312dd8dfa0f0521033f97 (commit)
via e533dc83ccb7bf541e53f753c28a52248d7b195b (commit)
via 2ae72d76c74f61a67590722c73ebbf631388acbd (commit)
via 1ecbd2b16b44b6439030fd245f951fe5a538ecc5 (commit)
via 83ce13c2d85068a1bec015361e4ef8c35590a5d0 (commit)
via 5f34cb40976859771ab996113f78937310e7bda5 (commit)
via 8ee52fc304388aef162a819e2e59066bb70f0618 (commit)
via 3fdce88046bdad392bd89ea656ec4ac3c858ca2f (commit)
via 6690b8985d656aba3f25082cb62c9c02e5ad5a0b (commit)
via 144549c04afdc36a98c7530eaebafb2b3d38d545 (commit)
via 4464612807e6c4bd120298ca105b0503af0d3110 (commit)
via 0b5da8bd0800bfa3744e23c367cee2c38de7a497 (commit)
via f06cabd31d8e43781e4e32bdfdf24c78931d3ca8 (commit)
via 0e945f09e0e127e5097c32e5c84d96e34a18b3b6 (commit)
via c9be5877151f0564725d1cd9a20fe393fe7b422f (commit)
via 560122414abc11fa2a39331734c607cc37a4e76c (commit)
via 5468c7defd530b29696108cbda6d278b14be351b (commit)
via 64853ae0cca2070a0536ee6f499084c8a9017fa2 (commit)
via 1d8a592d1301b7e3a39c88ce1e001122db125307 (commit)
via 9682bf18607745c83437cd4592d3289e68410772 (commit)
via 537af1705fc5c1695b4b601571f65ead81dc1289 (commit)
via ada65c7115b9840f509f2b0c640368735fe69520 (commit)
via 966fdcc69001cd2562ca96b392b9a45e7c336224 (commit)
via c789138250b33b6b08262425a08a2a0469d90433 (commit)
via e665914a467810569a22e093a56ff5c711179143 (commit)
via 5b64e839be2906b8950f5b1e42a3fadd72fca033 (commit)
via 4dc636c8d46d857f617bfef2ae9444dce438cff4 (commit)
via eb6053d466fcea08fa66205d598d316e550863c8 (commit)
via 42d4a37a121ea7df3440268fe15995267fb66b12 (commit)
via 1b186f0a6fc242fa6dff08944ef43b60010d3631 (commit)
via b6568546ccdac044fd30200a54708f9418e7af9d (commit)
via de9532ce586ac69ff58dad2096f23db0cb062639 (commit)
via a81436b0c8f9c3974ec3373f586c45e2cf03cb64 (commit)
via 07b884ef0f72044fa5a5fd661ab068794ff68ca6 (commit)
via 207038a3ff7503cd2b2ab44238c71da55912bb4a (commit)
via 4ca30d27a1149bf5c445f382c4767b5c4e168d95 (commit)
via f6def2435fe72e00a782244461e8a186a4a23e63 (commit)
via 75fc5817606b892c8eeb4964dc2f3d2f044f5491 (commit)
via 3e1a6afcabbef24f9501d8f4e3ed27d824db4309 (commit)
via 5dc772648fb9779359b4d409086d55745afccad9 (commit)
via 36dc8dd6f15a42f401ffa32829ed7c436e529eb3 (commit)
via c5117dc4d2fd89f1a66849713c6a3cd51735699f (commit)
via 5d7004d0ac4fe553a61fd2eb99a8af3eb7324956 (commit)
via fc0fe98a085ece85e143188c5647740f95d347bc (commit)
via 456933355bf3bc2db5a6c52ba4dc6d8e826ce6e1 (commit)
from 11885ff9e91c98ff0c4e93d81fc2b3d47a02090d (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 862b0e38047101afef0f6d81ba3f0d74c9a51ea5
Merge: 11885ff9e91c98ff0c4e93d81fc2b3d47a02090d cf5e4481b1f1fb00e9897e4cd0527a9a707c4a63
Author: Xie Jiagui <xiejiagui at cnnic.cn>
Date: Tue Dec 6 17:15:01 2011 +0800
Merge branch 'master' into trac1308
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 49 ++++
configure.ac | 3 +
src/bin/bind10/bind10_src.py.in | 43 ---
src/bin/bind10/bob.spec | 4 +-
src/bin/bind10/tests/bind10_test.py.in | 9 +-
src/bin/xfrin/tests/Makefile.am | 1 +
src/bin/xfrin/tests/xfrin_test.py | 8 +-
src/bin/xfrout/tests/xfrout_test.py.in | 182 ++++++++++++-
src/bin/xfrout/xfrout.py.in | 58 +++-
src/lib/asiolink/Makefile.am | 3 +
src/lib/datasrc/Makefile.am | 13 +-
src/lib/datasrc/database.cc | 12 +-
.../datasrc_config.h.pre.in} | 28 +--
src/lib/datasrc/factory.cc | 53 ++++-
src/lib/datasrc/factory.h | 11 +-
src/lib/datasrc/tests/Makefile.am | 25 ++-
src/lib/datasrc/tests/factory_unittest.cc | 65 +++++
src/lib/dhcp/Makefile.am | 1 +
src/lib/dhcp/libdhcp.cc | 12 +-
src/lib/dhcp/option.cc | 31 +--
src/lib/dhcp/option.h | 27 +--
src/lib/dhcp/option4_addrlst.cc | 135 ++++++++++
src/lib/dhcp/option4_addrlst.h | 167 ++++++++++++
src/lib/dhcp/option6_addrlst.cc | 6 +-
src/lib/dhcp/option6_addrlst.h | 19 +-
src/lib/dhcp/option6_ia.cc | 6 +-
src/lib/dhcp/option6_ia.h | 2 +-
src/lib/dhcp/option6_iaaddr.cc | 4 +-
src/lib/dhcp/option6_iaaddr.h | 3 +-
src/lib/dhcp/pkt4.cc | 6 +-
src/lib/dhcp/tests/Makefile.am | 1 +
src/lib/dhcp/tests/option4_addrlst_unittest.cc | 273 +++++++++++++++++++
src/lib/dhcp/tests/pkt4_unittest.cc | 4 +-
src/lib/dns/Makefile.am | 3 +
src/lib/dns/python/Makefile.am | 1 +
src/lib/dns/python/pydnspp.cc | 17 ++
src/lib/dns/python/rdata_python.cc | 210 ++++++++++-----
src/lib/dns/python/rrset_python.cc | 97 ++++---
src/lib/dns/python/serial_python.cc | 281 ++++++++++++++++++++
.../python/{opcode_python.h => serial_python.h} | 32 ++--
src/lib/dns/python/tests/Makefile.am | 1 +
src/lib/dns/python/tests/rdata_python_test.py | 8 +
src/lib/dns/python/tests/serial_python_test.py | 109 ++++++++
src/lib/dns/rdata/generic/soa_6.cc | 4 +-
src/lib/dns/rdata/generic/soa_6.h | 3 +-
src/lib/dns/serial.cc | 76 ++++++
src/lib/dns/serial.h | 155 +++++++++++
src/lib/dns/tests/Makefile.am | 1 +
src/lib/dns/tests/rdata_soa_unittest.cc | 2 +-
src/lib/dns/tests/serial_unittest.cc | 179 +++++++++++++
src/lib/python/isc/bind10/special_component.py | 13 -
src/lib/python/isc/bind10/tests/component_test.py | 4 -
src/lib/python/isc/datasrc/tests/Makefile.am | 1 +
src/lib/python/isc/notify/tests/Makefile.am | 1 +
src/lib/python/isc/testutils/rrset_utils.py | 13 +
tests/lettuce/features/terrain/bind10_control.py | 7 +-
tests/lettuce/features/xfrin_bind10.feature | 1 +
57 files changed, 2150 insertions(+), 333 deletions(-)
copy src/lib/{python/isc/datasrc/client_python.h => datasrc/datasrc_config.h.pre.in} (60%)
create mode 100644 src/lib/dhcp/option4_addrlst.cc
create mode 100644 src/lib/dhcp/option4_addrlst.h
create mode 100644 src/lib/dhcp/tests/option4_addrlst_unittest.cc
create mode 100644 src/lib/dns/python/serial_python.cc
copy src/lib/dns/python/{opcode_python.h => serial_python.h} (66%)
create mode 100644 src/lib/dns/python/tests/serial_python_test.py
create mode 100644 src/lib/dns/serial.cc
create mode 100644 src/lib/dns/serial.h
create mode 100644 src/lib/dns/tests/serial_unittest.cc
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index eaec9e9..e4b4405 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,52 @@
+337. [func] tomek
+ libdhcp++: Support for DHCPv4 option that can store a single
+ address or a list of IPv4 addresses added. Support for END option
+ added.
+ (Trac #1350, git cc20ff993da1ddb1c6e8a98370438b45a2be9e0a)
+
+336. [func] jelte
+ libdns++ (and its python wrapper) now includes a class Serial, for
+ SOA SERIAL comparison and addition. Operations on instances of this
+ class follow the specification from RFC 1982.
+ Rdata::SOA::getSerial() now returns values of this type (and not
+ uint32_t).
+ (Trac #1278, git 2ae72d76c74f61a67590722c73ebbf631388acbd)
+
+335. [bug]* jelte
+ The DataSourceClientContainer class that dynamically loads
+ datasource backend libraries no longer provides just a .so file name
+ to its call to dlopen(), but passes it an absolute path. This means
+ that it is no longer an system implementation detail that depends on
+ [DY]LD_LIBRARY_PATH which file is chosen, should there be multiple
+ options (for instance, when test-running a new build while a
+ different version is installed).
+ These loadable libraries are also no longer installed in the default
+ library path, but in a subdirectory of the libexec directory of the
+ target ($prefix/libexec/[version]/backends).
+ This also removes the need to handle b10-xfin and b10-xfrout as
+ 'special' hardcoded components, and they are now started as regular
+ components as dictated by the configuration of the boss process.
+ (Trac #1292, git 83ce13c2d85068a1bec015361e4ef8c35590a5d0)
+
+334. [bug] jinmei
+ b10-xfrout could potentially create an overflow response message
+ (exceeding the 64KB max) or could create unnecessarily small
+ messages. The former was actually unlikely to happen due to the
+ effect of name compression, and the latter was marginal and at least
+ shouldn't cause an interoperability problem, but these were still
+ potential problems and were fixed.
+ (Trac #1389, git 3fdce88046bdad392bd89ea656ec4ac3c858ca2f)
+
+333. [bug] dvv
+ Solaris needs "-z now" to force non-lazy binding and prevent g++ static
+ initialization code from deadlocking.
+ (Trac #1439, git c789138250b33b6b08262425a08a2a0469d90433)
+
+332. [bug] vorner
+ C++ exceptions in the isc.dns.Rdata wrapper are now converted
+ to python ones instead of just aborting the interpretter.
+ (Trac #1407, git 5b64e839be2906b8950f5b1e42a3fadd72fca033)
+
bind10-devel-20111128 released on November 28, 2011
331. [bug] shane
diff --git a/configure.ac b/configure.ac
index 26c1e34..917a32d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -96,6 +96,8 @@ case "$host" in
# Solaris requires special definitions to get some standard libraries
# (e.g. getopt(3)) available with common used header files.
CPPFLAGS="$CPPFLAGS -D_XPG4_2 -D__EXTENSIONS__"
+ # "now" binding is necessary to prevent deadlocks in C++ static initialization code
+ LDFLAGS="$LDFLAGS -z now"
;;
*-apple-darwin*)
# libtool doesn't work perfectly with Darwin: libtool embeds the
@@ -990,6 +992,7 @@ AC_OUTPUT([doc/version.ent
src/lib/python/bind10_config.py
src/lib/cc/session_config.h.pre
src/lib/cc/tests/session_unittests_config.h
+ src/lib/datasrc/datasrc_config.h.pre
src/lib/log/tests/console_test.sh
src/lib/log/tests/destination_test.sh
src/lib/log/tests/init_logger_test.sh
diff --git a/src/bin/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in
index 13cd3e3..f9816fb 100755
--- a/src/bin/bind10/bind10_src.py.in
+++ b/src/bin/bind10/bind10_src.py.in
@@ -574,33 +574,6 @@ class BoB:
# ... and start
return self.start_process("b10-resolver", resargs, self.c_channel_env)
- def __ld_path_hack(self):
- # XXX: a quick-hack workaround. xfrin/out will implicitly use
- # dynamically loadable data source modules, which will be installed in
- # $(libdir).
- # On some OSes (including MacOS X and *BSDs) the main process (python)
- # cannot find the modules unless they are located in a common shared
- # object path or a path in the (DY)LD_LIBRARY_PATH. We should seek
- # a cleaner solution, but for a short term workaround we specify the
- # path here, unconditionally, and without even bothering which
- # environment variable should be used.
- #
- # We reuse the ADD_LIBEXEC_PATH variable to see whether we need to
- # do this, as the conditions that make this workaround needed are
- # the same as for the libexec path addition
- # TODO: Once #1292 is finished, remove this method and the special
- # component, use it as normal component.
- env = dict(self.c_channel_env)
- if ADD_LIBEXEC_PATH:
- cur_path = os.getenv('DYLD_LIBRARY_PATH')
- cur_path = '' if cur_path is None else ':' + cur_path
- env['DYLD_LIBRARY_PATH'] = "@@LIBDIR@@" + cur_path
-
- cur_path = os.getenv('LD_LIBRARY_PATH')
- cur_path = '' if cur_path is None else ':' + cur_path
- env['LD_LIBRARY_PATH'] = "@@LIBDIR@@" + cur_path
- return env
-
def start_cmdctl(self):
"""
Starts the command control process
@@ -613,22 +586,6 @@ class BoB:
return self.start_process("b10-cmdctl", args, self.c_channel_env,
self.cmdctl_port)
- def start_xfrin(self):
- # Set up the command arguments.
- args = ['b10-xfrin']
- if self.verbose:
- args += ['-v']
-
- return self.start_process("b10-xfrin", args, self.__ld_path_hack())
-
- def start_xfrout(self):
- # Set up the command arguments.
- args = ['b10-xfrout']
- if self.verbose:
- args += ['-v']
-
- return self.start_process("b10-xfrout", args, self.__ld_path_hack())
-
def start_all_components(self):
"""
Starts up all the components. Any exception generated during the
diff --git a/src/bin/bind10/bob.spec b/src/bin/bind10/bob.spec
index 4267b70..adc9798 100644
--- a/src/bin/bind10/bob.spec
+++ b/src/bin/bind10/bob.spec
@@ -14,8 +14,8 @@
"priority": 5,
"kind": "dispensable"
},
- "b10-xfrin": { "special": "xfrin", "kind": "dispensable" },
- "b10-xfrout": { "special": "xfrout", "kind": "dispensable" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
"b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
"b10-stats": { "address": "Stats", "kind": "dispensable" },
"b10-stats-httpd": {
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
index b7b741c..c917d33 100644
--- a/src/bin/bind10/tests/bind10_test.py.in
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -268,7 +268,9 @@ class MockBob(BoB):
'b10-stats-httpd': self.start_stats_httpd,
'b10-cmdctl': self.start_cmdctl,
'b10-dhcp6': self.start_dhcp6,
- 'b10-dhcp4': self.start_dhcp4 }
+ 'b10-dhcp4': self.start_dhcp4,
+ 'b10-xfrin': self.start_xfrin,
+ 'b10-xfrout': self.start_xfrout }
return procmap[name]()
def start_xfrout(self):
@@ -463,8 +465,9 @@ class TestStartStopProcessesBob(unittest.TestCase):
if start_auth:
config['b10-auth'] = { 'kind': 'needed', 'special': 'auth' }
config['b10-xfrout'] = { 'kind': 'dispensable',
- 'special': 'xfrout' }
- config['b10-xfrin'] = { 'kind': 'dispensable', 'special': 'xfrin' }
+ 'address': 'Xfrout' }
+ config['b10-xfrin'] = { 'kind': 'dispensable',
+ 'address': 'Xfrin' }
config['b10-zonemgr'] = { 'kind': 'dispensable',
'address': 'Zonemgr' }
if start_resolver:
diff --git a/src/bin/xfrin/tests/Makefile.am b/src/bin/xfrin/tests/Makefile.am
index cffafe1..cba98ae 100644
--- a/src/bin/xfrin/tests/Makefile.am
+++ b/src/bin/xfrin/tests/Makefile.am
@@ -27,5 +27,6 @@ endif
PYTHONPATH=$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/bin/xfrin:$(COMMON_PYTHON_PATH) \
TESTDATASRCDIR=$(abs_top_srcdir)/src/bin/xfrin/tests/testdata/ \
TESTDATAOBJDIR=$(abs_top_builddir)/src/bin/xfrin/tests/testdata/ \
+ B10_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index 3c41110..00e42be 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -17,7 +17,6 @@ import unittest
import re
import shutil
import socket
-import sqlite3
import sys
import io
from isc.testutils.tsigctx_mock import MockTSIGContext
@@ -25,6 +24,13 @@ from xfrin import *
import xfrin
from isc.xfrin.diff import Diff
import isc.log
+# If we use any python library that is basically a wrapper for
+# a library we use as well (like sqlite3 in our datasources),
+# we must make sure we import ours first; If we have special
+# rpath or libtool rules to pick the correct version, python might
+# choose the wrong one first, if those rules aren't hit first.
+# This would result in missing symbols later.
+import sqlite3
#
# Commonly used (mostly constant) test parameters
diff --git a/src/bin/xfrout/tests/xfrout_test.py.in b/src/bin/xfrout/tests/xfrout_test.py.in
index 37e8993..8373e15 100644
--- a/src/bin/xfrout/tests/xfrout_test.py.in
+++ b/src/bin/xfrout/tests/xfrout_test.py.in
@@ -67,10 +67,12 @@ class MySocket():
self.sendqueue = self.sendqueue[size:]
return result
- def read_msg(self, parse_options=Message.PARSE_DEFAULT):
+ def read_msg(self, parse_options=Message.PARSE_DEFAULT, need_len=False):
sent_data = self.readsent()
get_msg = Message(Message.PARSE)
get_msg.from_wire(bytes(sent_data[2:]), parse_options)
+ if need_len:
+ return (get_msg, len(sent_data) - 2)
return get_msg
def clear_send(self):
@@ -863,7 +865,150 @@ class TestXfroutSession(TestXfroutSessionBase):
self.assertEqual(len(expected_records), len(actual_records))
for (expected_rr, actual_rr) in zip(expected_records, actual_records):
- self.assertTrue(expected_rr, actual_rr)
+ self.assertTrue(rrsets_equal(expected_rr, actual_rr))
+
+ def test_reply_xfrout_query_axfr_maxlen(self):
+ # The test RR(set) has the length of 65535 - 12 (size of hdr) bytes:
+ # owner name = 1 (root), fixed fields (type,class,TTL,RDLEN) = 10
+ # RDATA = 65512 (= 65535 - 12 - 1 - 10)
+ self.xfrsess._soa = self.soa_rrset
+ test_rr = create_generic(Name('.'), 65512)
+ self.xfrsess._iterator = [self.soa_rrset, test_rr]
+ self.xfrsess._reply_xfrout_query(self.getmsg(), self.sock)
+ # The first message should contain the beginning SOA, and only that RR
+ r = self.sock.read_msg()
+ self.assertEqual(1, r.get_rr_count(Message.SECTION_ANSWER))
+ self.assertTrue(rrsets_equal(self.soa_rrset,
+ r.get_section(Message.SECTION_ANSWER)[0]))
+ # The second message should contain the beginning SOA, and only that RR
+ # The wire format data should have the possible maximum size.
+ r, rlen = self.sock.read_msg(need_len=True)
+ self.assertEqual(65535, rlen)
+ self.assertEqual(1, r.get_rr_count(Message.SECTION_ANSWER))
+ self.assertTrue(rrsets_equal(test_rr,
+ r.get_section(Message.SECTION_ANSWER)[0]))
+ # The third message should contain the ending SOA, and only that RR
+ r = self.sock.read_msg()
+ self.assertEqual(1, r.get_rr_count(Message.SECTION_ANSWER))
+ self.assertTrue(rrsets_equal(self.soa_rrset,
+ r.get_section(Message.SECTION_ANSWER)[0]))
+
+ # there should be no more message
+ self.assertEqual(0, len(self.sock.sendqueue))
+
+ def maxlen_test_common_setup(self, tsig=False):
+ '''Common initialization for some of the tests below
+
+ For those tests we use '.' for all owner names and names in RDATA
+ to avoid having unexpected results due to compression. It returns
+ the created SOA for convenience.
+
+ If tsig is True, also setup TSIG (mock) context. In our test cases
+ the size of the TSIG RR is 81 bytes (key name = example.com,
+ algorithm = hmac-md5)
+
+ '''
+ soa = RRset(Name('.'), RRClass.IN(), RRType.SOA(), RRTTL(3600))
+ soa.add_rdata(Rdata(RRType.SOA(), RRClass.IN(), '. . 0 0 0 0 0'))
+ self.mdata = self.create_request_data(zone_name=Name('.'))
+ self.xfrsess._soa = soa
+ if tsig:
+ self.xfrsess._tsig_ctx = \
+ self.create_mock_tsig_ctx(TSIGError.NOERROR)
+ self.xfrsess._tsig_len = 81
+ return soa
+
+ def maxlen_test_common_checks(self, soa_rr, test_rr, expected_n_rr):
+ '''A set of common assertion checks for some tests below.
+
+ In all cases two AXFR response messages should have been created.
+ expected_n_rr is a list of two elements, each specifies the expected
+ number of answer RRs for each message: expected_n_rr[0] is the expected
+ number of the first answer RRs; expected_n_rr[1] is the expected number
+ of the second answer RRs. The message that contains two RRs should
+ have the maximum possible wire length (65535 bytes). And, in all
+ cases, the resulting RRs should be in the order of SOA, another RR,
+ SOA.
+
+ '''
+ # Check the first message
+ r, rlen = self.sock.read_msg(need_len=True)
+ if expected_n_rr[0] == 2:
+ self.assertEqual(65535, rlen)
+ self.assertEqual(expected_n_rr[0],
+ r.get_rr_count(Message.SECTION_ANSWER))
+ actual_rrs = r.get_section(Message.SECTION_ANSWER)[:]
+
+ # Check the second message
+ r, rlen = self.sock.read_msg(need_len=True)
+ if expected_n_rr[1] == 2:
+ self.assertEqual(65535, rlen)
+ self.assertEqual(expected_n_rr[1],
+ r.get_rr_count(Message.SECTION_ANSWER))
+ actual_rrs.extend(r.get_section(Message.SECTION_ANSWER))
+ for (expected_rr, actual_rr) in zip([soa_rr, test_rr, soa_rr],
+ actual_rrs):
+ self.assertTrue(rrsets_equal(expected_rr, actual_rr))
+
+ # there should be no more message
+ self.assertEqual(0, len(self.sock.sendqueue))
+
+ def test_reply_xfrout_query_axfr_maxlen_with_soa(self):
+ # Similar to the 'maxlen' test, but the first message should be
+ # able to contain both SOA and the large RR.
+ soa = self.maxlen_test_common_setup()
+
+ # The first message will contain the question (5 bytes), so the
+ # test RDATA should allow a room for that.
+ test_rr = create_generic(Name('.'), 65512 - 5 - get_rrset_len(soa))
+ self.xfrsess._iterator = [soa, test_rr]
+ self.xfrsess._reply_xfrout_query(self.getmsg(), self.sock)
+ self.maxlen_test_common_checks(soa, test_rr, [2, 1])
+
+ def test_reply_xfrout_query_axfr_maxlen_with_soa_with_tsig(self):
+ # Similar to the previous case, but with TSIG (whose size is 81 bytes).
+ soa = self.maxlen_test_common_setup(True)
+ test_rr = create_generic(Name('.'), 65512 - 5 - 81 -
+ get_rrset_len(soa))
+ self.xfrsess._iterator = [soa, test_rr]
+ self.xfrsess._reply_xfrout_query(self.getmsg(), self.sock)
+ self.maxlen_test_common_checks(soa, test_rr, [2, 1])
+
+ def test_reply_xfrout_query_axfr_maxlen_with_endsoa(self):
+ # Similar to the max w/ soa test, but the first message cannot contain
+ # both SOA and the long RR due to the question section. The second
+ # message should be able to contain both.
+ soa = self.maxlen_test_common_setup()
+ test_rr = create_generic(Name('.'), 65512 - get_rrset_len(soa))
+ self.xfrsess._iterator = [soa, test_rr]
+ self.xfrsess._reply_xfrout_query(self.getmsg(), self.sock)
+ self.maxlen_test_common_checks(soa, test_rr, [1, 2])
+
+ def test_reply_xfrout_query_axfr_maxlen_with_endsoa_with_tsig(self):
+ # Similar to the previous case, but with TSIG.
+ soa = self.maxlen_test_common_setup(True)
+ test_rr = create_generic(Name('.'), 65512 - 81 - get_rrset_len(soa))
+ self.xfrsess._iterator = [soa, test_rr]
+ self.xfrsess._reply_xfrout_query(self.getmsg(), self.sock)
+ self.maxlen_test_common_checks(soa, test_rr, [1, 2])
+
+ def test_reply_xfrout_query_axfr_toobigdata(self):
+ # Similar to the 'maxlen' test, but the RR doesn't even fit in a
+ # single message.
+ self.xfrsess._soa = self.soa_rrset
+ test_rr = create_generic(Name('.'), 65513) # 1 byte larger than 'max'
+ self.xfrsess._iterator = [self.soa_rrset, test_rr]
+ # the reply method should fail with exception
+ self.assertRaises(XfroutSessionError, self.xfrsess._reply_xfrout_query,
+ self.getmsg(), self.sock)
+ # The first message should still have been sent and contain the
+ # beginning SOA, and only that RR
+ r = self.sock.read_msg()
+ self.assertEqual(1, r.get_rr_count(Message.SECTION_ANSWER))
+ self.assertTrue(rrsets_equal(self.soa_rrset,
+ r.get_section(Message.SECTION_ANSWER)[0]))
+ # And there should have been no other messages sent
+ self.assertEqual(0, len(self.sock.sendqueue))
def test_reply_xfrout_query_ixfr_soa_only(self):
# Creating an IXFR response that contains only one RR, which is the
@@ -875,7 +1020,8 @@ class TestXfroutSession(TestXfroutSessionBase):
reply_msg = self.sock.read_msg(Message.PRESERVE_ORDER)
answer = reply_msg.get_section(Message.SECTION_ANSWER)
self.assertEqual(1, len(answer))
- self.assertTrue(create_soa(SOA_CURRENT_VERSION), answer[0])
+ self.assertTrue(rrsets_equal(create_soa(SOA_CURRENT_VERSION),
+ answer[0]))
class TestXfroutSessionWithSQLite3(TestXfroutSessionBase):
'''Tests for XFR-out sessions using an SQLite3 DB.
@@ -899,14 +1045,23 @@ class TestXfroutSessionWithSQLite3(TestXfroutSessionBase):
# This zone contains two A RRs for the same name with different TTLs.
# These TTLs should be preseved in the AXFR stream.
actual_records = response.get_section(Message.SECTION_ANSWER)
- expected_records = [create_soa(2011112001),
- create_ns(self.ns_name),
- create_a(Name(self.ns_name), '192.0.2.1', 3600),
- create_a(Name(self.ns_name), '192.0.2.2', 7200),
- create_soa(2011112001)]
- self.assertEqual(len(expected_records), len(actual_records))
- for (expected_rr, actual_rr) in zip(expected_records, actual_records):
- self.assertTrue(expected_rr, actual_rr)
+ self.assertEqual(5, len(actual_records))
+ # The first and last RR should be the expected SOA
+ expected_soa = create_soa(2011112001)
+ self.assertTrue(rrsets_equal(expected_soa, actual_records[0]))
+ self.assertTrue(rrsets_equal(expected_soa, actual_records[-1]))
+
+ # The ordering of the intermediate RRs can differ depending on the
+ # internal details of the SQLite3 library, so we sort them by a simple
+ # rule sufficient for the purpose here, and then compare them.
+ expected_others = [create_ns(self.ns_name),
+ create_a(Name(self.ns_name), '192.0.2.1', 3600),
+ create_a(Name(self.ns_name), '192.0.2.2', 7200)]
+ keyfn = lambda x: (x.get_type(), x.get_ttl())
+ for (expected_rr, actual_rr) in zip(sorted(expected_others, key=keyfn),
+ sorted(actual_records[1:4],
+ key=keyfn)):
+ self.assertTrue(rrsets_equal(expected_rr, actual_rr))
def test_axfr_normal_session(self):
XfroutSession._handle(self.xfrsess)
@@ -945,7 +1100,7 @@ class TestXfroutSessionWithSQLite3(TestXfroutSessionBase):
create_soa(2011112001)]
self.assertEqual(len(expected_records), len(actual_records))
for (expected_rr, actual_rr) in zip(expected_records, actual_records):
- self.assertTrue(expected_rr, actual_rr)
+ self.assertTrue(rrsets_equal(expected_rr, actual_rr))
def test_ixfr_soa_only(self):
# The requested SOA serial is the latest one. The response should
@@ -956,7 +1111,8 @@ class TestXfroutSessionWithSQLite3(TestXfroutSessionBase):
response = self.sock.read_msg(Message.PRESERVE_ORDER);
answers = response.get_section(Message.SECTION_ANSWER)
self.assertEqual(1, len(answers))
- self.assertTrue(create_soa(SOA_CURRENT_VERSION), answers[0])
+ self.assertTrue(rrsets_equal(create_soa(SOA_CURRENT_VERSION),
+ answers[0]))
class MyUnixSockServer(UnixSockServer):
def __init__(self):
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index d450138..310a0aa 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -66,6 +66,11 @@ class XfroutConfigError(Exception):
"""
pass
+class XfroutSessionError(Exception):
+ '''An exception raised for some unexpected events during an xfrout session.
+ '''
+ pass
+
def init_paths():
global SPECFILE_PATH
global AUTH_SPECFILE_PATH
@@ -93,7 +98,8 @@ init_paths()
SPECFILE_LOCATION = SPECFILE_PATH + "/xfrout.spec"
AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + os.sep + "auth.spec"
VERBOSE_MODE = False
-XFROUT_MAX_MESSAGE_SIZE = 65535
+XFROUT_DNS_HEADER_SIZE = 12 # protocol constant
+XFROUT_MAX_MESSAGE_SIZE = 65535 # ditto
# borrowed from xfrin.py @ #1298. We should eventually unify it.
def format_zone_str(zone_name, zone_class):
@@ -534,32 +540,44 @@ class XfroutSession():
def _send_message_with_last_soa(self, msg, sock_fd, rrset_soa,
message_upper_len):
- '''Add the SOA record to the end of message. If it can't be
- added, a new message should be created to send out the last soa .
+ '''Add the SOA record to the end of message.
+
+ If it would exceed the maximum allowable size of a message, a new
+ message will be created to send out the last SOA.
+
+ We assume a message with a single SOA can always fit the buffer
+ with or without TSIG. In theory this could be wrong if TSIG is
+ stupidly large, but in practice this assumption should be reasonable.
'''
- if (message_upper_len + self._tsig_len + get_rrset_len(rrset_soa) >=
- XFROUT_MAX_MESSAGE_SIZE):
+ if message_upper_len + get_rrset_len(rrset_soa) > \
+ XFROUT_MAX_MESSAGE_SIZE:
self._send_message(sock_fd, msg, self._tsig_ctx)
msg = self._clear_message(msg)
- # If tsig context exist, sign the last packet
msg.add_rrset(Message.SECTION_ANSWER, rrset_soa)
self._send_message(sock_fd, msg, self._tsig_ctx)
def _reply_xfrout_query(self, msg, sock_fd):
- #TODO, there should be a better way to insert rrset.
msg.make_response()
msg.set_header_flag(Message.HEADERFLAG_AA)
+ # Reserved space for the fixed header size, the size of the question
+ # section, and TSIG size (when included). The size of the question
+ # section is the sum of the qname length and the size of the
+ # fixed-length fields (type and class, 2 bytes each).
+ message_upper_len = XFROUT_DNS_HEADER_SIZE + \
+ msg.get_question()[0].get_name().get_length() + 4 + \
+ self._tsig_len
# If the iterator is None, we are responding to IXFR with a single
# SOA RR.
if self._iterator is None:
- self._send_message_with_last_soa(msg, sock_fd, self._soa, 0)
+ self._send_message_with_last_soa(msg, sock_fd, self._soa,
+ message_upper_len)
return
# Add the beginning SOA
msg.add_rrset(Message.SECTION_ANSWER, self._soa)
- message_upper_len = get_rrset_len(self._soa) + self._tsig_len
+ message_upper_len += get_rrset_len(self._soa)
# Add the rest of the zone/diff contets
for rrset in self._iterator:
@@ -577,20 +595,33 @@ class XfroutSession():
# size without compression) and use that to see if we
# may have reached the limit
rrset_len = get_rrset_len(rrset)
- if message_upper_len + rrset_len < XFROUT_MAX_MESSAGE_SIZE:
+
+ if message_upper_len + rrset_len <= XFROUT_MAX_MESSAGE_SIZE:
msg.add_rrset(Message.SECTION_ANSWER, rrset)
message_upper_len += rrset_len
continue
+ # RR would not fit. If there are other RRs in the buffer, send
+ # them now and leave this RR to the next message.
self._send_message(sock_fd, msg, self._tsig_ctx)
+ # Create a new message and reserve space for the carried-over
+ # RR (and TSIG space in case it's to be TSIG signed)
msg = self._clear_message(msg)
+ message_upper_len = XFROUT_DNS_HEADER_SIZE + rrset_len + \
+ self._tsig_len
+
+ # If this RR overflows the buffer all by itself, fail. In theory
+ # some RRs might fit in a TCP message when compressed even if they
+ # do not fit when uncompressed, but surely we don't want to send
+ # such monstrosities to an unsuspecting slave.
+ if message_upper_len > XFROUT_MAX_MESSAGE_SIZE:
+ raise XfroutSessionError('RR too large for zone transfer (' +
+ str(rrset_len) + ' bytes)')
+
# Add the RRset to the new message
msg.add_rrset(Message.SECTION_ANSWER, rrset)
- # Reserve tsig space for signed packet
- message_upper_len = rrset_len + self._tsig_len
-
# Add and send the trailing SOA
self._send_message_with_last_soa(msg, sock_fd, self._soa,
message_upper_len)
@@ -782,7 +813,6 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
os.unlink(self._sock_file)
except Exception as e:
logger.error(XFROUT_REMOVE_UNIX_SOCKET_FILE_ERROR, self._sock_file, str(e))
- pass
def update_config_data(self, new_config):
'''Apply the new config setting of xfrout module.
diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am
index 5444547..07c3e13 100644
--- a/src/lib/asiolink/Makefile.am
+++ b/src/lib/asiolink/Makefile.am
@@ -14,6 +14,9 @@ CLEANFILES = *.gcno *.gcda
# with -Werror (our default setting).
lib_LTLIBRARIES = libasiolink.la
+
+libasiolink_la_LDFLAGS = -no-undefined -version-info 1:0:1
+
libasiolink_la_SOURCES = asiolink.h
libasiolink_la_SOURCES += dummy_io_cb.h
libasiolink_la_SOURCES += interval_timer.cc interval_timer.h
diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am
index bf1171e..b6c314c 100644
--- a/src/lib/datasrc/Makefile.am
+++ b/src/lib/datasrc/Makefile.am
@@ -7,9 +7,15 @@ AM_CPPFLAGS += $(SQLITE_CFLAGS)
AM_CXXFLAGS = $(B10_CXXFLAGS)
+pkglibexecdir = $(libexecdir)/@PACKAGE@/backends
+
+datasrc_config.h: datasrc_config.h.pre
+ $(SED) -e "s|@@PKGLIBEXECDIR@@|$(pkglibexecdir)|" datasrc_config.h.pre >$@
+
CLEANFILES = *.gcno *.gcda datasrc_messages.h datasrc_messages.cc
+CLEANFILES += datasrc_config.h
-lib_LTLIBRARIES = libdatasrc.la sqlite3_ds.la memory_ds.la
+lib_LTLIBRARIES = libdatasrc.la
libdatasrc_la_SOURCES = data_source.h data_source.cc
libdatasrc_la_SOURCES += static_datasrc.h static_datasrc.cc
libdatasrc_la_SOURCES += sqlite3_datasrc.h sqlite3_datasrc.cc
@@ -25,8 +31,11 @@ libdatasrc_la_SOURCES += database.h database.cc
libdatasrc_la_SOURCES += factory.h factory.cc
nodist_libdatasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
+pkglibexec_LTLIBRARIES = sqlite3_ds.la memory_ds.la
+
sqlite3_ds_la_SOURCES = sqlite3_accessor.h sqlite3_accessor.cc
sqlite3_ds_la_LDFLAGS = -module
+sqlite3_ds_la_LDFLAGS += -no-undefined -version-info 1:0:0
sqlite3_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
sqlite3_ds_la_LIBADD += libdatasrc.la
sqlite3_ds_la_LIBADD += $(SQLITE_LIBS)
@@ -42,7 +51,7 @@ libdatasrc_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la
libdatasrc_la_LIBADD += $(top_builddir)/src/lib/cc/libcc.la
libdatasrc_la_LIBADD += $(SQLITE_LIBS)
-BUILT_SOURCES = datasrc_messages.h datasrc_messages.cc
+BUILT_SOURCES = datasrc_config.h datasrc_messages.h datasrc_messages.cc
datasrc_messages.h datasrc_messages.cc: Makefile datasrc_messages.mes
$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/datasrc/datasrc_messages.mes
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index 053d4bc..45ce0c2 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -843,7 +843,7 @@ public:
committed_(false), accessor_(accessor), zone_id_(zone_id),
db_name_(accessor->getDBName()), zone_name_(zone_name.toText()),
zone_class_(zone_class), journaling_(journaling),
- diff_phase_(NOT_STARTED),
+ diff_phase_(NOT_STARTED), serial_(0),
finder_(new DatabaseClient::Finder(accessor_, zone_id_, zone_name))
{
logger.debug(DBG_TRACE_DATA, DATASRC_DATABASE_UPDATER_CREATED)
@@ -896,7 +896,7 @@ private:
ADD
};
DiffPhase diff_phase_;
- uint32_t serial_;
+ Serial serial_;
boost::scoped_ptr<DatabaseClient::Finder> finder_;
// This is a set of validation checks commonly used for addRRset() and
@@ -985,8 +985,8 @@ DatabaseUpdater::addRRset(const RRset& rrset) {
columns[Accessor::ADD_RDATA] = it->getCurrent().toText();
if (journaling_) {
journal[Accessor::DIFF_RDATA] = columns[Accessor::ADD_RDATA];
- accessor_->addRecordDiff(zone_id_, serial_, Accessor::DIFF_ADD,
- journal);
+ accessor_->addRecordDiff(zone_id_, serial_.getValue(),
+ Accessor::DIFF_ADD, journal);
}
accessor_->addRecordToZone(columns);
}
@@ -1023,8 +1023,8 @@ DatabaseUpdater::deleteRRset(const RRset& rrset) {
params[Accessor::DEL_RDATA] = it->getCurrent().toText();
if (journaling_) {
journal[Accessor::DIFF_RDATA] = params[Accessor::DEL_RDATA];
- accessor_->addRecordDiff(zone_id_, serial_, Accessor::DIFF_DELETE,
- journal);
+ accessor_->addRecordDiff(zone_id_, serial_.getValue(),
+ Accessor::DIFF_DELETE, journal);
}
accessor_->deleteRecordInZone(params);
}
diff --git a/src/lib/datasrc/datasrc_config.h.pre.in b/src/lib/datasrc/datasrc_config.h.pre.in
new file mode 100644
index 0000000..ff99601
--- /dev/null
+++ b/src/lib/datasrc/datasrc_config.h.pre.in
@@ -0,0 +1,31 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+#ifndef __DATASRC_CONFIG_H
+#define __DATASRC_CONFIG_H 1
+
+namespace isc {
+namespace datasrc {
+
+/// \brief Default directory to find the loadable data source libraries
+///
+/// This is the directory where, once installed, loadable backend libraries
+/// such as memory_ds.so and sqlite3_ds.so are found. It is used by the
+/// DataSourceClient loader if no absolute path is used and
+/// B10_FROM_BUILD is not set in the environment.
+const char* const BACKEND_LIBRARY_PATH = "@@PKGLIBEXECDIR@@/";
+
+} // end namespace datasrc
+} // end namespace isc
+
+#endif // __DATASRC_CONFIG_H
diff --git a/src/lib/datasrc/factory.cc b/src/lib/datasrc/factory.cc
index 1818c70..35a79fe 100644
--- a/src/lib/datasrc/factory.cc
+++ b/src/lib/datasrc/factory.cc
@@ -19,13 +19,59 @@
#include "sqlite3_accessor.h"
#include "memory_datasrc.h"
+#include "datasrc_config.h"
+
#include <datasrc/logger.h>
#include <dlfcn.h>
+#include <cstdlib>
+using namespace std;
using namespace isc::data;
using namespace isc::datasrc;
+namespace {
+// This helper function takes the 'type' string as passed to
+// the DataSourceClient container below, and, unless it
+// already specifies a specific loadable .so file, will
+// convert the short-name to the full file.
+// I.e. it will add '_ds.so' (if necessary), and prepend
+// it with an absolute path (if necessary).
+// Returns the resulting string to use with LibraryContainer.
+const std::string
+getDataSourceLibFile(const std::string& type) {
+ if (type.empty()) {
+ isc_throw(DataSourceLibraryError,
+ "DataSourceClient container called with empty type value");
+ }
+ if (type == ".so") {
+ isc_throw(DataSourceLibraryError, "DataSourceClient container called "
+ "with bad type or file name");
+ }
+
+ // Type can be either a short name, in which case we need to
+ // append "_ds.so", or it can be a direct .so library.
+ std::string lib_file = type;
+ const int ext_pos = lib_file.rfind(".so");
+ if (ext_pos == std::string::npos || ext_pos + 3 != lib_file.length()) {
+ lib_file.append("_ds.so");
+ }
+ // And if it is not an absolute path, prepend it with our
+ // loadable backend library path
+ if (type[0] != '/') {
+ // When running from the build tree, we do NOT want
+ // to load the installed loadable library
+ if (getenv("B10_FROM_BUILD") != NULL) {
+ lib_file = std::string(getenv("B10_FROM_BUILD")) +
+ "/src/lib/datasrc/.libs/" + lib_file;
+ } else {
+ lib_file = isc::datasrc::BACKEND_LIBRARY_PATH + lib_file;
+ }
+ }
+ return (lib_file);
+}
+} // end anonymous namespace
+
namespace isc {
namespace datasrc {
@@ -34,7 +80,10 @@ LibraryContainer::LibraryContainer(const std::string& name) {
// are recognized as such
ds_lib_ = dlopen(name.c_str(), RTLD_NOW | RTLD_GLOBAL);
if (ds_lib_ == NULL) {
- isc_throw(DataSourceLibraryError, dlerror());
+ // This may cause the filename to appear twice in the actual
+ // error, but the output of dlerror is implementation-dependent
+ isc_throw(DataSourceLibraryError, "dlopen failed for " << name <<
+ ": " << dlerror());
}
}
@@ -61,7 +110,7 @@ LibraryContainer::getSym(const char* name) {
DataSourceClientContainer::DataSourceClientContainer(const std::string& type,
ConstElementPtr config)
-: ds_lib_(type + "_ds.so")
+: ds_lib_(getDataSourceLibFile(type))
{
// We are casting from a data to a function pointer here
// Some compilers (rightfully) complain about that, but
diff --git a/src/lib/datasrc/factory.h b/src/lib/datasrc/factory.h
index 0284067..9d0a762 100644
--- a/src/lib/datasrc/factory.h
+++ b/src/lib/datasrc/factory.h
@@ -68,7 +68,7 @@ public:
/// the library path.
///
/// \exception DataSourceLibraryError If the library cannot be found or
- /// cannot be loaded.
+ /// cannot be loaded, or if name is an empty string.
LibraryContainer(const std::string& name);
/// \brief Destructor
@@ -115,6 +115,15 @@ private:
/// easy recognition and to reduce potential mistakes.
/// For example, the sqlite3 implementation has the type 'sqlite3', and the
/// derived filename 'sqlite3_ds.so'
+/// The value of type can be a specific loadable library; if it already ends
+/// with '.so', the loader will not add '_ds.so'.
+/// It may also be an absolute path; if it starts with '/', nothing is
+/// prepended. If it does not, the loadable library will be taken from the
+/// installation directory, see the value of
+/// isc::datasrc::BACKEND_LIBRARY_PATH in datasrc_config.h for the exact path.
+///
+/// \note When 'B10_FROM_BUILD' is set in the environment, the build
+/// directory is used instead of the install directory.
///
/// There are of course some demands to an implementation, not all of which
/// can be verified compile-time. It must provide a creator and destructor
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index 70f2999..6dd6b0a 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -55,13 +55,6 @@ run_unittests_SOURCES += test_datasrc.h test_datasrc.cc
run_unittests_SOURCES += rbtree_unittest.cc
run_unittests_SOURCES += logger_unittest.cc
run_unittests_SOURCES += client_unittest.cc
-if !USE_STATIC_LINK
-# This test uses dynamically loadable module. It will cause various
-# troubles with static link such as "missing" symbols in the static object
-# for the module. As a workaround we disable this particualr test
-# in this case.
-run_unittests_SOURCES += factory_unittest.cc
-endif
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
@@ -110,3 +103,21 @@ EXTRA_DIST += testdata/test.sqlite3
EXTRA_DIST += testdata/test.sqlite3.nodiffs
EXTRA_DIST += testdata/rwtest.sqlite3
EXTRA_DIST += testdata/diffs.sqlite3
+
+# For the factory unit tests, we need to specify that we want
+# the loadable backend libraries from the build tree, and not from
+# the installation directory. Therefore we build it into a separate
+# binary, and call that from check-local with B10_FROM_BUILD set.
+# Also, we only want to do this when static building is not used,
+# since it will cause various troubles with static link such as
+# "missing" symbols in the static object for the module.
+if !USE_STATIC_LINK
+noinst_PROGRAMS+=run_unittests_factory
+run_unittests_factory_SOURCES = $(common_sources)
+run_unittests_factory_SOURCES += factory_unittest.cc
+run_unittests_factory_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_factory_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_factory_LDADD = $(common_ldadd)
+check-local:
+ B10_FROM_BUILD=${abs_top_builddir} ./run_unittests_factory
+endif
diff --git a/src/lib/datasrc/tests/factory_unittest.cc b/src/lib/datasrc/tests/factory_unittest.cc
index 0133508..e98f9bc 100644
--- a/src/lib/datasrc/tests/factory_unittest.cc
+++ b/src/lib/datasrc/tests/factory_unittest.cc
@@ -14,6 +14,7 @@
#include <boost/scoped_ptr.hpp>
+#include <datasrc/datasrc_config.h>
#include <datasrc/factory.h>
#include <datasrc/data_source.h>
#include <datasrc/sqlite3_accessor.h>
@@ -30,6 +31,70 @@ std::string SQLITE_DBFILE_EXAMPLE_ORG = TEST_DATA_DIR "/example.org.sqlite3";
namespace {
+// note this helper only checks the error that is received up to the length
+// of the expected string. It will always pass if you give it an empty
+// expected_error
+void
+pathtestHelper(const std::string& file, const std::string& expected_error) {
+ std::string error;
+ try {
+ DataSourceClientContainer(file, ElementPtr());
+ } catch (const DataSourceLibraryError& dsle) {
+ error = dsle.what();
+ }
+ ASSERT_LT(expected_error.size(), error.size());
+ EXPECT_EQ(expected_error, error.substr(0, expected_error.size()));
+}
+
+TEST(FactoryTest, paths) {
+ // Test whether the paths are made absolute if they are not,
+ // by inspecting the error that is raised when they are wrong
+ const std::string error("dlopen failed for ");
+ // With the current implementation, we can safely assume this has
+ // been set for this test (as the loader would otherwise also fail
+ // unless the loadable backend library happens to be installed)
+ const std::string builddir(getenv("B10_FROM_BUILD"));
+
+ // Absolute and ending with .so should have no change
+ pathtestHelper("/no_such_file.so", error + "/no_such_file.so");
+
+ // If no ending in .so, it should get _ds.so
+ pathtestHelper("/no_such_file", error + "/no_such_file_ds.so");
+
+ // If not starting with /, path should be added. For this test that
+ // means the build directory as set in B10_FROM_BUILD
+ pathtestHelper("no_such_file.so", error + builddir +
+ "/src/lib/datasrc/.libs/no_such_file.so");
+ pathtestHelper("no_such_file", error + builddir +
+ "/src/lib/datasrc/.libs/no_such_file_ds.so");
+
+ // Some tests with '.so' in the name itself
+ pathtestHelper("no_such_file.so.something", error + builddir +
+ "/src/lib/datasrc/.libs/no_such_file.so.something_ds.so");
+ pathtestHelper("/no_such_file.so.something", error +
+ "/no_such_file.so.something_ds.so");
+ pathtestHelper("/no_such_file.so.something.so", error +
+ "/no_such_file.so.something.so");
+ pathtestHelper("/no_such_file.so.so", error +
+ "/no_such_file.so.so");
+ pathtestHelper("no_such_file.so.something", error + builddir +
+ "/src/lib/datasrc/.libs/no_such_file.so.something_ds.so");
+
+ // Temporarily unset B10_FROM_BUILD to see that BACKEND_LIBRARY_PATH
+ // is used
+ unsetenv("B10_FROM_BUILD");
+ pathtestHelper("no_such_file.so", error + BACKEND_LIBRARY_PATH +
+ "no_such_file.so");
+ // Put it back just in case
+ setenv("B10_FROM_BUILD", builddir.c_str(), 1);
+
+ // Test some bad input values
+ ASSERT_THROW(DataSourceClientContainer("", ElementPtr()),
+ DataSourceLibraryError);
+ ASSERT_THROW(DataSourceClientContainer(".so", ElementPtr()),
+ DataSourceLibraryError);
+}
+
TEST(FactoryTest, sqlite3ClientBadConfig) {
// We start out by building the configuration data bit by bit,
// testing each form of 'bad config', until we have a good one.
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index 64dda17..3991033 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -14,6 +14,7 @@ libdhcp_la_SOURCES += option.cc option.h
libdhcp_la_SOURCES += option6_ia.cc option6_ia.h
libdhcp_la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
libdhcp_la_SOURCES += option6_addrlst.cc option6_addrlst.h
+libdhcp_la_SOURCES += option4_addrlst.cc option4_addrlst.h
libdhcp_la_SOURCES += dhcp6.h dhcp4.h
libdhcp_la_SOURCES += pkt6.cc pkt6.h
libdhcp_la_SOURCES += pkt4.cc pkt4.h
diff --git a/src/lib/dhcp/libdhcp.cc b/src/lib/dhcp/libdhcp.cc
index b95a427..f84e495 100644
--- a/src/lib/dhcp/libdhcp.cc
+++ b/src/lib/dhcp/libdhcp.cc
@@ -17,6 +17,7 @@
#include <util/buffer.h>
#include <dhcp/libdhcp.h>
#include "config.h"
+#include <dhcp/dhcp4.h>
#include <dhcp/dhcp6.h>
#include <dhcp/option.h>
#include <dhcp/option6_ia.h>
@@ -90,8 +91,17 @@ LibDHCP::unpackOptions4(const std::vector<uint8_t>& buf,
size_t offset = 0;
// 2 - header of DHCPv4 option
- while (offset + 2 <= buf.size()) {
+ while (offset + 1 <= buf.size()) {
uint8_t opt_type = buf[offset++];
+ if (offset + 1 == buf.size()) {
+ if (opt_type == DHO_END)
+ return; // just return. Don't need to add DHO_END option
+ else {
+ isc_throw(OutOfRange, "Attempt to parse truncated option "
+ << opt_type);
+ }
+ }
+
uint8_t opt_len = buf[offset++];
if (offset + opt_len > buf.size() ) {
isc_throw(OutOfRange, "Option parse failed. Tried to parse "
diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc
index daef288..20dd97a 100644
--- a/src/lib/dhcp/option.cc
+++ b/src/lib/dhcp/option.cc
@@ -128,23 +128,6 @@ Option::pack4(isc::util::OutputBuffer& buf) {
}
unsigned int
-Option::pack4(boost::shared_array<uint8_t>& buf,
- unsigned int buf_len,
- unsigned int offset) {
- if (offset + len() > buf_len) {
- isc_throw(OutOfRange, "Failed to pack v4 option=" <<
- type_ << ",len=" << len() << ": too small buffer.");
- }
- uint8_t *ptr = &buf[offset];
- ptr[0] = type_;
- ptr[1] = len() - getHeaderLen();
- ptr += 2;
- memcpy(ptr, &data_[0], data_.size());
-
- return offset + len();
-}
-
-unsigned int
Option::pack6(boost::shared_array<uint8_t>& buf,
unsigned int buf_len,
unsigned int offset) {
@@ -220,7 +203,7 @@ Option::unpack6(const boost::shared_array<uint8_t>& buf,
/// Returns length of the complete option (data length + DHCPv4/DHCPv6
/// option header)
-unsigned short
+uint16_t
Option::len() {
// length of the whole option is header and data stored in this option...
@@ -295,17 +278,7 @@ std::string Option::toText(int indent /* =0 */ ) {
return tmp.str();
}
-unsigned short
-Option::getType() {
- return type_;
-}
-
-const std::vector<uint8_t>&
-Option::getData() {
- return (data_);
-}
-
-unsigned short
+uint16_t
Option::getHeaderLen() {
switch (universe_) {
case V4:
diff --git a/src/lib/dhcp/option.h b/src/lib/dhcp/option.h
index 3822cf0..088d094 100644
--- a/src/lib/dhcp/option.h
+++ b/src/lib/dhcp/option.h
@@ -178,20 +178,19 @@ public:
/// Returns option type (0-255 for DHCPv4, 0-65535 for DHCPv6)
///
/// @return option type
- unsigned short
- getType();
+ unsigned short getType() { return (type_); }
/// Returns length of the complete option (data length + DHCPv4/DHCPv6
/// option header)
///
/// @return length of the option
- virtual unsigned short
+ virtual uint16_t
len();
/// @brief Returns length of header (2 for v4, 4 for v6)
///
/// @return length of option header
- virtual unsigned short
+ virtual uint16_t
getHeaderLen();
/// returns if option is valid (e.g. option may be truncated)
@@ -202,9 +201,9 @@ public:
/// Returns pointer to actual data.
///
- /// @return pointer to actual data (or NULL if there is no data)
- virtual const std::vector<uint8_t>&
- getData();
+ /// @return pointer to actual data (or reference to an empty vector
+ /// if there is no data)
+ virtual const std::vector<uint8_t>& getData() { return (data_); }
/// Adds a sub-option.
///
@@ -242,20 +241,6 @@ public:
~Option();
protected:
-
- /// Builds raw (over-wire) buffer of this option, including all
- /// defined suboptions. Version for building DHCPv4 options.
- ///
- /// @param buf output buffer (built options will be stored here)
- /// @param buf_len buffer length (used for buffer overflow checks)
- /// @param offset offset from start of the buf buffer
- ///
- /// @return offset to the next byte after last used byte
- virtual unsigned int
- pack4(boost::shared_array<uint8_t>& buf,
- unsigned int buf_len,
- unsigned int offset);
-
/// Builds raw (over-wire) buffer of this option, including all
/// defined suboptions. Version for building DHCPv4 options.
///
diff --git a/src/lib/dhcp/option4_addrlst.cc b/src/lib/dhcp/option4_addrlst.cc
new file mode 100644
index 0000000..88eb915
--- /dev/null
+++ b/src/lib/dhcp/option4_addrlst.cc
@@ -0,0 +1,135 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <string.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+#include <sstream>
+#include <iomanip>
+#include <exceptions/exceptions.h>
+#include <asiolink/io_address.h>
+#include <util/io_utilities.h>
+#include <dhcp/option4_addrlst.h>
+
+using namespace std;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace isc::asiolink;
+
+Option4AddrLst::Option4AddrLst(uint8_t type)
+ :Option(V4, type) {
+}
+
+Option4AddrLst::Option4AddrLst(uint8_t type, const AddressContainer& addrs)
+ :Option(V4, type) {
+ setAddresses(addrs);
+ // don't set addrs_ directly. setAddresses() will do additional checks.
+}
+
+
+Option4AddrLst::Option4AddrLst(uint8_t type,
+ vector<uint8_t>::const_iterator first,
+ vector<uint8_t>::const_iterator last)
+ :Option(V4, type) {
+ if ( (distance(first, last) % V4ADDRESS_LEN) ) {
+ isc_throw(OutOfRange, "DHCPv4 Option4AddrLst " << type_
+ << " has invalid length=" << distance(first, last)
+ << ", must be divisible by 4.");
+ }
+
+ while (first != last) {
+ const uint8_t* ptr = &(*first);
+ addAddress(IOAddress(readUint32(ptr)));
+ first += V4ADDRESS_LEN;
+ }
+}
+
+Option4AddrLst::Option4AddrLst(uint8_t type, const IOAddress& addr)
+ :Option(V4, type) {
+ setAddress(addr);
+}
+
+void
+Option4AddrLst::pack4(isc::util::OutputBuffer& buf) {
+
+ if (addrs_.size() * V4ADDRESS_LEN > 255) {
+ isc_throw(OutOfRange, "DHCPv4 Option4AddrLst " << type_ << " is too big."
+ << "At most 255 bytes are supported.");
+ /// TODO Larger options can be stored as separate instances
+ /// of DHCPv4 options. Clients MUST concatenate them.
+ /// Fortunately, there are no such large options used today.
+ }
+
+ buf.writeUint8(type_);
+ buf.writeUint8(len() - getHeaderLen());
+
+ AddressContainer::const_iterator addr = addrs_.begin();
+
+ while (addr != addrs_.end()) {
+ buf.writeUint32(*addr);
+ ++addr;
+ }
+}
+
+void Option4AddrLst::setAddress(const isc::asiolink::IOAddress& addr) {
+ if (addr.getFamily() != AF_INET) {
+ isc_throw(BadValue, "Can't store non-IPv4 address in "
+ << "Option4AddrLst option");
+ }
+ addrs_.clear();
+ addAddress(addr);
+}
+
+void Option4AddrLst::setAddresses(const AddressContainer& addrs) {
+
+ // Do not copy it as a whole. addAddress() does sanity checks.
+ // i.e. throw if someone tries to set IPv6 address.
+ addrs_.clear();
+ for (AddressContainer::const_iterator addr = addrs.begin();
+ addr != addrs.end(); ++addr) {
+ addAddress(*addr);
+ }
+}
+
+
+void Option4AddrLst::addAddress(const isc::asiolink::IOAddress& addr) {
+ if (addr.getFamily() != AF_INET) {
+ isc_throw(BadValue, "Can't store non-IPv4 address in "
+ << "Option4AddrLst option");
+ }
+ addrs_.push_back(addr);
+}
+
+uint16_t Option4AddrLst::len() {
+
+ // Returns length of the complete option (option header + data length)
+ return (getHeaderLen() + addrs_.size() * V4ADDRESS_LEN);
+}
+
+std::string Option4AddrLst::toText(int indent /* =0 */ ) {
+ std::stringstream tmp;
+
+ for (int i = 0; i < indent; i++) {
+ tmp << " ";
+ }
+
+ tmp << "type=" << type_ << ", len=" << len()-getHeaderLen() << ":";
+
+ for (AddressContainer::const_iterator addr = addrs_.begin();
+ addr != addrs_.end(); ++addr) {
+ tmp << " " << (*addr);
+ }
+
+ return tmp.str();
+}
diff --git a/src/lib/dhcp/option4_addrlst.h b/src/lib/dhcp/option4_addrlst.h
new file mode 100644
index 0000000..c795805
--- /dev/null
+++ b/src/lib/dhcp/option4_addrlst.h
@@ -0,0 +1,167 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef OPTION4_ADDRLST_H_
+#define OPTION4_ADDRLST_H_
+
+#include <string>
+#include <map>
+#include <vector>
+#include <boost/shared_ptr.hpp>
+#include <boost/shared_array.hpp>
+#include <util/buffer.h>
+#include <dhcp/option.h>
+
+namespace isc {
+namespace dhcp {
+
+
+/// @brief DHCPv4 Option class for handling list of IPv4 addresses.
+///
+/// This class handles a list of IPv4 addresses. An example of such option
+/// is dns-servers option. It can also be used to handle a single address.
+class Option4AddrLst : public isc::dhcp::Option {
+public:
+
+ /// Defines a collection of IPv4 addresses.
+ typedef std::vector<isc::asiolink::IOAddress> AddressContainer;
+
+ /// @brief Constructor, creates an option with empty list of addresses.
+ ///
+ /// Creates empty option that can hold addresses. Addresses can be added
+ /// with addAddress(), setAddress() or setAddresses().
+ ///
+ /// @param type option type
+ Option4AddrLst(uint8_t type);
+
+ /// @brief Constructor, creates an option with a list of addresses.
+ ///
+ /// Creates an option that contains specified list of IPv4 addresses.
+ ///
+ /// @param type option type
+ /// @param addrs container with a list of addresses
+ Option4AddrLst(uint8_t type, const AddressContainer& addrs);
+
+ /// @brief Constructor, creates an option with a single address.
+ ///
+ /// Creates an option that contains a single address.
+ ///
+ /// @param type option type
+ /// @param addr a single address that will be stored as 1-elem. address list
+ Option4AddrLst(uint8_t type, const isc::asiolink::IOAddress& addr);
+
+ /// @brief Constructor, used for received options.
+ ///
+ /// TODO: This can be templated to use different containers, not just
+ /// vector. Prototype should look like this:
+ /// template<typename InputIterator> Option(Universe u, uint16_t type,
+ /// InputIterator first, InputIterator last);
+ ///
+ /// vector<int8_t> myData;
+ /// Example usage: new Option(V4, 123, myData.begin()+1, myData.end()-1)
+ /// This will create DHCPv4 option of type 123 that contains data from
+ /// trimmed (first and last byte removed) myData vector.
+ ///
+ /// @param type option type (0-255 for V4 and 0-65535 for V6)
+ /// @param first iterator to the first element that should be copied
+ /// @param last iterator to the next element after the last one
+ /// to be copied.
+ Option4AddrLst(uint8_t type, std::vector<uint8_t>::const_iterator first,
+ std::vector<uint8_t>::const_iterator last);
+
+ /// @brief Writes option in a wire-format to a buffer.
+ ///
+ /// Method will throw if option storing fails for some reason.
+ ///
+ /// TODO Once old (DHCPv6) implementation is rewritten,
+ /// unify pack4() and pack6() and rename them to just pack().
+ ///
+ /// @param buf output buffer (option will be stored there)
+ virtual void
+ pack4(isc::util::OutputBuffer& buf);
+
+ /// Returns string representation of the option.
+ ///
+ /// @param indent number of spaces before printing text
+ ///
+ /// @return string with text representation.
+ virtual std::string
+ toText(int indent = 0);
+
+ /// Returns length of the complete option (data length + DHCPv4/DHCPv6
+ /// option header)
+ ///
+ /// @return length of the option
+ virtual uint16_t len();
+
+ /// @brief Returns vector with addresses.
+ ///
+ /// We return a copy of our list. Although this includes overhead,
+ /// it also makes this list safe to use after this option object
+ /// is no longer available. As options are expected to hold only
+ /// a couple (1-3) addresses, the overhead is not that big.
+ ///
+ /// @return address container with addresses
+ AddressContainer
+ getAddresses() { return addrs_; };
+
+ /// @brief Sets addresses list.
+ ///
+ /// Clears existing list of addresses and adds a single address to that
+ /// list. This is very convenient method for options that are supposed to
+ /// only a single option. See addAddress() if you want to add
+ /// address to existing list or setAddresses() if you want to
+ /// set the whole list at once.
+ ///
+ /// Passed address must be IPv4 address. Otherwire BadValue exception
+ /// will be thrown.
+ ///
+ /// @param addrs address collection to be set
+ void setAddresses(const AddressContainer& addrs);
+
+ /// @brief Clears address list and sets a single address.
+ ///
+ /// Clears existing list of addresses and adds a single address to that
+ /// list. This is very convenient method for options that are supposed to
+ /// only a single option. See addAddress() if you want to add
+ /// address to existing list or setAddresses() if you want to
+ /// set the whole list at once.
+ ///
+ /// Passed address must be IPv4 address. Otherwire BadValue exception
+ /// will be thrown.
+ ///
+ /// @param addr an address that is going to be set as 1-element address list
+ void setAddress(const isc::asiolink::IOAddress& addr);
+
+ /// @brief Adds address to existing list of addresses.
+ ///
+ /// Adds a single address to that list. See setAddress() if you want to
+ /// define only a single address or setAddresses() if you want to
+ /// set the whole list at once.
+ ///
+ /// Passed address must be IPv4 address. Otherwire BadValue exception
+ /// will be thrown.
+ ///
+ /// @param addr an address thait is going to be added to existing list
+ void addAddress(const isc::asiolink::IOAddress& addr);
+
+protected:
+ /// contains list of addresses
+ AddressContainer addrs_;
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcp/option6_addrlst.cc b/src/lib/dhcp/option6_addrlst.cc
index fc981fa..9be3810 100644
--- a/src/lib/dhcp/option6_addrlst.cc
+++ b/src/lib/dhcp/option6_addrlst.cc
@@ -50,6 +50,10 @@ Option6AddrLst::Option6AddrLst(unsigned short type,
void
Option6AddrLst::setAddress(const isc::asiolink::IOAddress& addr) {
+ if (addr.getFamily() != AF_INET6) {
+ isc_throw(BadValue, "Can't store non-IPv6 address in Option6AddrLst option");
+ }
+
addrs_.clear();
addrs_.push_back(addr);
}
@@ -128,7 +132,7 @@ std::string Option6AddrLst::toText(int indent /* =0 */) {
return tmp.str();
}
-unsigned short Option6AddrLst::len() {
+uint16_t Option6AddrLst::len() {
return (OPTION6_HDR_LEN + addrs_.size()*16);
}
diff --git a/src/lib/dhcp/option6_addrlst.h b/src/lib/dhcp/option6_addrlst.h
index c5b32af..a73dc55 100644
--- a/src/lib/dhcp/option6_addrlst.h
+++ b/src/lib/dhcp/option6_addrlst.h
@@ -16,17 +16,16 @@
#define OPTION6_ADDRLST_H_
#include <vector>
-#include "asiolink/io_address.h"
-#include "dhcp/option.h"
+#include <asiolink/io_address.h>
+#include <dhcp/option.h>
namespace isc {
namespace dhcp {
-/// @brief Option class for handling list of IPv6 addresses.
+/// @brief DHCPv6 Option class for handling list of IPv6 addresses.
///
/// This class handles a list of IPv6 addresses. An example of such option
/// is dns-servers option. It can also be used to handle single address.
-///
class Option6AddrLst: public Option {
public:
@@ -105,17 +104,17 @@ public:
/// @brief Returns vector with addresses.
///
- /// As user may want to use/modify this list, it is better to return
- /// a copy rather than const reference to the original. This is
- /// usually one or two addresses long, so it is not a big deal.
- ///
- /// @return vector with addresses
+ /// We return a copy of our list. Although this includes overhead,
+ /// it also makes this list safe to use after this option object
+ /// is no longer available. As options are expected to hold only
+ /// a couple (1-3) addresses, the overhead is not that big.
///
+ /// @return address container with addresses
AddressContainer
getAddresses() { return addrs_; };
// returns data length (data length + DHCPv4/DHCPv6 option header)
- virtual unsigned short len();
+ virtual uint16_t len();
protected:
AddressContainer addrs_;
diff --git a/src/lib/dhcp/option6_ia.cc b/src/lib/dhcp/option6_ia.cc
index 46daee1..209f500 100644
--- a/src/lib/dhcp/option6_ia.cc
+++ b/src/lib/dhcp/option6_ia.cc
@@ -77,7 +77,7 @@ Option6IA::unpack(const boost::shared_array<uint8_t>& buf,
if ( parse_len < OPTION6_IA_LEN || offset + OPTION6_IA_LEN > buf_len) {
isc_throw(OutOfRange, "Option " << type_ << " truncated");
}
-
+
iaid_ = readUint32(&buf[offset]);
offset += sizeof(uint32_t);
@@ -121,9 +121,9 @@ std::string Option6IA::toText(int indent /* = 0*/) {
return tmp.str();
}
-unsigned short Option6IA::len() {
+uint16_t Option6IA::len() {
- unsigned short length = OPTION6_HDR_LEN /*header (4)*/ +
+ uint16_t length = OPTION6_HDR_LEN /*header (4)*/ +
OPTION6_IA_LEN /* option content (12) */;
// length of all suboptions
diff --git a/src/lib/dhcp/option6_ia.h b/src/lib/dhcp/option6_ia.h
index 516b2fc..cab8068 100644
--- a/src/lib/dhcp/option6_ia.h
+++ b/src/lib/dhcp/option6_ia.h
@@ -116,7 +116,7 @@ public:
/// Returns length of this option, including option header and suboptions
///
/// @return length of this option
- virtual unsigned short
+ virtual uint16_t
len();
protected:
diff --git a/src/lib/dhcp/option6_iaaddr.cc b/src/lib/dhcp/option6_iaaddr.cc
index 4177714..fd3bca4 100644
--- a/src/lib/dhcp/option6_iaaddr.cc
+++ b/src/lib/dhcp/option6_iaaddr.cc
@@ -116,9 +116,9 @@ std::string Option6IAAddr::toText(int indent /* =0 */) {
return tmp.str();
}
-unsigned short Option6IAAddr::len() {
+uint16_t Option6IAAddr::len() {
- unsigned short length = OPTION6_HDR_LEN + OPTION6_IAADDR_LEN;
+ uint16_t length = OPTION6_HDR_LEN + OPTION6_IAADDR_LEN;
// length of all suboptions
// TODO implement:
diff --git a/src/lib/dhcp/option6_iaaddr.h b/src/lib/dhcp/option6_iaaddr.h
index 60c5c48..40e5967 100644
--- a/src/lib/dhcp/option6_iaaddr.h
+++ b/src/lib/dhcp/option6_iaaddr.h
@@ -126,8 +126,7 @@ public:
getValid() const { return valid_; }
/// returns data length (data length + DHCPv4/DHCPv6 option header)
- virtual unsigned short
- len();
+ virtual uint16_t len();
protected:
/// contains an IPv6 address
diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc
index ba07a10..1f68527 100644
--- a/src/lib/dhcp/pkt4.cc
+++ b/src/lib/dhcp/pkt4.cc
@@ -51,7 +51,6 @@ Pkt4::Pkt4(uint8_t msg_type, uint32_t transid)
bufferOut_(DHCPV4_PKT_HDR_LEN),
msg_type_(msg_type)
{
- /// TODO: fixed fields, uncomment in ticket #1224
memset(chaddr_, 0, MAX_CHADDR_LEN);
memset(sname_, 0, MAX_SNAME_LEN);
memset(file_, 0, MAX_FILE_LEN);
@@ -64,7 +63,6 @@ Pkt4::Pkt4(const uint8_t* data, size_t len)
ifindex_(-1),
local_port_(DHCP4_SERVER_PORT),
remote_port_(DHCP4_CLIENT_PORT),
- /// TODO Fixed fields, uncomment in ticket #1224
op_(BOOTREQUEST),
transid_(0),
secs_(0),
@@ -117,6 +115,10 @@ Pkt4::pack() {
LibDHCP::packOptions(bufferOut_, options_);
+ // add END option that indicates end of options
+ // (End option is very simple, just a 255 octet)
+ bufferOut_.writeUint8(DHO_END);
+
return (true);
}
bool
diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am
index 01799da..176992f 100644
--- a/src/lib/dhcp/tests/Makefile.am
+++ b/src/lib/dhcp/tests/Makefile.am
@@ -18,6 +18,7 @@ libdhcp_unittests_SOURCES += ../libdhcp.h ../libdhcp.cc libdhcp_unittest.cc
libdhcp_unittests_SOURCES += ../option6_iaaddr.h ../option6_iaaddr.cc option6_iaaddr_unittest.cc
libdhcp_unittests_SOURCES += ../option6_ia.h ../option6_ia.cc option6_ia_unittest.cc
libdhcp_unittests_SOURCES += ../option6_addrlst.h ../option6_addrlst.cc option6_addrlst_unittest.cc
+libdhcp_unittests_SOURCES += ../option4_addrlst.cc ../option4_addrlst.h option4_addrlst_unittest.cc
libdhcp_unittests_SOURCES += ../option.h ../option.cc option_unittest.cc
libdhcp_unittests_SOURCES += ../pkt6.h ../pkt6.cc pkt6_unittest.cc
libdhcp_unittests_SOURCES += ../pkt4.h ../pkt4.cc pkt4_unittest.cc
diff --git a/src/lib/dhcp/tests/option4_addrlst_unittest.cc b/src/lib/dhcp/tests/option4_addrlst_unittest.cc
new file mode 100644
index 0000000..d4ecf80
--- /dev/null
+++ b/src/lib/dhcp/tests/option4_addrlst_unittest.cc
@@ -0,0 +1,273 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <iostream>
+#include <sstream>
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+#include <asiolink/io_address.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/option.h>
+#include <dhcp/option4_addrlst.h>
+#include <util/buffer.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace {
+
+// a sample data (list of 4 addresses)
+const uint8_t sampledata[] = {
+ 192, 0, 2, 3, // 192.0.2.3
+ 255, 255, 255, 0, // 255.255.255.0 - popular netmask
+ 0, 0, 0 , 0, // used for default routes or (any address)
+ 127, 0, 0, 1 // loopback
+};
+
+// expected on-wire format for an option with 1 address
+const uint8_t expected1[] = { // 1 address
+ DHO_DOMAIN_NAME_SERVERS, 4, // type, length
+ 192, 0, 2, 3, // 192.0.2.3
+};
+
+// expected on-wire format for an option with 4 addresses
+const uint8_t expected4[] = { // 4 addresses
+ 254, 16, // type = 254, len = 16
+ 192, 0, 2, 3, // 192.0.2.3
+ 255, 255, 255, 0, // 255.255.255.0 - popular netmask
+ 0, 0, 0 ,0, // used for default routes or (any address)
+ 127, 0, 0, 1 // loopback
+};
+
+class Option4AddrLstTest : public ::testing::Test {
+protected:
+
+ Option4AddrLstTest():
+ vec_(vector<uint8_t>(300,0)) // 300 bytes long filled with 0s
+ {
+ sampleAddrs_.push_back(IOAddress("192.0.2.3"));
+ sampleAddrs_.push_back(IOAddress("255.255.255.0"));
+ sampleAddrs_.push_back(IOAddress("0.0.0.0"));
+ sampleAddrs_.push_back(IOAddress("127.0.0.1"));
+ }
+
+ vector<uint8_t> vec_;
+ Option4AddrLst::AddressContainer sampleAddrs_;
+
+};
+
+TEST_F(Option4AddrLstTest, parse1) {
+
+ memcpy(&vec_[0], sampledata, sizeof(sampledata));
+
+ // just one address
+ Option4AddrLst* opt1 = 0;
+ EXPECT_NO_THROW(
+ opt1 = new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS,
+ vec_.begin(),
+ vec_.begin()+4);
+ // use just first address (4 bytes), not the whole
+ // sampledata
+ );
+
+ EXPECT_EQ(Option::V4, opt1->getUniverse());
+
+ EXPECT_EQ(DHO_DOMAIN_NAME_SERVERS, opt1->getType());
+ EXPECT_EQ(6, opt1->len()); // 2 (header) + 4 (1x IPv4 addr)
+
+ Option4AddrLst::AddressContainer addrs = opt1->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+
+ EXPECT_EQ("192.0.2.3", addrs[0].toText());
+
+ EXPECT_NO_THROW(
+ delete opt1;
+ opt1 = 0;
+ );
+
+ // 1 address
+}
+
+TEST_F(Option4AddrLstTest, parse4) {
+
+ vector<uint8_t> buffer(300,0); // 300 bytes long filled with 0s
+
+ memcpy(&buffer[0], sampledata, sizeof(sampledata));
+
+ // 4 addresses
+ Option4AddrLst* opt4 = 0;
+ EXPECT_NO_THROW(
+ opt4 = new Option4AddrLst(254,
+ buffer.begin(),
+ buffer.begin()+sizeof(sampledata));
+ );
+
+ EXPECT_EQ(Option::V4, opt4->getUniverse());
+
+ EXPECT_EQ(254, opt4->getType());
+ EXPECT_EQ(18, opt4->len()); // 2 (header) + 16 (4x IPv4 addrs)
+
+ Option4AddrLst::AddressContainer addrs = opt4->getAddresses();
+ ASSERT_EQ(4, addrs.size());
+
+ EXPECT_EQ("192.0.2.3", addrs[0].toText());
+ EXPECT_EQ("255.255.255.0", addrs[1].toText());
+ EXPECT_EQ("0.0.0.0", addrs[2].toText());
+ EXPECT_EQ("127.0.0.1", addrs[3].toText());
+
+ EXPECT_NO_THROW(
+ delete opt4;
+ opt4 = 0;
+ );
+}
+
+TEST_F(Option4AddrLstTest, assembly1) {
+
+ Option4AddrLst* opt = 0;
+ EXPECT_NO_THROW(
+ opt = new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, IOAddress("192.0.2.3"));
+ );
+ EXPECT_EQ(Option::V4, opt->getUniverse());
+ EXPECT_EQ(DHO_DOMAIN_NAME_SERVERS, opt->getType());
+
+ Option4AddrLst::AddressContainer addrs = opt->getAddresses();
+ ASSERT_EQ(1, addrs.size() );
+ EXPECT_EQ("192.0.2.3", addrs[0].toText());
+
+ OutputBuffer buf(100);
+ EXPECT_NO_THROW(
+ opt->pack4(buf);
+ );
+
+ ASSERT_EQ(6, opt->len());
+ ASSERT_EQ(6, buf.getLength());
+
+ EXPECT_EQ(0, memcmp(expected1, buf.getData(), 6));
+
+ EXPECT_NO_THROW(
+ delete opt;
+ opt = 0;
+ );
+
+ // This is old-fashioned option. We don't serve IPv6 types here!
+ EXPECT_THROW(
+ opt = new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, IOAddress("2001:db8::1")),
+ BadValue
+ );
+ if (opt) {
+ // test failed. Execption was not thrown, but option was created instead.
+ delete opt;
+ }
+}
+
+TEST_F(Option4AddrLstTest, assembly4) {
+
+
+ Option4AddrLst* opt = 0;
+ EXPECT_NO_THROW(
+ opt = new Option4AddrLst(254, sampleAddrs_);
+ );
+ EXPECT_EQ(Option::V4, opt->getUniverse());
+ EXPECT_EQ(254, opt->getType());
+
+ Option4AddrLst::AddressContainer addrs = opt->getAddresses();
+ ASSERT_EQ(4, addrs.size() );
+ EXPECT_EQ("192.0.2.3", addrs[0].toText());
+ EXPECT_EQ("255.255.255.0", addrs[1].toText());
+ EXPECT_EQ("0.0.0.0", addrs[2].toText());
+ EXPECT_EQ("127.0.0.1", addrs[3].toText());
+
+ OutputBuffer buf(100);
+ EXPECT_NO_THROW(
+ opt->pack4(buf);
+ );
+
+ ASSERT_EQ(18, opt->len()); // 2(header) + 4xsizeof(IPv4addr)
+ ASSERT_EQ(18, buf.getLength());
+
+ ASSERT_EQ(0, memcmp(expected4, buf.getData(), 18));
+
+ EXPECT_NO_THROW(
+ delete opt;
+ opt = 0;
+ );
+
+ // This is old-fashioned option. We don't serve IPv6 types here!
+ sampleAddrs_.push_back(IOAddress("2001:db8::1"));
+ EXPECT_THROW(
+ opt = new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, sampleAddrs_),
+ BadValue
+ );
+ if (opt) {
+ // test failed. Execption was not thrown, but option was created instead.
+ delete opt;
+ }
+}
+
+TEST_F(Option4AddrLstTest, setAddress) {
+ Option4AddrLst* opt = 0;
+ EXPECT_NO_THROW(
+ opt = new Option4AddrLst(123, IOAddress("1.2.3.4"));
+ );
+ opt->setAddress(IOAddress("192.0.255.255"));
+
+ Option4AddrLst::AddressContainer addrs = opt->getAddresses();
+ ASSERT_EQ(1, addrs.size() );
+ EXPECT_EQ("192.0.255.255", addrs[0].toText());
+
+ // We should accept IPv4-only addresses.
+ EXPECT_THROW(
+ opt->setAddress(IOAddress("2001:db8::1")),
+ BadValue
+ );
+
+ EXPECT_NO_THROW(
+ delete opt;
+ );
+}
+
+TEST_F(Option4AddrLstTest, setAddresses) {
+
+ Option4AddrLst* opt = 0;
+
+ EXPECT_NO_THROW(
+ opt = new Option4AddrLst(123); // empty list
+ );
+
+ opt->setAddresses(sampleAddrs_);
+
+ Option4AddrLst::AddressContainer addrs = opt->getAddresses();
+ ASSERT_EQ(4, addrs.size() );
+ EXPECT_EQ("192.0.2.3", addrs[0].toText());
+ EXPECT_EQ("255.255.255.0", addrs[1].toText());
+ EXPECT_EQ("0.0.0.0", addrs[2].toText());
+ EXPECT_EQ("127.0.0.1", addrs[3].toText());
+
+ // We should accept IPv4-only addresses.
+ sampleAddrs_.push_back(IOAddress("2001:db8::1"));
+ EXPECT_THROW(
+ opt->setAddresses(sampleAddrs_),
+ BadValue
+ );
+
+ EXPECT_NO_THROW(
+ delete opt;
+ );
+}
+
+} // namespace
diff --git a/src/lib/dhcp/tests/pkt4_unittest.cc b/src/lib/dhcp/tests/pkt4_unittest.cc
index c89743f..0f70442 100644
--- a/src/lib/dhcp/tests/pkt4_unittest.cc
+++ b/src/lib/dhcp/tests/pkt4_unittest.cc
@@ -487,13 +487,15 @@ TEST(Pkt4Test, options) {
const OutputBuffer& buf = pkt->getBuffer();
// check that all options are stored, they should take sizeof(v4Opts)
- ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + sizeof(v4Opts),
+ // there also should be OPTION_END added (just one byte)
+ ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + sizeof(v4Opts) + 1,
buf.getLength());
// that that this extra data actually contain our options
const uint8_t* ptr = static_cast<const uint8_t*>(buf.getData());
ptr += Pkt4::DHCPV4_PKT_HDR_LEN; // rewind to end of fixed part
EXPECT_EQ(0, memcmp(ptr, v4Opts, sizeof(v4Opts)));
+ EXPECT_EQ(DHO_END, static_cast<uint8_t>(*(ptr + sizeof(v4Opts))));
EXPECT_NO_THROW(
delete pkt;
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
index 0d2bffd..5b93f75 100644
--- a/src/lib/dns/Makefile.am
+++ b/src/lib/dns/Makefile.am
@@ -84,6 +84,8 @@ BUILT_SOURCES += rdataclass.h rdataclass.cc
lib_LTLIBRARIES = libdns++.la
+libdns___la_LDFLAGS = -no-undefined -version-info 1:0:1
+
libdns___la_SOURCES =
libdns___la_SOURCES += edns.h edns.cc
libdns___la_SOURCES += exceptions.h exceptions.cc
@@ -102,6 +104,7 @@ libdns___la_SOURCES += rrsetlist.h rrsetlist.cc
libdns___la_SOURCES += rrttl.h rrttl.cc
libdns___la_SOURCES += rrtype.cc
libdns___la_SOURCES += question.h question.cc
+libdns___la_SOURCES += serial.h serial.cc
libdns___la_SOURCES += tsig.h tsig.cc
libdns___la_SOURCES += tsigerror.h tsigerror.cc
libdns___la_SOURCES += tsigkey.h tsigkey.cc
diff --git a/src/lib/dns/python/Makefile.am b/src/lib/dns/python/Makefile.am
index 3b89358..dd14991 100644
--- a/src/lib/dns/python/Makefile.am
+++ b/src/lib/dns/python/Makefile.am
@@ -12,6 +12,7 @@ libpydnspp_la_SOURCES += rrclass_python.cc rrclass_python.h
libpydnspp_la_SOURCES += rrtype_python.cc rrtype_python.h
libpydnspp_la_SOURCES += rrttl_python.cc rrttl_python.h
libpydnspp_la_SOURCES += rdata_python.cc rdata_python.h
+libpydnspp_la_SOURCES += serial_python.cc serial_python.h
libpydnspp_la_SOURCES += messagerenderer_python.cc messagerenderer_python.h
libpydnspp_la_SOURCES += rcode_python.cc rcode_python.h
libpydnspp_la_SOURCES += opcode_python.cc opcode_python.h
diff --git a/src/lib/dns/python/pydnspp.cc b/src/lib/dns/python/pydnspp.cc
index 0a7d8e5..212141c 100644
--- a/src/lib/dns/python/pydnspp.cc
+++ b/src/lib/dns/python/pydnspp.cc
@@ -49,6 +49,7 @@
#include "rrset_python.h"
#include "rrttl_python.h"
#include "rrtype_python.h"
+#include "serial_python.h"
#include "tsigerror_python.h"
#include "tsigkey_python.h"
#include "tsig_python.h"
@@ -492,6 +493,18 @@ initModulePart_RRType(PyObject* mod) {
}
bool
+initModulePart_Serial(PyObject* mod) {
+ if (PyType_Ready(&serial_type) < 0) {
+ return (false);
+ }
+ Py_INCREF(&serial_type);
+ PyModule_AddObject(mod, "Serial",
+ reinterpret_cast<PyObject*>(&serial_type));
+
+ return (true);
+}
+
+bool
initModulePart_TSIGError(PyObject* mod) {
if (PyType_Ready(&tsigerror_type) < 0) {
return (false);
@@ -804,6 +817,10 @@ PyInit_pydnspp(void) {
return (NULL);
}
+ if (!initModulePart_Serial(mod)) {
+ return (NULL);
+ }
+
if (!initModulePart_TSIGKey(mod)) {
return (NULL);
}
diff --git a/src/lib/dns/python/rdata_python.cc b/src/lib/dns/python/rdata_python.cc
index 06c0263..e4ff890 100644
--- a/src/lib/dns/python/rdata_python.cc
+++ b/src/lib/dns/python/rdata_python.cc
@@ -16,6 +16,7 @@
#include <Python.h>
#include <dns/rdata.h>
#include <dns/messagerenderer.h>
+#include <dns/exceptions.h>
#include <util/buffer.h>
#include <util/python/pycppwrapper_util.h>
@@ -23,6 +24,7 @@
#include "rrtype_python.h"
#include "rrclass_python.h"
#include "messagerenderer_python.h"
+#include "name_python.h"
using namespace isc::dns;
using namespace isc::dns::python;
@@ -31,6 +33,27 @@ using namespace isc::util::python;
using namespace isc::dns::rdata;
namespace {
+
+typedef PyObject* method(PyObject* self, PyObject* args);
+
+// Wrap a method into an exception handling, converting C++ exceptions
+// to python ones. The params and return value is just passed through.
+PyObject*
+exception_wrap(method* method, PyObject* self, PyObject* args) {
+ try {
+ return (method(self, args));
+ } catch (const std::exception& ex) {
+ // FIXME: These exceptions are not tested, I don't know how or if
+ // at all they can be triggered. But they are caught just in the case.
+ PyErr_SetString(PyExc_Exception, (std::string("Unknown exception: ") +
+ ex.what()).c_str());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(PyExc_Exception, "Unknown exception");
+ return (NULL);
+ }
+}
+
class s_Rdata : public PyObject {
public:
isc::dns::rdata::ConstRdataPtr cppobj;
@@ -44,16 +67,16 @@ typedef CPPPyObjectContainer<s_Rdata, Rdata> RdataContainer;
//
// General creation and destruction
-int Rdata_init(s_Rdata* self, PyObject* args);
-void Rdata_destroy(s_Rdata* self);
+int Rdata_init(PyObject* self, PyObject* args, PyObject*);
+void Rdata_destroy(PyObject* self);
// These are the functions we export
-PyObject* Rdata_toText(s_Rdata* self);
+PyObject* Rdata_toText(PyObject* self, PyObject*);
// This is a second version of toText, we need one where the argument
// is a PyObject*, for the str() function in python.
PyObject* Rdata_str(PyObject* self);
-PyObject* Rdata_toWire(s_Rdata* self, PyObject* args);
-PyObject* RData_richcmp(s_Rdata* self, s_Rdata* other, int op);
+PyObject* Rdata_toWire(PyObject* self, PyObject* args);
+PyObject* RData_richcmp(PyObject* self, PyObject* other, int op);
// This list contains the actual set of functions we have in
// python. Each entry has
@@ -62,9 +85,9 @@ PyObject* RData_richcmp(s_Rdata* self, s_Rdata* other, int op);
// 3. Argument type
// 4. Documentation
PyMethodDef Rdata_methods[] = {
- { "to_text", reinterpret_cast<PyCFunction>(Rdata_toText), METH_NOARGS,
+ { "to_text", Rdata_toText, METH_NOARGS,
"Returns the string representation" },
- { "to_wire", reinterpret_cast<PyCFunction>(Rdata_toWire), METH_VARARGS,
+ { "to_wire", Rdata_toWire, METH_VARARGS,
"Converts the Rdata object to wire format.\n"
"The argument can be either a MessageRenderer or an object that "
"implements the sequence interface. If the object is mutable "
@@ -75,58 +98,89 @@ PyMethodDef Rdata_methods[] = {
};
int
-Rdata_init(s_Rdata* self, PyObject* args) {
+Rdata_init(PyObject* self_p, PyObject* args, PyObject*) {
PyObject* rrtype;
PyObject* rrclass;
const char* s;
const char* data;
Py_ssize_t len;
+ s_Rdata* self(static_cast<s_Rdata*>(self_p));
- // Create from string
- if (PyArg_ParseTuple(args, "O!O!s", &rrtype_type, &rrtype,
- &rrclass_type, &rrclass,
- &s)) {
- self->cppobj = createRdata(PyRRType_ToRRType(rrtype),
- PyRRClass_ToRRClass(rrclass), s);
- return (0);
- } else if (PyArg_ParseTuple(args, "O!O!y#", &rrtype_type, &rrtype,
- &rrclass_type, &rrclass, &data, &len)) {
- InputBuffer input_buffer(data, len);
- self->cppobj = createRdata(PyRRType_ToRRType(rrtype),
- PyRRClass_ToRRClass(rrclass),
- input_buffer, len);
- return (0);
+ try {
+ // Create from string
+ if (PyArg_ParseTuple(args, "O!O!s", &rrtype_type, &rrtype,
+ &rrclass_type, &rrclass,
+ &s)) {
+ self->cppobj = createRdata(PyRRType_ToRRType(rrtype),
+ PyRRClass_ToRRClass(rrclass), s);
+ return (0);
+ } else if (PyArg_ParseTuple(args, "O!O!y#", &rrtype_type, &rrtype,
+ &rrclass_type, &rrclass, &data, &len)) {
+ InputBuffer input_buffer(data, len);
+ self->cppobj = createRdata(PyRRType_ToRRType(rrtype),
+ PyRRClass_ToRRClass(rrclass),
+ input_buffer, len);
+ return (0);
+ }
+ } catch (const isc::dns::rdata::InvalidRdataText& irdt) {
+ PyErr_SetString(po_InvalidRdataText, irdt.what());
+ return (-1);
+ } catch (const isc::dns::rdata::InvalidRdataLength& irdl) {
+ PyErr_SetString(po_InvalidRdataLength, irdl.what());
+ return (-1);
+ } catch (const isc::dns::rdata::CharStringTooLong& cstl) {
+ PyErr_SetString(po_CharStringTooLong, cstl.what());
+ return (-1);
+ } catch (const isc::dns::DNSMessageFORMERR& dmfe) {
+ PyErr_SetString(po_DNSMessageFORMERR, dmfe.what());
+ return (-1);
+ } catch (const std::exception& ex) {
+ // FIXME: These exceptions are not tested, I don't know how or if
+ // at all they can be triggered. But they are caught just in the case.
+ PyErr_SetString(PyExc_Exception, (std::string("Unknown exception: ") +
+ ex.what()).c_str());
+ return (-1);
+ } catch (...) {
+ PyErr_SetString(PyExc_Exception, "Unknown exception");
+ return (-1);
}
return (-1);
}
void
-Rdata_destroy(s_Rdata* self) {
+Rdata_destroy(PyObject* self) {
// Clear the shared_ptr so that its reference count is zero
// before we call tp_free() (there is no direct release())
- self->cppobj.reset();
+ static_cast<s_Rdata*>(self)->cppobj.reset();
Py_TYPE(self)->tp_free(self);
}
PyObject*
-Rdata_toText(s_Rdata* self) {
+Rdata_toText_internal(PyObject* self, PyObject*) {
// Py_BuildValue makes python objects from native data
- return (Py_BuildValue("s", self->cppobj->toText().c_str()));
+ return (Py_BuildValue("s", static_cast<const s_Rdata*>(self)->cppobj->
+ toText().c_str()));
+}
+
+PyObject*
+Rdata_toText(PyObject* self, PyObject* args) {
+ return (exception_wrap(&Rdata_toText_internal, self, args));
}
PyObject*
Rdata_str(PyObject* self) {
// Simply call the to_text method we already defined
return (PyObject_CallMethod(self,
- const_cast<char*>("to_text"),
+ const_cast<char*>("to_text"),
const_cast<char*>("")));
}
PyObject*
-Rdata_toWire(s_Rdata* self, PyObject* args) {
+Rdata_toWire_internal(PyObject* self_p, PyObject* args) {
PyObject* bytes;
PyObject* mr;
+ const s_Rdata* self(static_cast<const s_Rdata*>(self_p));
if (PyArg_ParseTuple(args, "O", &bytes) && PySequence_Check(bytes)) {
PyObject* bytes_o = bytes;
@@ -134,6 +188,11 @@ Rdata_toWire(s_Rdata* self, PyObject* args) {
OutputBuffer buffer(4);
self->cppobj->toWire(buffer);
PyObject* rd_bytes = PyBytes_FromStringAndSize(static_cast<const char*>(buffer.getData()), buffer.getLength());
+ // Make sure exceptions from here are propagated.
+ // The exception is already set, so we just return NULL
+ if (rd_bytes == NULL) {
+ return (NULL);
+ }
PyObject* result = PySequence_InPlaceConcat(bytes_o, rd_bytes);
// We need to release the object we temporarily created here
// to prevent memory leak
@@ -152,45 +211,64 @@ Rdata_toWire(s_Rdata* self, PyObject* args) {
}
PyObject*
-RData_richcmp(s_Rdata* self, s_Rdata* other, int op) {
- bool c;
+Rdata_toWire(PyObject* self, PyObject* args) {
+ return (exception_wrap(&Rdata_toWire_internal, self, args));
+}
+
+PyObject*
+RData_richcmp(PyObject* self_p, PyObject* other_p, int op) {
+ try {
+ bool c;
+ const s_Rdata* self(static_cast<const s_Rdata*>(self_p)),
+ * other(static_cast<const s_Rdata*>(other_p));
- // Check for null and if the types match. If different type,
- // simply return False
- if (!other || (self->ob_type != other->ob_type)) {
- Py_RETURN_FALSE;
- }
+ // Check for null and if the types match. If different type,
+ // simply return False
+ if (!other || (self->ob_type != other->ob_type)) {
+ Py_RETURN_FALSE;
+ }
- switch (op) {
- case Py_LT:
- c = self->cppobj->compare(*other->cppobj) < 0;
- break;
- case Py_LE:
- c = self->cppobj->compare(*other->cppobj) < 0 ||
- self->cppobj->compare(*other->cppobj) == 0;
- break;
- case Py_EQ:
- c = self->cppobj->compare(*other->cppobj) == 0;
- break;
- case Py_NE:
- c = self->cppobj->compare(*other->cppobj) != 0;
- break;
- case Py_GT:
- c = self->cppobj->compare(*other->cppobj) > 0;
- break;
- case Py_GE:
- c = self->cppobj->compare(*other->cppobj) > 0 ||
- self->cppobj->compare(*other->cppobj) == 0;
- break;
- default:
- PyErr_SetString(PyExc_IndexError,
- "Unhandled rich comparison operator");
+ switch (op) {
+ case Py_LT:
+ c = self->cppobj->compare(*other->cppobj) < 0;
+ break;
+ case Py_LE:
+ c = self->cppobj->compare(*other->cppobj) < 0 ||
+ self->cppobj->compare(*other->cppobj) == 0;
+ break;
+ case Py_EQ:
+ c = self->cppobj->compare(*other->cppobj) == 0;
+ break;
+ case Py_NE:
+ c = self->cppobj->compare(*other->cppobj) != 0;
+ break;
+ case Py_GT:
+ c = self->cppobj->compare(*other->cppobj) > 0;
+ break;
+ case Py_GE:
+ c = self->cppobj->compare(*other->cppobj) > 0 ||
+ self->cppobj->compare(*other->cppobj) == 0;
+ break;
+ default:
+ PyErr_SetString(PyExc_IndexError,
+ "Unhandled rich comparison operator");
+ return (NULL);
+ }
+ if (c) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+ } catch (const std::exception& ex) {
+ // FIXME: These exceptions are not tested, I don't know how or if
+ // at all they can be triggered. But they are caught just in the case.
+ PyErr_SetString(PyExc_Exception, (std::string("Unknown exception: ") +
+ ex.what()).c_str());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(PyExc_Exception, "Unknown exception");
return (NULL);
}
- if (c)
- Py_RETURN_TRUE;
- else
- Py_RETURN_FALSE;
}
} // end of unnamed namespace
@@ -217,7 +295,7 @@ PyTypeObject rdata_type = {
"pydnspp.Rdata",
sizeof(s_Rdata), // tp_basicsize
0, // tp_itemsize
- (destructor)Rdata_destroy, // tp_dealloc
+ Rdata_destroy, // tp_dealloc
NULL, // tp_print
NULL, // tp_getattr
NULL, // tp_setattr
@@ -237,7 +315,7 @@ PyTypeObject rdata_type = {
"a set of common interfaces to manipulate concrete RDATA objects.",
NULL, // tp_traverse
NULL, // tp_clear
- (richcmpfunc)RData_richcmp, // tp_richcompare
+ RData_richcmp, // tp_richcompare
0, // tp_weaklistoffset
NULL, // tp_iter
NULL, // tp_iternext
@@ -249,7 +327,7 @@ PyTypeObject rdata_type = {
NULL, // tp_descr_get
NULL, // tp_descr_set
0, // tp_dictoffset
- (initproc)Rdata_init, // tp_init
+ Rdata_init, // tp_init
NULL, // tp_alloc
PyType_GenericNew, // tp_new
NULL, // tp_free
diff --git a/src/lib/dns/python/rrset_python.cc b/src/lib/dns/python/rrset_python.cc
index 73a19e7..77d520b 100644
--- a/src/lib/dns/python/rrset_python.cc
+++ b/src/lib/dns/python/rrset_python.cc
@@ -52,51 +52,51 @@ public:
int RRset_init(s_RRset* self, PyObject* args);
void RRset_destroy(s_RRset* self);
-PyObject* RRset_getRdataCount(s_RRset* self);
-PyObject* RRset_getName(s_RRset* self);
-PyObject* RRset_getClass(s_RRset* self);
-PyObject* RRset_getType(s_RRset* self);
-PyObject* RRset_getTTL(s_RRset* self);
-PyObject* RRset_setName(s_RRset* self, PyObject* args);
-PyObject* RRset_setTTL(s_RRset* self, PyObject* args);
-PyObject* RRset_toText(s_RRset* self);
+PyObject* RRset_getRdataCount(PyObject* self, PyObject* args);
+PyObject* RRset_getName(PyObject* self, PyObject* args);
+PyObject* RRset_getClass(PyObject* self, PyObject* args);
+PyObject* RRset_getType(PyObject* self, PyObject* args);
+PyObject* RRset_getTTL(PyObject* self, PyObject* args);
+PyObject* RRset_setName(PyObject* self, PyObject* args);
+PyObject* RRset_setTTL(PyObject* self, PyObject* args);
+PyObject* RRset_toText(PyObject* self, PyObject* args);
PyObject* RRset_str(PyObject* self);
-PyObject* RRset_toWire(s_RRset* self, PyObject* args);
-PyObject* RRset_addRdata(s_RRset* self, PyObject* args);
-PyObject* RRset_getRdata(PyObject* po_self, PyObject*);
-PyObject* RRset_removeRRsig(s_RRset* self);
+PyObject* RRset_toWire(PyObject* self, PyObject* args);
+PyObject* RRset_addRdata(PyObject* self, PyObject* args);
+PyObject* RRset_getRdata(PyObject* po_self, PyObject* args);
+PyObject* RRset_removeRRsig(PyObject* self, PyObject* args);
// TODO: iterator?
PyMethodDef RRset_methods[] = {
- { "get_rdata_count", reinterpret_cast<PyCFunction>(RRset_getRdataCount), METH_NOARGS,
+ { "get_rdata_count", RRset_getRdataCount, METH_NOARGS,
"Returns the number of rdata fields." },
- { "get_name", reinterpret_cast<PyCFunction>(RRset_getName), METH_NOARGS,
+ { "get_name", RRset_getName, METH_NOARGS,
"Returns the name of the RRset, as a Name object." },
- { "get_class", reinterpret_cast<PyCFunction>(RRset_getClass), METH_NOARGS,
+ { "get_class", RRset_getClass, METH_NOARGS,
"Returns the class of the RRset as an RRClass object." },
- { "get_type", reinterpret_cast<PyCFunction>(RRset_getType), METH_NOARGS,
+ { "get_type", RRset_getType, METH_NOARGS,
"Returns the type of the RRset as an RRType object." },
- { "get_ttl", reinterpret_cast<PyCFunction>(RRset_getTTL), METH_NOARGS,
+ { "get_ttl", RRset_getTTL, METH_NOARGS,
"Returns the TTL of the RRset as an RRTTL object." },
- { "set_name", reinterpret_cast<PyCFunction>(RRset_setName), METH_VARARGS,
+ { "set_name", RRset_setName, METH_VARARGS,
"Sets the name of the RRset.\nTakes a Name object as an argument." },
- { "set_ttl", reinterpret_cast<PyCFunction>(RRset_setTTL), METH_VARARGS,
+ { "set_ttl", RRset_setTTL, METH_VARARGS,
"Sets the TTL of the RRset.\nTakes an RRTTL object as an argument." },
- { "to_text", reinterpret_cast<PyCFunction>(RRset_toText), METH_NOARGS,
+ { "to_text", RRset_toText, METH_NOARGS,
"Returns the text representation of the RRset as a string" },
- { "to_wire", reinterpret_cast<PyCFunction>(RRset_toWire), METH_VARARGS,
+ { "to_wire", RRset_toWire, METH_VARARGS,
"Converts the RRset object to wire format.\n"
"The argument can be either a MessageRenderer or an object that "
"implements the sequence interface. If the object is mutable "
"(for instance a bytearray()), the wire data is added in-place.\n"
"If it is not (for instance a bytes() object), a new object is "
"returned" },
- { "add_rdata", reinterpret_cast<PyCFunction>(RRset_addRdata), METH_VARARGS,
+ { "add_rdata", RRset_addRdata, METH_VARARGS,
"Adds the rdata for one RR to the RRset.\nTakes an Rdata object as an argument" },
{ "get_rdata", RRset_getRdata, METH_NOARGS,
"Returns a List containing all Rdata elements" },
- { "remove_rrsig", reinterpret_cast<PyCFunction>(RRset_removeRRsig), METH_NOARGS,
+ { "remove_rrsig", RRset_removeRRsig, METH_NOARGS,
"Clears the list of RRsigs for this RRset" },
{ NULL, NULL, 0, NULL }
};
@@ -133,14 +133,16 @@ RRset_destroy(s_RRset* self) {
}
PyObject*
-RRset_getRdataCount(s_RRset* self) {
- return (Py_BuildValue("I", self->cppobj->getRdataCount()));
+RRset_getRdataCount(PyObject* self, PyObject*) {
+ return (Py_BuildValue("I", static_cast<const s_RRset*>(self)->cppobj->
+ getRdataCount()));
}
PyObject*
-RRset_getName(s_RRset* self) {
+RRset_getName(PyObject* self, PyObject*) {
try {
- return (createNameObject(self->cppobj->getName()));
+ return (createNameObject(static_cast<const s_RRset*>(self)->cppobj->
+ getName()));
} catch (const exception& ex) {
const string ex_what =
"Unexpected failure getting rrset Name: " +
@@ -154,9 +156,10 @@ RRset_getName(s_RRset* self) {
}
PyObject*
-RRset_getClass(s_RRset* self) {
+RRset_getClass(PyObject* self, PyObject*) {
try {
- return (createRRClassObject(self->cppobj->getClass()));
+ return (createRRClassObject(static_cast<const s_RRset*>(self)->cppobj->
+ getClass()));
} catch (const exception& ex) {
const string ex_what =
"Unexpected failure getting question RRClass: " +
@@ -170,9 +173,10 @@ RRset_getClass(s_RRset* self) {
}
PyObject*
-RRset_getType(s_RRset* self) {
+RRset_getType(PyObject* self, PyObject*) {
try {
- return (createRRTypeObject(self->cppobj->getType()));
+ return (createRRTypeObject(static_cast<const s_RRset*>(self)->cppobj->
+ getType()));
} catch (const exception& ex) {
const string ex_what =
"Unexpected failure getting question RRType: " +
@@ -186,9 +190,10 @@ RRset_getType(s_RRset* self) {
}
PyObject*
-RRset_getTTL(s_RRset* self) {
+RRset_getTTL(PyObject* self, PyObject*) {
try {
- return (createRRTTLObject(self->cppobj->getTTL()));
+ return (createRRTTLObject(static_cast<const s_RRset*>(self)->cppobj->
+ getTTL()));
} catch (const exception& ex) {
const string ex_what =
"Unexpected failure getting question TTL: " +
@@ -202,29 +207,30 @@ RRset_getTTL(s_RRset* self) {
}
PyObject*
-RRset_setName(s_RRset* self, PyObject* args) {
+RRset_setName(PyObject* self, PyObject* args) {
PyObject* name;
if (!PyArg_ParseTuple(args, "O!", &name_type, &name)) {
return (NULL);
}
- self->cppobj->setName(PyName_ToName(name));
+ static_cast<s_RRset*>(self)->cppobj->setName(PyName_ToName(name));
Py_RETURN_NONE;
}
PyObject*
-RRset_setTTL(s_RRset* self, PyObject* args) {
+RRset_setTTL(PyObject* self, PyObject* args) {
PyObject* rrttl;
if (!PyArg_ParseTuple(args, "O!", &rrttl_type, &rrttl)) {
return (NULL);
}
- self->cppobj->setTTL(PyRRTTL_ToRRTTL(rrttl));
+ static_cast<s_RRset*>(self)->cppobj->setTTL(PyRRTTL_ToRRTTL(rrttl));
Py_RETURN_NONE;
}
PyObject*
-RRset_toText(s_RRset* self) {
+RRset_toText(PyObject* self, PyObject*) {
try {
- return (Py_BuildValue("s", self->cppobj->toText().c_str()));
+ return (Py_BuildValue("s", static_cast<const s_RRset*>(self)->cppobj->
+ toText().c_str()));
} catch (const EmptyRRset& ers) {
PyErr_SetString(po_EmptyRRset, ers.what());
return (NULL);
@@ -235,14 +241,15 @@ PyObject*
RRset_str(PyObject* self) {
// Simply call the to_text method we already defined
return (PyObject_CallMethod(self,
- const_cast<char*>("to_text"),
+ const_cast<char*>("to_text"),
const_cast<char*>("")));
}
PyObject*
-RRset_toWire(s_RRset* self, PyObject* args) {
+RRset_toWire(PyObject* self_p, PyObject* args) {
PyObject* bytes;
PyObject* mr;
+ const s_RRset* self(static_cast<const s_RRset*>(self_p));
try {
if (PyArg_ParseTuple(args, "O", &bytes) && PySequence_Check(bytes)) {
@@ -274,13 +281,13 @@ RRset_toWire(s_RRset* self, PyObject* args) {
}
PyObject*
-RRset_addRdata(s_RRset* self, PyObject* args) {
+RRset_addRdata(PyObject* self, PyObject* args) {
PyObject* rdata;
if (!PyArg_ParseTuple(args, "O!", &rdata_type, &rdata)) {
return (NULL);
}
try {
- self->cppobj->addRdata(PyRdata_ToRdata(rdata));
+ static_cast<s_RRset*>(self)->cppobj->addRdata(PyRdata_ToRdata(rdata));
Py_RETURN_NONE;
} catch (const std::bad_cast&) {
PyErr_Clear();
@@ -324,8 +331,8 @@ RRset_getRdata(PyObject* po_self, PyObject*) {
}
PyObject*
-RRset_removeRRsig(s_RRset* self) {
- self->cppobj->removeRRsig();
+RRset_removeRRsig(PyObject* self, PyObject*) {
+ static_cast<s_RRset*>(self)->cppobj->removeRRsig();
Py_RETURN_NONE;
}
diff --git a/src/lib/dns/python/serial_python.cc b/src/lib/dns/python/serial_python.cc
new file mode 100644
index 0000000..e2bd809
--- /dev/null
+++ b/src/lib/dns/python/serial_python.cc
@@ -0,0 +1,281 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <Python.h>
+
+#include <dns/serial.h>
+#include <util/python/pycppwrapper_util.h>
+
+#include "serial_python.h"
+#include "pydnspp_common.h"
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::python;
+using namespace isc::util;
+using namespace isc::util::python;
+
+namespace {
+// The s_* Class simply covers one instantiation of the object
+class s_Serial : public PyObject {
+public:
+ s_Serial() : cppobj(NULL) {};
+ isc::dns::Serial* cppobj;
+};
+
+typedef CPPPyObjectContainer<s_Serial, Serial> SerialContainer;
+
+PyObject* Serial_str(PyObject* self);
+PyObject* Serial_getValue(s_Serial* self);
+PyObject* Serial_richcmp(s_Serial* self, s_Serial* other, int op);
+PyObject* Serial_add(PyObject *right, PyObject *left);
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef Serial_methods[] = {
+ { "get_value", reinterpret_cast<PyCFunction>(Serial_getValue), METH_NOARGS,
+ "Returns the Serial value as an integer" },
+ { NULL, NULL, 0, NULL }
+};
+
+// For overriding the + operator. We do not define any other operators for
+// this type.
+PyNumberMethods Serial_NumberMethods = {
+ Serial_add, //nb_add;
+ NULL, //nb_subtract;
+ NULL, //nb_multiply;
+ NULL, //nb_remainder;
+ NULL, //nb_divmod;
+ NULL, //nb_power;
+ NULL, //nb_negative;
+ NULL, //nb_positive;
+ NULL, //nb_absolute;
+ NULL, //nb_bool;
+ NULL, //nb_invert;
+ NULL, //nb_lshift;
+ NULL, //nb_rshift;
+ NULL, //nb_and;
+ NULL, //nb_xor;
+ NULL, //nb_or;
+ NULL, //nb_int;
+ NULL, //nb_reserved;
+ NULL, //nb_float;
+
+ NULL, //nb_inplace_add;
+ NULL, //nb_inplace_subtract;
+ NULL, //nb_inplace_multiply;
+ NULL, //nb_inplace_remainder;
+ NULL, //nb_inplace_power;
+ NULL, //nb_inplace_lshift;
+ NULL, //nb_inplace_rshift;
+ NULL, //nb_inplace_and;
+ NULL, //nb_inplace_xor;
+ NULL, //nb_inplace_or;
+
+ NULL, //nb_floor_divide;
+ NULL, //nb_true_divide;
+ NULL, //nb_inplace_floor_divide;
+ NULL, //nb_inplace_true_divide;
+
+ NULL, //nb_index;
+};
+
+int
+Serial_init(s_Serial* self, PyObject* args) {
+ long long i;
+ if (PyArg_ParseTuple(args, "L", &i)) {
+ PyErr_Clear();
+ if (i < 0 || i > 0xffffffff) {
+ PyErr_SetString(PyExc_ValueError, "Serial number out of range");
+ return (-1);
+ }
+ self->cppobj = new Serial(i);
+ return (0);
+ } else {
+ return (-1);
+ }
+}
+
+void
+Serial_destroy(s_Serial* self) {
+ delete self->cppobj;
+ self->cppobj = NULL;
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+Serial_getValue(s_Serial* self) {
+ return (Py_BuildValue("I", self->cppobj->getValue()));
+}
+
+PyObject*
+Serial_str(PyObject* po_self) {
+ const s_Serial* const self = static_cast<s_Serial*>(po_self);
+ return (PyUnicode_FromFormat("%u", self->cppobj->getValue()));
+}
+
+PyObject*
+Serial_richcmp(s_Serial* self, s_Serial* other, int op) {
+ bool c = false;
+
+ // Check for null and if the types match. If different type,
+ // simply return False
+ if (!other || (self->ob_type != other->ob_type)) {
+ Py_RETURN_FALSE;
+ }
+
+ switch (op) {
+ case Py_LT:
+ c = *self->cppobj < *other->cppobj;
+ break;
+ case Py_LE:
+ c = *self->cppobj <= *other->cppobj;
+ break;
+ case Py_EQ:
+ c = *self->cppobj == *other->cppobj;
+ break;
+ case Py_NE:
+ c = *self->cppobj != *other->cppobj;
+ break;
+ case Py_GT:
+ c = *self->cppobj > *other->cppobj;
+ break;
+ case Py_GE:
+ c = *self->cppobj >= *other->cppobj;
+ break;
+ }
+ if (c) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+}
+
+PyObject *
+Serial_add(PyObject *left, PyObject *right) {
+ // Either can be either a serial or a long, as long as one of them is a
+ // serial
+ if (PySerial_Check(left) && PySerial_Check(right)) {
+ return (createSerialObject(PySerial_ToSerial(left) +
+ PySerial_ToSerial(right)));
+ } else if (PySerial_Check(left) && PyLong_Check(right)) {
+ return (createSerialObject(PySerial_ToSerial(left) +
+ PyLong_AsLong(right)));
+ } else if (PyLong_Check(left) && PySerial_Check(right)) {
+ return (createSerialObject(PySerial_ToSerial(right) +
+ PyLong_AsLong(left)));
+ } else {
+ Py_INCREF(Py_NotImplemented);
+ return Py_NotImplemented;
+ }
+}
+
+} // end anonymous namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_Serial
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject serial_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "pydnspp.Serial",
+ sizeof(s_Serial), // tp_basicsize
+ 0, // tp_itemsize
+ (destructor)Serial_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ &Serial_NumberMethods, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ Serial_str, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ "The Serial class encapsulates Serials used in DNS SOA records.\n\n"
+ "This is a straightforward class; an Serial object simply maintains a "
+ "32-bit unsigned integer corresponding to the SOA SERIAL value. The "
+ "main purpose of this class is to provide serial number arithmetic, as "
+ "described in RFC 1892. Objects of this type can be compared and added "
+ "to each other, as described in RFC 1892. Apart from str(), get_value(), "
+ "comparison operators, and the + operator, no other operations are "
+ "defined for this type.",
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ (richcmpfunc)Serial_richcmp, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ Serial_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ (initproc)Serial_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+PyObject*
+createSerialObject(const Serial& source) {
+ SerialContainer container(PyObject_New(s_Serial, &serial_type));
+ container.set(new Serial(source));
+ return (container.release());
+}
+
+bool
+PySerial_Check(PyObject* obj) {
+ if (obj == NULL) {
+ isc_throw(PyCPPWrapperException,
+ "obj argument NULL in Serial typecheck");
+ }
+ return (PyObject_TypeCheck(obj, &serial_type));
+}
+
+const Serial&
+PySerial_ToSerial(const PyObject* serial_obj) {
+ if (serial_obj == NULL) {
+ isc_throw(PyCPPWrapperException,
+ "obj argument NULL in Serial PyObject conversion");
+ }
+ const s_Serial* serial = static_cast<const s_Serial*>(serial_obj);
+ return (*serial->cppobj);
+}
+
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/serial_python.h b/src/lib/dns/python/serial_python.h
new file mode 100644
index 0000000..48b5199
--- /dev/null
+++ b/src/lib/dns/python/serial_python.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_SERIAL_H
+#define __PYTHON_SERIAL_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class Serial;
+
+namespace python {
+
+extern PyTypeObject serial_type;
+
+/// This is a simple shortcut to create a python Serial object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called within a try block
+/// followed by necessary setup for python exception.
+PyObject* createSerialObject(const Serial& source);
+
+/// \brief Checks if the given python object is a Serial object
+///
+/// \exception PyCPPWrapperException if obj is NULL
+///
+/// \param obj The object to check the type of
+/// \return true if the object is of type Serial, false otherwise
+bool PySerial_Check(PyObject* obj);
+
+/// \brief Returns a reference to the Serial object contained within the given
+/// Python object.
+///
+/// \note The given object MUST be of type Serial; this can be checked with
+/// either the right call to ParseTuple("O!"), or with PySerial_Check()
+///
+/// \note This is not a copy; if the Serial is needed when the PyObject
+/// may be destroyed, the caller must copy it itself.
+///
+/// \param Serial_obj The Serial object to convert
+const Serial& PySerial_ToSerial(const PyObject* Serial_obj);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_SERIAL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/tests/Makefile.am b/src/lib/dns/python/tests/Makefile.am
index d1273f3..3338727 100644
--- a/src/lib/dns/python/tests/Makefile.am
+++ b/src/lib/dns/python/tests/Makefile.am
@@ -11,6 +11,7 @@ PYTESTS += rrclass_python_test.py
PYTESTS += rrset_python_test.py
PYTESTS += rrttl_python_test.py
PYTESTS += rrtype_python_test.py
+PYTESTS += serial_python_test.py
PYTESTS += tsig_python_test.py
PYTESTS += tsig_rdata_python_test.py
PYTESTS += tsigerror_python_test.py
diff --git a/src/lib/dns/python/tests/rdata_python_test.py b/src/lib/dns/python/tests/rdata_python_test.py
index 776f792..81dea5f 100644
--- a/src/lib/dns/python/tests/rdata_python_test.py
+++ b/src/lib/dns/python/tests/rdata_python_test.py
@@ -35,6 +35,14 @@ class RdataTest(unittest.TestCase):
self.assertRaises(TypeError, Rdata, "wrong", RRClass("IN"), "192.0.2.99")
self.assertRaises(TypeError, Rdata, RRType("A"), "wrong", "192.0.2.99")
self.assertRaises(TypeError, Rdata, RRType("A"), RRClass("IN"), 1)
+ self.assertRaises(InvalidRdataText, Rdata, RRType("A"), RRClass("IN"),
+ "Invalid Rdata Text")
+ self.assertRaises(CharStringTooLong, Rdata, RRType("TXT"),
+ RRClass("IN"), ' ' * 256)
+ self.assertRaises(InvalidRdataLength, Rdata, RRType("TXT"),
+ RRClass("IN"), bytes(65536))
+ self.assertRaises(DNSMessageFORMERR, Rdata, RRType("TXT"),
+ RRClass("IN"), b"\xff")
def test_rdata_to_wire(self):
b = bytearray()
diff --git a/src/lib/dns/python/tests/serial_python_test.py b/src/lib/dns/python/tests/serial_python_test.py
new file mode 100644
index 0000000..4e87f3d
--- /dev/null
+++ b/src/lib/dns/python/tests/serial_python_test.py
@@ -0,0 +1,109 @@
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+#
+# Tests for the rrttl part of the pydnspp module
+#
+
+import unittest
+import os
+from pydnspp import *
+
+class SerialTest(unittest.TestCase):
+ def setUp(self):
+ self.one = Serial(1)
+ self.one_2 = Serial(1)
+ self.two = Serial(2)
+ self.date_zero = Serial(1980120100)
+ self.date_one = Serial(1980120101)
+ self.zero = Serial(0)
+ self.highest = Serial(4294967295)
+ self.number_low = Serial(12345)
+ self.number_medium = Serial(2000000000)
+ self.number_high = Serial(4000000000)
+
+ def test_init(self):
+ self.assertRaises(ValueError, Serial, -1)
+ self.assertRaises(ValueError, Serial, 4294967296)
+ self.assertRaises(ValueError, Serial, 4294967297)
+ self.assertRaises(ValueError, Serial, 100000000000)
+
+ def test_get_value(self):
+ self.assertEqual(1, self.one.get_value())
+ self.assertNotEqual(2, self.one_2.get_value())
+ self.assertEqual(2, self.two.get_value())
+ self.assertEqual(1980120100, self.date_zero.get_value())
+ self.assertEqual(1980120101, self.date_one.get_value())
+ self.assertEqual(0, self.zero.get_value())
+ self.assertEqual(4294967295, self.highest.get_value())
+ self.assertEqual(12345, self.number_low.get_value())
+ self.assertEqual(2000000000, self.number_medium.get_value())
+ self.assertEqual(4000000000, self.number_high.get_value())
+
+ def test_str(self):
+ self.assertEqual('1', str(self.one))
+ self.assertNotEqual('2', str(self.one_2))
+ self.assertEqual('2', str(self.two))
+ self.assertEqual('1980120100', str(self.date_zero))
+ self.assertEqual('1980120101', str(self.date_one))
+ self.assertEqual('0', str(self.zero))
+ self.assertEqual('4294967295', str(self.highest))
+ self.assertEqual('12345', str(self.number_low))
+ self.assertEqual('2000000000', str(self.number_medium))
+ self.assertEqual('4000000000', str(self.number_high))
+
+ def test_equals(self):
+ self.assertEqual(self.one, self.one)
+ self.assertEqual(self.one, self.one_2)
+ self.assertNotEqual(self.one, self.two)
+ self.assertNotEqual(self.two, self.one)
+ self.assertEqual(Serial(12345), self.number_low)
+ self.assertNotEqual(Serial(12346), self.number_low)
+
+ def test_compare(self):
+ # These should be true/false even without serial arithmetic
+ self.assertLessEqual(self.one, self.one)
+ self.assertLessEqual(self.one, self.one_2)
+ self.assertLess(self.one, self.two)
+ self.assertLessEqual(self.one, self.two)
+ self.assertGreater(self.two, self.one)
+ self.assertGreaterEqual(self.two, self.two)
+ self.assertLess(self.one, self.number_low)
+ self.assertLess(self.number_low, self.number_medium)
+ self.assertLess(self.number_medium, self.number_high)
+
+ # These should 'wrap'
+ self.assertGreater(self.zero, self.highest)
+ self.assertLess(self.highest, self.one)
+ self.assertLess(self.number_high, self.number_low)
+
+ def test_addition(self):
+ self.assertEqual(self.two, self.one + self.one)
+ self.assertEqual(self.two, self.one + self.one_2)
+ self.assertEqual(self.highest, self.highest + self.zero)
+ self.assertEqual(self.zero, self.highest + self.one)
+ self.assertEqual(self.one, self.highest + self.two)
+ self.assertEqual(self.one, self.highest + self.one + self.one)
+ self.assertEqual(self.one + 100, self.highest + 102)
+ self.assertEqual(100 + self.one, self.highest + 102)
+ self.assertEqual(self.zero + 2147483645, self.highest + 2147483646)
+
+ # using lambda so the error doesn't get thrown on initial evaluation
+ self.assertRaises(TypeError, lambda: self.zero + "bad")
+ self.assertRaises(TypeError, lambda: self.zero + None)
+ self.assertRaises(TypeError, lambda: "bad" + self.zero)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/lib/dns/rdata/generic/soa_6.cc b/src/lib/dns/rdata/generic/soa_6.cc
index 875a957..e473bca 100644
--- a/src/lib/dns/rdata/generic/soa_6.cc
+++ b/src/lib/dns/rdata/generic/soa_6.cc
@@ -106,10 +106,10 @@ SOA::toWire(AbstractMessageRenderer& renderer) const {
renderer.writeData(numdata_, sizeof(numdata_));
}
-uint32_t
+Serial
SOA::getSerial() const {
InputBuffer b(numdata_, sizeof(numdata_));
- return (b.readUint32());
+ return (Serial(b.readUint32()));
}
string
diff --git a/src/lib/dns/rdata/generic/soa_6.h b/src/lib/dns/rdata/generic/soa_6.h
index 4c6b6ec..2c180b2 100644
--- a/src/lib/dns/rdata/generic/soa_6.h
+++ b/src/lib/dns/rdata/generic/soa_6.h
@@ -18,6 +18,7 @@
#include <dns/name.h>
#include <dns/rdata.h>
+#include <dns/serial.h>
// BEGIN_ISC_NAMESPACE
@@ -35,7 +36,7 @@ public:
uint32_t refresh, uint32_t retry, uint32_t expire,
uint32_t minimum);
/// \brief Returns the serial stored in the SOA.
- uint32_t getSerial() const;
+ Serial getSerial() const;
private:
/// Note: this is a prototype version; we may reconsider
/// this representation later.
diff --git a/src/lib/dns/serial.cc b/src/lib/dns/serial.cc
new file mode 100644
index 0000000..7f07444
--- /dev/null
+++ b/src/lib/dns/serial.cc
@@ -0,0 +1,76 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/serial.h>
+
+namespace isc {
+namespace dns {
+
+bool
+Serial::operator==(const Serial& other) const {
+ return (value_ == other.getValue());
+}
+
+bool
+Serial::operator!=(const Serial& other) const {
+ return (value_ != other.getValue());
+}
+
+bool
+Serial::operator<(const Serial& other) const {
+ uint32_t other_val = other.getValue();
+ bool result = false;
+ if (value_ < other_val) {
+ result = ((other_val - value_) <= MAX_SERIAL_INCREMENT);
+ } else if (other_val < value_) {
+ result = ((value_ - other_val) > MAX_SERIAL_INCREMENT);
+ }
+ return (result);
+}
+
+bool
+Serial::operator<=(const Serial& other) const {
+ return (operator==(other) || operator<(other));
+}
+
+bool
+Serial::operator>(const Serial& other) const {
+ return (!operator==(other) && !operator<(other));
+}
+
+bool
+Serial::operator>=(const Serial& other) const {
+ return (operator==(other) || !operator>(other));
+}
+
+Serial
+Serial::operator+(uint32_t other_val) const {
+ uint64_t new_val = static_cast<uint64_t>(value_) +
+ static_cast<uint64_t>(other_val);
+ return Serial(static_cast<uint32_t>(new_val % MAX_SERIAL_VALUE));
+}
+
+Serial
+Serial::operator+(const Serial& other) const {
+ return (operator+(other.getValue()));
+}
+
+std::ostream&
+operator<<(std::ostream& os, const Serial& serial) {
+ return (os << serial.getValue());
+}
+
+} // end namespace dns
+} // end namespace isc
+
diff --git a/src/lib/dns/serial.h b/src/lib/dns/serial.h
new file mode 100644
index 0000000..3549860
--- /dev/null
+++ b/src/lib/dns/serial.h
@@ -0,0 +1,155 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __SERIAL_H
+#define __SERIAL_H 1
+
+#include <stdint.h>
+#include <iostream>
+
+namespace isc {
+namespace dns {
+
+/// The maximum difference between two serial numbers. If the (plain uint32_t)
+/// difference between two serials is greater than this number, the smaller one
+/// is considered greater.
+const uint32_t MAX_SERIAL_INCREMENT = 2147483647;
+
+/// Maximum value a serial can have, used in + operator.
+const uint64_t MAX_SERIAL_VALUE = 4294967296ull;
+
+/// \brief This class defines DNS serial numbers and serial arithmetic.
+///
+/// DNS Serial number are in essence unsigned 32-bits numbers, with one
+/// catch; they should be compared using sequence space arithmetic.
+/// So given that they are 32-bits; as soon as the difference between two
+/// serial numbers is greater than 2147483647 (2^31 - 1), the lower number
+/// (in plain comparison) is considered the higher one.
+///
+/// In order to do this as transparently as possible, these numbers are
+/// stored in the Serial class, which overrides the basic comparison operators.
+///
+/// In this specific context, these operations are called 'serial number
+/// arithmetic', and they are defined in RFC 1982.
+///
+/// \note RFC 1982 defines everything based on the value SERIAL_BITS. Since
+/// the serial number has a fixed length of 32 bits, the values we use are
+/// hard-coded, and not computed based on variable bit lengths.
+class Serial {
+public:
+ /// \brief Constructor with value
+ ///
+ /// \param value The uint32_t value of the serial
+ explicit Serial(uint32_t value) : value_(value) {}
+
+ /// \brief Copy constructor
+ Serial(const Serial& other) : value_(other.getValue()) {}
+
+ /// \brief Direct assignment from other Serial
+ ///
+ /// \param other The Serial to assign the value from
+ void operator=(const Serial& other) { value_ = other.getValue(); }
+
+ /// \brief Direct assignment from value
+ ///
+ /// \param value the uint32_t value to assing
+ void operator=(uint32_t value) { value_ = value; }
+
+ /// \brief Returns the uint32_t representation of this serial value
+ ///
+ /// \return The uint32_t value of this Serial
+ uint32_t getValue() const { return (value_); }
+
+ /// \brief Returns true if the serial values are equal
+ ///
+ /// \return True if the values are equal
+ bool operator==(const Serial& other) const;
+
+ /// \brief Returns true if the serial values are not equal
+ ///
+ /// \return True if the values are not equal
+ bool operator!=(const Serial& other) const;
+
+ /// \brief Returns true if the serial value of this serial is smaller than
+ /// the other, according to serial arithmetic as described in RFC 1982
+ ///
+ /// \param other The Serial to compare to
+ ///
+ /// \return True if this is smaller than the given value
+ bool operator<(const Serial& other) const;
+
+ /// \brief Returns true if the serial value of this serial is equal to or
+ /// smaller than the other, according to serial arithmetic as described
+ /// in RFC 1982
+ ///
+ /// \param other The Serial to compare to
+ ///
+ /// \return True if this is smaller than or equal to the given value
+ bool operator<=(const Serial& other) const;
+
+ /// \brief Returns true if the serial value of this serial is greater than
+ /// the other, according to serial arithmetic as described in RFC 1982
+ ///
+ /// \param other The Serial to compare to
+ ///
+ /// \return True if this is greater than the given value
+ bool operator>(const Serial& other) const;
+
+ /// \brief Returns true if the serial value of this serial is equal to or
+ /// greater than the other, according to serial arithmetic as described in
+ /// RFC 1982
+ ///
+ /// \param other The Serial to compare to
+ ///
+ /// \return True if this is greater than or equal to the given value
+ bool operator>=(const Serial& other) const;
+
+ /// \brief Adds the given value to the serial number. If this would make
+ /// the number greater than 2^32-1, it is 'wrapped'.
+ /// \note According to the specification, an addition greater than
+ /// MAX_SERIAL_INCREMENT is undefined. We do NOT catch this error (so as not
+ /// to raise exceptions), but this behaviour remains undefined.
+ ///
+ /// \param other The Serial to add
+ ///
+ /// \return The result of the addition
+ Serial operator+(const Serial& other) const;
+
+ /// \brief Adds the given value to the serial number. If this would make
+ /// the number greater than 2^32-1, it is 'wrapped'.
+ ///
+ /// \note According to the specification, an addition greater than
+ /// MAX_SERIAL_INCREMENT is undefined. We do NOT catch this error (so as not
+ /// to raise exceptions), but this behaviour remains undefined.
+ ///
+ /// \param other_val The uint32_t value to add
+ ///
+ /// \return The result of the addition
+ Serial operator+(uint32_t other_val) const;
+
+private:
+ uint32_t value_;
+};
+
+/// \brief Helper operator for output streams, writes the value to the stream
+///
+/// \param os The ostream to write to
+/// \param serial The Serial to write
+/// \return the output stream
+std::ostream& operator<<(std::ostream& os, const Serial& serial);
+
+} // end namespace dns
+} // end namespace isc
+
+#endif // __SERIAL_H
diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am
index ceeb3b8..fc6c87c 100644
--- a/src/lib/dns/tests/Makefile.am
+++ b/src/lib/dns/tests/Makefile.am
@@ -54,6 +54,7 @@ run_unittests_SOURCES += question_unittest.cc
run_unittests_SOURCES += rrparamregistry_unittest.cc
run_unittests_SOURCES += masterload_unittest.cc
run_unittests_SOURCES += message_unittest.cc
+run_unittests_SOURCES += serial_unittest.cc
run_unittests_SOURCES += tsig_unittest.cc
run_unittests_SOURCES += tsigerror_unittest.cc
run_unittests_SOURCES += tsigkey_unittest.cc
diff --git a/src/lib/dns/tests/rdata_soa_unittest.cc b/src/lib/dns/tests/rdata_soa_unittest.cc
index 17498eb..07c24d5 100644
--- a/src/lib/dns/tests/rdata_soa_unittest.cc
+++ b/src/lib/dns/tests/rdata_soa_unittest.cc
@@ -76,7 +76,7 @@ TEST_F(Rdata_SOA_Test, toText) {
}
TEST_F(Rdata_SOA_Test, getSerial) {
- EXPECT_EQ(2010012601, rdata_soa.getSerial());
+ EXPECT_EQ(2010012601, rdata_soa.getSerial().getValue());
}
}
diff --git a/src/lib/dns/tests/serial_unittest.cc b/src/lib/dns/tests/serial_unittest.cc
new file mode 100644
index 0000000..128c92d
--- /dev/null
+++ b/src/lib/dns/tests/serial_unittest.cc
@@ -0,0 +1,179 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <gtest/gtest.h>
+
+#include <dns/serial.h>
+
+using namespace isc::dns;
+
+class SerialTest : public ::testing::Test {
+public:
+ SerialTest() : one(1), one_2(1), two(2),
+ date_zero(1980120100), date_one(1980120101),
+ min(0), max(4294967295u),
+ number_low(12345),
+ number_medium(2000000000),
+ number_high(4000000000u)
+ {}
+ Serial one, one_2, two, date_zero, date_one, min, max, number_low, number_medium, number_high;
+};
+
+//
+// Basic tests
+//
+
+TEST_F(SerialTest, get_value) {
+ EXPECT_EQ(1, one.getValue());
+ EXPECT_NE(2, one.getValue());
+ EXPECT_EQ(2, two.getValue());
+ EXPECT_EQ(1980120100, date_zero.getValue());
+ EXPECT_EQ(1980120101, date_one.getValue());
+ EXPECT_EQ(0, min.getValue());
+ EXPECT_EQ(4294967295u, max.getValue());
+ EXPECT_EQ(12345, number_low.getValue());
+ EXPECT_EQ(2000000000, number_medium.getValue());
+ EXPECT_EQ(4000000000u, number_high.getValue());
+}
+
+TEST_F(SerialTest, equals) {
+ EXPECT_EQ(one, one);
+ EXPECT_EQ(one, one);
+ EXPECT_EQ(one, one_2);
+ EXPECT_NE(one, two);
+ EXPECT_NE(two, one);
+ EXPECT_EQ(Serial(12345), number_low);
+ EXPECT_NE(Serial(12346), number_low);
+}
+
+TEST_F(SerialTest, comparison) {
+ // These should be true/false even without serial arithmetic
+ EXPECT_LE(one, one);
+ EXPECT_LE(one, one_2);
+ EXPECT_LT(one, two);
+ EXPECT_LE(one, two);
+ EXPECT_GE(two, two);
+ EXPECT_GT(two, one);
+ EXPECT_LT(one, number_low);
+ EXPECT_LT(number_low, number_medium);
+ EXPECT_LT(number_medium, number_high);
+
+ // now let's try some that 'wrap', as it were
+ EXPECT_GT(min, max);
+ EXPECT_LT(max, min);
+ EXPECT_LT(number_high, number_low);
+}
+
+//
+// RFC 1982 Section 3.1
+//
+TEST_F(SerialTest, addition) {
+ EXPECT_EQ(two, one + one);
+ EXPECT_EQ(two, one + one_2);
+ EXPECT_EQ(max, max + min);
+ EXPECT_EQ(min, max + one);
+ EXPECT_EQ(one, max + two);
+ EXPECT_EQ(one, max + one + one);
+
+ EXPECT_EQ(one + 100, max + 102);
+ EXPECT_EQ(min + 2147483645, max + 2147483646);
+ EXPECT_EQ(min + 2147483646, max + MAX_SERIAL_INCREMENT);
+}
+
+//
+// RFC 1982 Section 3.2 has been checked by the basic tests above
+//
+
+//
+// RFC 1982 Section 4.1
+//
+
+// Helper function for addition_always_larger test, add some numbers
+// and check that the result is always larger than the original
+void do_addition_larger_test(const Serial& number) {
+ EXPECT_GE(number + 0, number);
+ EXPECT_EQ(number + 0, number);
+ EXPECT_GT(number + 1, number);
+ EXPECT_GT(number + 2, number);
+ EXPECT_GT(number + 100, number);
+ EXPECT_GT(number + 1111111, number);
+ EXPECT_GT(number + 2147483646, number);
+ EXPECT_GT(number + MAX_SERIAL_INCREMENT, number);
+ // Try MAX_SERIAL_INCREMENT as a hardcoded number as well
+ EXPECT_GT(number + 2147483647, number);
+}
+
+TEST_F(SerialTest, addition_always_larger) {
+ do_addition_larger_test(one);
+ do_addition_larger_test(two);
+ do_addition_larger_test(date_zero);
+ do_addition_larger_test(date_one);
+ do_addition_larger_test(min);
+ do_addition_larger_test(max);
+ do_addition_larger_test(number_low);
+ do_addition_larger_test(number_medium);
+ do_addition_larger_test(number_high);
+}
+
+//
+// RFC 1982 Section 4.2
+//
+
+// Helper function to do the second addition
+void
+do_two_additions_test_second(const Serial &original,
+ const Serial &number)
+{
+ EXPECT_NE(original, number);
+ EXPECT_NE(original, number + 0);
+ EXPECT_NE(original, number + 1);
+ EXPECT_NE(original, number + 2);
+ EXPECT_NE(original, number + 100);
+ EXPECT_NE(original, number + 1111111);
+ EXPECT_NE(original, number + 2147483646);
+ EXPECT_NE(original, number + MAX_SERIAL_INCREMENT);
+ EXPECT_NE(original, number + 2147483647);
+}
+
+void do_two_additions_test_first(const Serial &number) {
+ do_two_additions_test_second(number, number + 1);
+ do_two_additions_test_second(number, number + 2);
+ do_two_additions_test_second(number, number + 100);
+ do_two_additions_test_second(number, number + 1111111);
+ do_two_additions_test_second(number, number + 2147483646);
+ do_two_additions_test_second(number, number + MAX_SERIAL_INCREMENT);
+ do_two_additions_test_second(number, number + 2147483647);
+}
+
+TEST_F(SerialTest, two_additions_never_equal) {
+ do_two_additions_test_first(one);
+ do_two_additions_test_first(two);
+ do_two_additions_test_first(date_zero);
+ do_two_additions_test_first(date_one);
+ do_two_additions_test_first(min);
+ do_two_additions_test_first(max);
+ do_two_additions_test_first(number_low);
+ do_two_additions_test_first(number_medium);
+ do_two_additions_test_first(number_high);
+}
+
+//
+// RFC 1982 Section 4.3 and 4.4 have nothing to test
+//
+
+//
+// Tests from RFC 1982 examples
+//
+TEST(SerialTextRFCExamples, rfc_example_tests) {
+}
diff --git a/src/lib/python/isc/bind10/special_component.py b/src/lib/python/isc/bind10/special_component.py
index 9972200..dad10bb 100644
--- a/src/lib/python/isc/bind10/special_component.py
+++ b/src/lib/python/isc/bind10/special_component.py
@@ -108,16 +108,6 @@ class CmdCtl(Component):
Component.__init__(self, process, boss, kind, 'Cmdctl', None,
boss.start_cmdctl)
-class XfrIn(Component):
- def __init__(self, process, boss, kind, address=None, params=None):
- Component.__init__(self, process, boss, kind, 'Xfrin', None,
- boss.start_xfrin)
-
-class XfrOut(Component):
- def __init__(self, process, boss, kind, address=None, params=None):
- Component.__init__(self, process, boss, kind, 'Xfrout', None,
- boss.start_xfrout)
-
class SetUID(BaseComponent):
"""
This is a pseudo-component which drops root privileges when started
@@ -157,9 +147,6 @@ def get_specials():
'auth': Auth,
'resolver': Resolver,
'cmdctl': CmdCtl,
- # FIXME: Temporary workaround before #1292 is done
- 'xfrin': XfrIn,
- 'xfrout': XfrOut,
# TODO: Remove when not needed, workaround before sockcreator works
'setuid': SetUID
}
diff --git a/src/lib/python/isc/bind10/tests/component_test.py b/src/lib/python/isc/bind10/tests/component_test.py
index 7e18e60..3b49b18 100644
--- a/src/lib/python/isc/bind10/tests/component_test.py
+++ b/src/lib/python/isc/bind10/tests/component_test.py
@@ -86,9 +86,6 @@ class BossUtils:
def start_cmdctl(self):
pass
- def start_xfrin(self):
- pass
-
class ComponentTests(BossUtils, unittest.TestCase):
"""
Tests for the bind10.component.Component class
@@ -511,7 +508,6 @@ class ComponentTests(BossUtils, unittest.TestCase):
isc.bind10.special_component.Auth,
isc.bind10.special_component.Resolver,
isc.bind10.special_component.CmdCtl,
- isc.bind10.special_component.XfrIn,
isc.bind10.special_component.SetUID]:
component = component_type('none', self, 'needed')
self.assertIsNone(component.pid())
diff --git a/src/lib/python/isc/datasrc/tests/Makefile.am b/src/lib/python/isc/datasrc/tests/Makefile.am
index 400abcf..ab89b93 100644
--- a/src/lib/python/isc/datasrc/tests/Makefile.am
+++ b/src/lib/python/isc/datasrc/tests/Makefile.am
@@ -34,5 +34,6 @@ endif
PYTHONPATH=:$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/python/isc/log:$(abs_top_builddir)/src/lib/python/isc/datasrc/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs \
TESTDATA_PATH=$(abs_srcdir)/testdata \
TESTDATA_WRITE_PATH=$(abs_builddir) \
+ B10_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/lib/python/isc/notify/tests/Makefile.am b/src/lib/python/isc/notify/tests/Makefile.am
index 6b62b90..3af5991 100644
--- a/src/lib/python/isc/notify/tests/Makefile.am
+++ b/src/lib/python/isc/notify/tests/Makefile.am
@@ -29,5 +29,6 @@ endif
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/dns/python/.libs \
$(LIBRARY_PATH_PLACEHOLDER) \
TESTDATASRCDIR=$(abs_top_srcdir)/src/lib/python/isc/notify/tests/testdata/ \
+ B10_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/lib/python/isc/testutils/rrset_utils.py b/src/lib/python/isc/testutils/rrset_utils.py
index 8c22d92..70cee01 100644
--- a/src/lib/python/isc/testutils/rrset_utils.py
+++ b/src/lib/python/isc/testutils/rrset_utils.py
@@ -53,6 +53,19 @@ def create_ns(nsname, name=Name('example.com'), ttl=3600):
rrset.add_rdata(Rdata(RRType.NS(), RRClass.IN(), nsname))
return rrset
+def create_generic(name, rdlen, type=RRType('TYPE65300'), ttl=3600):
+ '''Create an RR of a general type with an arbitrary length of RDATA
+
+ If the RR type isn't specified, type of 65300 will be used, which is
+ arbitrarily chosen from the IANA "Reserved for Private Usage" range.
+ The RDATA will be filled with specified length of all-0 data.
+
+ '''
+ rrset = RRset(name, RRClass.IN(), type, RRTTL(ttl))
+ rrset.add_rdata(Rdata(type, RRClass.IN(), '\\# ' +
+ str(rdlen) + ' ' + '00' * rdlen))
+ return rrset
+
def create_soa(serial, name=Name('example.com'), ttl=3600):
'''For convenience we use a default name often used as a zone name'''
diff --git a/tests/lettuce/features/terrain/bind10_control.py b/tests/lettuce/features/terrain/bind10_control.py
index fbbb3a2..5248316 100644
--- a/tests/lettuce/features/terrain/bind10_control.py
+++ b/tests/lettuce/features/terrain/bind10_control.py
@@ -137,5 +137,8 @@ def send_command(step, command, cmdctl_port):
subprocess.PIPE, None)
bindctl.stdin.write(command + "\n")
bindctl.stdin.write("quit\n")
- result = bindctl.wait()
- assert result == 0, "bindctl exit code: " + str(result)
+ (stdout, stderr) = bindctl.communicate()
+ result = bindctl.returncode
+ assert result == 0, "bindctl exit code: " + str(result) +\
+ "\nstdout:\n" + str(stdout) +\
+ "stderr:\n" + str(stderr)
diff --git a/tests/lettuce/features/xfrin_bind10.feature b/tests/lettuce/features/xfrin_bind10.feature
index 23b2eda..70c3571 100644
--- a/tests/lettuce/features/xfrin_bind10.feature
+++ b/tests/lettuce/features/xfrin_bind10.feature
@@ -5,6 +5,7 @@ Feature: Xfrin
Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
And I have bind10 running with configuration xfrin/retransfer_slave.conf
A query for www.example.org should have rcode REFUSED
+ Wait for bind10 stderr message CMDCTL_STARTED
When I send bind10 the command Xfrin retransfer example.org IN 127.0.0.1 47807
Then wait for new bind10 stderr message XFRIN_XFR_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
A query for www.example.org should have rcode NOERROR
More information about the bind10-changes
mailing list