├── .gitignore
├── tests
├── Parser
│ ├── Resources
│ │ ├── testMultilineTxtRecords_expectation.txt
│ │ ├── testKeepCommentsWithoutLinefeedAtEnd_expectation.txt
│ │ ├── testKeepCommentsWithoutLinefeedAtEnd_sample.txt
│ │ ├── testMultilineTxtRecords_sample.txt
│ │ ├── ambiguous.acme.org.txt
│ │ ├── IncludeControlEntryTests
│ │ │ ├── _subdomain.mydomain.biz.db
│ │ │ ├── mydomain.biz.db
│ │ │ ├── testdomain.geek_expectation.db
│ │ │ ├── testdomain.geek - extra info.db
│ │ │ ├── mydomain.biz_expectation.db
│ │ │ └── testdomain.geek.db
│ │ ├── testClearComments_expectation.txt
│ │ ├── testKeepComments_expectation.txt
│ │ ├── testOriginDot_sample.txt
│ │ ├── multipleOrigins.txt
│ │ ├── testCollapseMultilines_expectation.txt
│ │ ├── testOriginDot_expectation.txt
│ │ ├── multipleOrigins_expectation.txt
│ │ ├── testClearComments_sample.txt
│ │ ├── testCollapseMultilinesWithComments_expectation.txt
│ │ ├── 50.100.200.in-addr.arpa.db
│ │ ├── testCollapseMultilines_sample.txt
│ │ └── testConvolutedZone_sample.txt
│ ├── CustomHandlerTest.php
│ ├── ResourceRecordIteratorTest.php
│ ├── ParseUnknownTypesTest.php
│ └── ReverseRecordTest.php
├── Resources
│ ├── wire
│ │ ├── wire_test.data0
│ │ ├── wire_test.data8
│ │ ├── wire_test.data7
│ │ ├── wire_test.data2
│ │ ├── wire_test.data3
│ │ ├── wire_test.data1
│ │ └── wire_test.data5
│ ├── google.com.cer
│ └── example.com_filled-out.txt
├── Rdata
│ ├── RdataTraitTest.php
│ ├── TypesTest.php
│ ├── CdnskeyTest.php
│ ├── FactoryTest.php
│ ├── NsTest.php
│ ├── UnknownTypeTest.php
│ ├── CnameTest.php
│ ├── DnameTest.php
│ ├── SigTest.php
│ ├── SpfTest.php
│ ├── RpTest.php
│ ├── CdsTest.php
│ ├── DlvTest.php
│ ├── OptTest.php
│ ├── TaTest.php
│ ├── MxTest.php
│ ├── HinfoTest.php
│ ├── AfsdbTest.php
│ ├── PtrTest.php
│ ├── KxTest.php
│ ├── SrvTest.php
│ ├── NsecTest.php
│ ├── DsTest.php
│ ├── KeyTest.php
│ ├── Nsec3paramTest.php
│ ├── TxtTest.php
│ ├── ATest.php
│ └── DnskeyTest.php
├── ZoneBuilderTest.php
├── Edns
│ └── Option
│ │ ├── CodesTest.php
│ │ ├── FactoryTest.php
│ │ ├── UnknownOptionTest.php
│ │ ├── CLIENT_SUBNETTest.php
│ │ ├── TCP_KEEPALIVETest.php
│ │ └── COOKIETest.php
├── AlgorithmsTest.php
├── ClassesTest.php
└── QuestionTest.php
├── phpstan.neon
├── lib
├── UnsetValueException.php
├── Rdata
│ ├── SIG.php
│ ├── CDS.php
│ ├── SPF.php
│ ├── TA.php
│ ├── NS.php
│ ├── CDNSKEY.php
│ ├── DLV.php
│ ├── UnsupportedTypeException.php
│ ├── DNAME.php
│ ├── RdataTrait.php
│ ├── DecodeException.php
│ ├── DNSKEY.php
│ ├── AAAA.php
│ ├── CNAME.php
│ ├── RdataInterface.php
│ ├── RP.php
│ ├── A.php
│ ├── PolymorphicRdata.php
│ ├── AFSDB.php
│ ├── HINFO.php
│ ├── UnknownType.php
│ ├── KX.php
│ ├── MX.php
│ ├── CSYNC.php
│ └── OPT.php
├── Edns
│ └── Option
│ │ ├── UnsupportedOptionException.php
│ │ ├── OptionTrait.php
│ │ ├── DecodeException.php
│ │ ├── OptionInterface.php
│ │ ├── TCP_KEEPALIVE.php
│ │ ├── UnknownOption.php
│ │ ├── Factory.php
│ │ ├── COOKIE.php
│ │ └── Codes.php
├── Parser
│ ├── Tokens.php
│ ├── ZoneFileFetcherInterface.php
│ ├── Comments.php
│ ├── ParseException.php
│ ├── ResourceRecordIterator.php
│ ├── StringIterator.php
│ └── TimeFormat.php
├── Opcode.php
├── Classes.php
└── Rcode.php
├── phpunit.xml.dist
├── composer.json
├── .php-cs-fixer.dist.php
├── .github
└── workflows
│ └── PHP-8.yml
├── LICENSE
└── docs
├── Parser
├── Custom-Rdata-Handlers.md
├── Basic-Usage.md
├── Include-Directive.md
└── Unkown-Types.md
├── Validation.md
├── Message.md
├── Reverse-Records.md
└── Zone.md
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
2 | phpunit.xml
3 | composer.lock
4 | coverage.clover
5 | *.cache
6 |
--------------------------------------------------------------------------------
/tests/Parser/Resources/testMultilineTxtRecords_expectation.txt:
--------------------------------------------------------------------------------
1 | test IN 7230 TXT "This is an " "example of a " "multiline TXT " "record.";This is a comment.
--------------------------------------------------------------------------------
/tests/Parser/Resources/testKeepCommentsWithoutLinefeedAtEnd_expectation.txt:
--------------------------------------------------------------------------------
1 | $ORIGIN example.com.
2 | $TTL 3600
3 | sub.domain A 192.168.1.42;This is a local ip.
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: max
3 | checkMissingIterableValueType: false
4 | checkGenericClassInNonGenericObjectType: false
5 | paths:
6 | - lib/
--------------------------------------------------------------------------------
/tests/Parser/Resources/testKeepCommentsWithoutLinefeedAtEnd_sample.txt:
--------------------------------------------------------------------------------
1 | $ORIGIN example.com.
2 | $TTL 3600
3 |
4 | ; A RECORDS
5 | sub.domain A 192.168.1.42 ; This is a local ip.
--------------------------------------------------------------------------------
/tests/Parser/Resources/testMultilineTxtRecords_sample.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 | test IN 7230 TXT "This is an "(
4 |
5 | "example of a "
6 | "multiline TXT "
7 | )"record." ;This is a comment.
8 |
9 |
--------------------------------------------------------------------------------
/tests/Parser/Resources/ambiguous.acme.org.txt:
--------------------------------------------------------------------------------
1 | $ORIGIN ambiguous.acme.org.
2 |
3 | ambiguous.acme.org. IN SOA ambiguous.acme.org. post.acme.org. 2019092401 3600 14400 604800 1800
4 |
5 | mx 900 IN A 200.100.50.35
6 | aaaa IN AAAA 2001:acdc:5889::35
7 | 3600 TXT "This name is silly."
8 | mx cname aaaa
9 | IN TXT "Mail Exchange IPv6 Address"
10 |
--------------------------------------------------------------------------------
/tests/Parser/Resources/IncludeControlEntryTests/_subdomain.mydomain.biz.db:
--------------------------------------------------------------------------------
1 | $TTL 7200
2 |
3 | @ MX (
4 | 10
5 | mail-gw1.example.net.
6 | ) ;The first mail gateway
7 | @ MX 20 mail-gw2.example.net.
8 | @ MX 30 mail-gw3.example.net.
9 | mail IN TXT "THIS IS SOME TEXT; WITH A SEMICOLON"
10 |
11 | $ORIGIN some_other_subdomain.mydomain.biz.
12 |
--------------------------------------------------------------------------------
/tests/Resources/wire/wire_test.data0:
--------------------------------------------------------------------------------
1 | # MESSAGE HEADER
2 | 0x00 0x2a #id=42
3 | 0x00 # QR=0 (query) OPCODE=0 AA=0 TC=0 RD=0
4 | 0x00 # RA=0 BIT9=0 AD=0 CD=0 RCODE=0
5 | 0x00 0x01 #QDCOUNT
6 | 0x00 0x00 #ANCOUNT
7 | 0x00 0x00 #NSCOUNT
8 | 0x00 0x00 #ARCOUNT
9 |
10 | # QUESTION SECTION
11 | 0x03 0x66 0x6f 0x6f 0x03 0x62 0x61 0x72 0x03 0x63 0x6f 0x6d 0x00 0x00 0x01 0x00 0x01 # foo.bar.com. A IN
12 |
13 |
--------------------------------------------------------------------------------
/tests/Parser/Resources/testClearComments_expectation.txt:
--------------------------------------------------------------------------------
1 | $ORIGIN example.com.
2 | $TTL 3600
3 | @ NS ns1.nameserver.com.
4 | @ NS ns2.nameserver.com.
5 | info TXT "This is some additional \"information\""
6 | sub.domain A 192.168.1.42
7 | ipv6.domain AAAA ::1
8 | @ MX 10 mail-gw1.example.net.
9 | @ MX 20 mail-gw2.example.net.
10 | @ MX 30 mail-gw3.example.net.
11 | mail IN TXT "THIS IS SOME TEXT; WITH A SEMICOLON"
--------------------------------------------------------------------------------
/lib/UnsetValueException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS;
15 |
16 | class UnsetValueException extends \Exception
17 | {
18 | }
19 |
--------------------------------------------------------------------------------
/tests/Resources/wire/wire_test.data8:
--------------------------------------------------------------------------------
1 | # HEADER SECTION
2 | 30 99 # ID
3 | 01 20 # FLAGS
4 | 00 01 # QDCOUNT=1
5 | 00 00 # ANCOUNT=0
6 | 00 00 # NSCOUNT=0
7 | 00 01 # ARCOUNT=1
8 |
9 | #QUESTION SECTION
10 | 05 67 6f 6f 67 65 03 63 6f 6d 00 00 01 00 01 # question section: google.com IN A
11 |
12 | #ADDITIONAL RECORDS
13 | 00 00 29 10 00 00 00 00 00 00 1c 00 08 00 08 00 01 20 00 c0 a8 01 01 00 0a 00 08 a8 51 73 83 92 4c a2 1e 00 0b 00 00
14 |
--------------------------------------------------------------------------------
/tests/Resources/wire/wire_test.data7:
--------------------------------------------------------------------------------
1 | # HEADER SECTION
2 | 0xcf 0xcf # ID
3 | 0x01 0x20 # FLAGS
4 | 0x00 0x01 # QDCOUNT=1
5 | 0x00 0x00 # ANCOUNT=0
6 | 0x00 0x00 # NSCOUNT=0
7 | 0x00 0x01 # ARCOUNT=1
8 |
9 | #QUESTION SECTION
10 | 0x09 0x6a 0x65 0x79 0x73 0x65 0x72 0x76 0x65 0x72 0x03 0x63 0x6f 0x6d 0x00
11 |
12 | #ADDITIONAL RECORDS
13 | 0x00 0x01 0x00 0x01 0x00 0x01 0x29 0x10 0x00 0x00 0x00 0x00 0x00 0x00 0x0c 0x00 0x0a 0x00 0x08 0x29 0x22 0xfd 0x1c 0xe5 0xc0 0x21 0xd1
14 |
--------------------------------------------------------------------------------
/lib/Rdata/SIG.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | class SIG extends RRSIG
17 | {
18 | public const TYPE = 'SIG';
19 | public const TYPE_CODE = 24;
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Parser/Resources/testKeepComments_expectation.txt:
--------------------------------------------------------------------------------
1 | $ORIGIN example.com.
2 | $TTL 3600
3 | @ NS ns1.nameserver.com.
4 | @ NS ns2.nameserver.com.
5 | info TXT "This is some additional \"information\""
6 | sub.domain A 192.168.1.42;This is a local ip.
7 | ipv6.domain AAAA ::1;This is an IPv6 domain.
8 | @ MX 10 mail-gw1.example.net.;This is the primary sever; and some more stuff.
9 | @ MX 20 mail-gw2.example.net.
10 | @ MX 30 mail-gw3.example.net.
11 | mail IN TXT "THIS IS SOME TEXT; WITH A SEMICOLON"
--------------------------------------------------------------------------------
/tests/Parser/Resources/testOriginDot_sample.txt:
--------------------------------------------------------------------------------
1 | $ORIGIN .
2 | $TTL 3600
3 |
4 | otherdomain.biz IN SOA otherdomain.biz. post.otherdomain.biz. 2014110501 3600 14400 604800 3600
5 |
6 | otherdomain.biz NS ns1.nameserver.com.
7 | otherdomain.biz NS ns2.nameserver.com.
8 |
9 | info.otherdomain.biz TXT "This is some additional \"information\""
10 | sub.domain.otherdomain.biz A 192.168.1.42
11 | ipv6.domain.otherdomain.biz AAAA ::1
12 |
13 | mail.otherdomain.biz IN CNAME mx1.bizmail.
14 | otherdomain.biz IN MX 10 mail
15 |
--------------------------------------------------------------------------------
/tests/Parser/Resources/IncludeControlEntryTests/mydomain.biz.db:
--------------------------------------------------------------------------------
1 | $ORIGIN mydomain.biz.
2 | $TTL 3600
3 |
4 | @ IN SOA mydomain.biz. post.mydomain.biz. 2014110501 3600 14400 604800 3600
5 |
6 | @ NS ns1.nameserver.com.
7 | @ NS ns2.nameserver.com.
8 |
9 | info TXT "This is some additional \"information\""
10 | sub.domain A 192.168.1.42
11 | ipv6.domain AAAA ::1
12 |
13 | $INCLUDE _subdomain.mydomain.biz.db _subdomain.mydomain.biz. ;include the subdomain _subdomain.mydomain.biz.
14 |
15 | mail IN CNAME mx1.bizmail.
16 | @ IN MX 10 mail
--------------------------------------------------------------------------------
/tests/Parser/Resources/multipleOrigins.txt:
--------------------------------------------------------------------------------
1 | $ORIGIN mydomain.biz.
2 | $TTL 3600
3 |
4 | @ IN SOA mydomain.biz. post.mydomain.biz. 2014110501 3600 14400 604800 3600
5 |
6 | @ NS ns1.nameserver.com.
7 | @ NS ns2.nameserver.com.
8 |
9 | info TXT "This is some additional \"information\""
10 | sub.domain A 192.168.1.42
11 | ipv6.domain AAAA ::1
12 |
13 | $ORIGIN _subdomain.mydomain.biz.
14 |
15 | @ MX 10 mail-gw1.example.net.
16 | @ MX 20 mail-gw2.example.net.
17 | @ MX 30 mail-gw3.example.net.
18 | mail IN TXT "THIS IS SOME TEXT; WITH A SEMICOLON"
--------------------------------------------------------------------------------
/lib/Rdata/CDS.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | /**
17 | * {@link https://tools.ietf.org/html/rfc7344}.
18 | */
19 | class CDS extends DS
20 | {
21 | public const TYPE = 'CDS';
22 | public const TYPE_CODE = 59;
23 | }
24 |
--------------------------------------------------------------------------------
/lib/Rdata/SPF.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | /**
17 | * {@link https://tools.ietf.org/html/rfc7208}.
18 | */
19 | class SPF extends TXT
20 | {
21 | public const TYPE = 'SPF';
22 | public const TYPE_CODE = 99;
23 | }
24 |
--------------------------------------------------------------------------------
/lib/Rdata/TA.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | /**
17 | * {@link http://www.watson.org/~weiler/INI1999-19.pdf}.
18 | */
19 | class TA extends DS
20 | {
21 | public const TYPE = 'TA';
22 | public const TYPE_CODE = 32768;
23 | }
24 |
--------------------------------------------------------------------------------
/lib/Rdata/NS.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | /**
17 | * @see https://tools.ietf.org/html/rfc1035#section-3.3.11
18 | */
19 | class NS extends CNAME
20 | {
21 | public const TYPE = 'NS';
22 | public const TYPE_CODE = 2;
23 | }
24 |
--------------------------------------------------------------------------------
/lib/Rdata/CDNSKEY.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | /**
17 | * {@link https://tools.ietf.org/html/rfc7344#page-7}.
18 | */
19 | class CDNSKEY extends DNSKEY
20 | {
21 | public const TYPE = 'CDNSKEY';
22 | public const TYPE_CODE = 60;
23 | }
24 |
--------------------------------------------------------------------------------
/lib/Rdata/DLV.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | /**
17 | * {@link https://tools.ietf.org/html/rfc4431}.
18 | */
19 | class DLV extends DS
20 | {
21 | use RdataTrait;
22 |
23 | public const TYPE = 'DLV';
24 | public const TYPE_CODE = 32769;
25 | }
26 |
--------------------------------------------------------------------------------
/lib/Rdata/UnsupportedTypeException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | class UnsupportedTypeException extends \Exception
17 | {
18 | public function __construct(string $type)
19 | {
20 | parent::__construct(sprintf('Rdata "%s" is not implemented.', $type));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Parser/Resources/testCollapseMultilines_expectation.txt:
--------------------------------------------------------------------------------
1 | $ORIGIN example.com.
2 | $TTL 1337
3 | $INCLUDE hq.example.com.txt
4 | @ IN SOA example.com. post.example.com. 2014110501 3600 14400 604800 3600
5 | @ NS ns1.nameserver.com.
6 | @ NS ns2.nameserver.com.
7 | info TXT "This is some additional \"information\""
8 | sub.domain A 192.168.1.42
9 | ipv6.domain AAAA ::1
10 | @ MX 10 mail-gw1.example.net.
11 | @ MX 20 mail-gw2.example.net.
12 | @ MX 30 mail-gw3.example.net.
13 | mail IN TXT "THIS IS SOME TEXT; WITH A SEMICOLON"
14 | multicast APL 1:192.168.0.0/23 2:2001:acad:1::/112 !1:192.168.1.64/28 !2:2001:acad:1::8/128
--------------------------------------------------------------------------------
/lib/Edns/Option/UnsupportedOptionException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Edns\Option;
15 |
16 | class UnsupportedOptionException extends \Exception
17 | {
18 | public function __construct(string $option)
19 | {
20 | parent::__construct(sprintf('Option "%s" is not implemented.', $option));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Parser/Resources/testOriginDot_expectation.txt:
--------------------------------------------------------------------------------
1 | $ORIGIN otherdomain.biz.
2 | $TTL 3600
3 | otherdomain.biz. 3600 IN SOA otherdomain.biz. post.otherdomain.biz. 2014110501 3600 14400 604800 3600
4 | otherdomain.biz. 3600 IN NS ns1.nameserver.com.
5 | otherdomain.biz. 3600 IN NS ns2.nameserver.com.
6 | info.otherdomain.biz. 3600 IN TXT "This is some additional \"information\""
7 | sub.domain.otherdomain.biz. 3600 IN A 192.168.1.42
8 | ipv6.domain.otherdomain.biz. 3600 IN AAAA 0000:0000:0000:0000:0000:0000:0000:0001
9 | mail.otherdomain.biz. 3600 IN CNAME mx1.bizmail.
10 | otherdomain.biz. 3600 IN MX 10 mail.otherdomain.biz.
11 |
--------------------------------------------------------------------------------
/tests/Parser/Resources/multipleOrigins_expectation.txt:
--------------------------------------------------------------------------------
1 | $ORIGIN mydomain.biz.
2 | $TTL 3600
3 | @ 3600 IN SOA mydomain.biz. post.mydomain.biz. 2014110501 3600 14400 604800 3600
4 | @ 3600 IN NS ns1.nameserver.com.
5 | @ 3600 IN NS ns2.nameserver.com.
6 | info 3600 IN TXT "This is some additional \"information\""
7 | sub.domain 3600 IN A 192.168.1.42
8 | ipv6.domain 3600 IN AAAA ::1
9 | _subdomain.mydomain.biz. 3600 IN MX 10 mail-gw1.example.net.
10 | _subdomain.mydomain.biz. 3600 IN MX 20 mail-gw2.example.net.
11 | _subdomain.mydomain.biz. 3600 IN MX 30 mail-gw3.example.net.
12 | mail._subdomain.mydomain.biz. 3600 IN TXT "THIS IS SOME TEXT; WITH A SEMICOLON"
13 |
--------------------------------------------------------------------------------
/lib/Edns/Option/OptionTrait.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Edns\Option;
15 |
16 | trait OptionTrait
17 | {
18 | public function getName(): string
19 | {
20 | /* @const NAME */
21 | return static::NAME;
22 | }
23 |
24 | public function getCode(): int
25 | {
26 | /* @const TYPE_CODE */
27 | return static::CODE;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/Parser/Resources/IncludeControlEntryTests/testdomain.geek_expectation.db:
--------------------------------------------------------------------------------
1 | $ORIGIN testdomain.geek.
2 | $TTL 7200
3 | @ 1337 IN SOA testdomain.geek. post.testdomain.geek. 2014110501 3600 14400 604800 3600
4 | @ 1337 IN NS ns1.nameserver.com.
5 | @ 1337 IN NS ns2.nameserver.com.
6 | info 7200 IN TXT "This is some additional \"information\""
7 | sub.domain 7200 IN A 192.168.1.42
8 | ipv6.domain 7200 IN AAAA ::1
9 | @ 7200 IN MX 10 mail-gw1.example.net.
10 | @ 7200 IN MX 20 mail-gw2.example.net.
11 | @ 7200 IN MX 30 mail-gw3.example.net.
12 | mail 7200 IN TXT "THIS IS SOME TEXT; WITH A SEMICOLON"
13 | multicast 7200 IN APL 1:192.168.0.0/23 2:2001:acad:1::/112 !1:192.168.1.64/28 !2:2001:acad:1::8/128
14 |
--------------------------------------------------------------------------------
/tests/Parser/Resources/IncludeControlEntryTests/testdomain.geek - extra info.db:
--------------------------------------------------------------------------------
1 | $ORIGIN testdomain.geek.
2 | $TTL 1337
3 | @ IN SOA (
4 | testdomain.geek. ; MNAME
5 | post.testdomain.geek. ; RNAME
6 | 2014110501 ; SERIAL
7 | 3600 ; REFRESH
8 | 14400 ; RETRY
9 | 604800 ; EXPIRE
10 | 3600 ; MINIMUM
11 | ); This is my Start of Authority Record; AKA SOA.
12 |
13 | ; NS RECORDS
14 | @ NS ns1.nameserver.com.
15 | @ NS ns2.nameserver.com.
16 |
--------------------------------------------------------------------------------
/tests/Parser/Resources/testClearComments_sample.txt:
--------------------------------------------------------------------------------
1 | $ORIGIN example.com.
2 | $TTL 3600
3 |
4 | ; NS RECORDS
5 | @ NS ns1.nameserver.com.
6 | @ NS ns2.nameserver.com.
7 |
8 | info TXT "This is some additional \"information\""
9 |
10 | ; A RECORDS
11 | sub.domain A 192.168.1.42 ; This is a local ip.
12 |
13 | ; AAAA RECORDS
14 | ipv6.domain AAAA ::1 ; This is an IPv6 domain.
15 |
16 | ; MX RECORDS
17 | @ MX 10 mail-gw1.example.net.;This is the primary sever; and some more stuff.
18 | @ MX 20 mail-gw2.example.net.
19 | @ MX 30 mail-gw3.example.net.
20 |
21 | mail IN TXT "THIS IS SOME TEXT; WITH A SEMICOLON"
22 |
--------------------------------------------------------------------------------
/tests/Rdata/RdataTraitTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Rdata\RdataTrait;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class RdataTraitTest extends TestCase
20 | {
21 | use RdataTrait;
22 |
23 | public const TYPE = 'RDATA_TEST';
24 |
25 | public function testGetType(): void
26 | {
27 | $this->assertEquals(self::TYPE, $this->getType());
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lib/Parser/Tokens.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Parser;
15 |
16 | class Tokens
17 | {
18 | public const BACKSLASH = '\\';
19 | public const CARRIAGE_RETURN = "\r";
20 | public const CLOSE_BRACKET = ')';
21 | public const DOUBLE_QUOTES = '"';
22 | public const LINE_FEED = "\n";
23 | public const OPEN_BRACKET = '(';
24 | public const SEMICOLON = ';';
25 | public const SPACE = ' ';
26 | public const TAB = "\t";
27 | }
28 |
--------------------------------------------------------------------------------
/tests/ZoneBuilderTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests;
15 |
16 | use Badcow\DNS\ZoneBuilder;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class ZoneBuilderTest extends TestCase
20 | {
21 | public function testBuild(): void
22 | {
23 | $zone = TestZone::buildTestZone();
24 | $zoneBuilder = new ZoneBuilder();
25 | $this->assertEquals(TestZone::getExpectation(), $output = $zoneBuilder->build($zone));
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib/Edns/Option/DecodeException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Edns\Option;
15 |
16 | use Badcow\DNS\Rdata;
17 |
18 | class DecodeException extends \Exception
19 | {
20 | public function __construct(string $option, string $value, int $code = 0, ?\Throwable $previous = null)
21 | {
22 | $message = sprintf('Unable to decode %s option from binary data "%s"', $option, Rdata\DecodeException::binaryToHex($value));
23 | parent::__construct($message, $code, $previous);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/Parser/Resources/IncludeControlEntryTests/mydomain.biz_expectation.db:
--------------------------------------------------------------------------------
1 | $ORIGIN mydomain.biz.
2 | $TTL 3600
3 | @ 3600 IN SOA mydomain.biz. post.mydomain.biz. 2014110501 3600 14400 604800 3600
4 | @ 3600 IN NS ns1.nameserver.com.
5 | @ 3600 IN NS ns2.nameserver.com.
6 | info 3600 IN TXT "This is some additional \"information\""
7 | sub.domain 3600 IN A 192.168.1.42
8 | ipv6.domain 3600 IN AAAA ::1
9 | ; include the subdomain _subdomain.mydomain.biz.
10 | _subdomain.mydomain.biz. 7200 IN MX 10 mail-gw1.example.net.; The first mail gateway
11 | _subdomain.mydomain.biz. 7200 IN MX 20 mail-gw2.example.net.
12 | _subdomain.mydomain.biz. 7200 IN MX 30 mail-gw3.example.net.
13 | mail._subdomain.mydomain.biz. 7200 IN TXT "THIS IS SOME TEXT; WITH A SEMICOLON"
14 | mail 3600 IN CNAME mx1.bizmail.
15 | @ 3600 IN MX 10 mail
16 |
--------------------------------------------------------------------------------
/lib/Rdata/DNAME.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | /**
17 | * Class DnameRdata.
18 | *
19 | * The DNAME record provides redirection for a subtree of the domain
20 | * name tree in the DNS. That is, all names that end with a particular
21 | * suffix are redirected to another part of the DNS.
22 | * Based on RFC6672
23 | *
24 | * @see http://tools.ietf.org/html/rfc6672
25 | */
26 | class DNAME extends CNAME
27 | {
28 | public const TYPE = 'DNAME';
29 | public const TYPE_CODE = 39;
30 | }
31 |
--------------------------------------------------------------------------------
/tests/Parser/Resources/testCollapseMultilinesWithComments_expectation.txt:
--------------------------------------------------------------------------------
1 | $ORIGIN example.com.
2 | $TTL 1337
3 | $INCLUDE hq.example.com.txt
4 | @ IN SOA example.com. post.example.com. 2014110501 3600 14400 604800 3600;MNAME RNAME SERIAL REFRESH RETRY EXPIRE MINIMUM This is my Start of Authority Record; AKA SOA.
5 | ;NS RECORDS
6 | @ NS ns1.nameserver.com.
7 | @ NS ns2.nameserver.com.
8 | info TXT "This is some additional \"information\""
9 | ;A RECORDS
10 | sub.domain A 192.168.1.42;This is a local ip.
11 | ;AAAA RECORDS
12 | ipv6.domain AAAA ::1;This is an IPv6 domain.
13 | ;MX RECORDS
14 | @ MX 10 mail-gw1.example.net.
15 | @ MX 20 mail-gw2.example.net.
16 | @ MX 30 mail-gw3.example.net.
17 | mail IN TXT "THIS IS SOME TEXT; WITH A SEMICOLON"
18 | multicast APL 1:192.168.0.0/23 2:2001:acad:1::/112 !1:192.168.1.64/28 !2:2001:acad:1::8/128
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 | ./lib
11 |
12 |
13 | ./tests
14 | ./vendor
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | ./tests
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "badcow/dns",
3 | "description": "A PHP library for creating DNS zone files based on RFC1035",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Sam Williams",
8 | "email": "sam@badcow.co"
9 | }
10 | ],
11 | "minimum-stability": "stable",
12 | "require": {
13 | "php": "^8.0",
14 | "rlanvin/php-ip": "^3.0",
15 | "christian-riesen/base32": "^1.5.2"
16 | },
17 | "require-dev": {
18 | "phpunit/phpunit": "^9.0",
19 | "friendsofphp/php-cs-fixer": "^3.5.0",
20 | "phpstan/phpstan": "^1.4"
21 | },
22 | "autoload": {
23 | "psr-4": {
24 | "Badcow\\DNS\\": "lib/"
25 | }
26 | },
27 | "autoload-dev": {
28 | "psr-4": {
29 | "Badcow\\DNS\\Tests\\": "tests/"
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/Parser/ZoneFileFetcherInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Parser;
15 |
16 | /**
17 | * Interface ZoneFileFetcherInterface: used to get the contents of other zone files that are stated in the $INCLUDE directive.
18 | */
19 | interface ZoneFileFetcherInterface
20 | {
21 | /**
22 | * Fetches the contents of a zone file with a given path.
23 | *
24 | * @param string $path the path, relative or otherwise, to a zone file
25 | *
26 | * @return string the text contents of the zone file
27 | */
28 | public function fetch(string $path): string;
29 | }
30 |
--------------------------------------------------------------------------------
/tests/Parser/Resources/IncludeControlEntryTests/testdomain.geek.db:
--------------------------------------------------------------------------------
1 | $ORIGIN testdomain.geek.
2 | $TTL 7200
3 | $INCLUDE testdomain.geek\ -\ extra\ info.db ; This contains the SOA ans NS data
4 |
5 | info TXT "This is some additional \"information\""
6 |
7 | ; A RECORDS
8 | sub.domain A 192.168.1.42 ; This is a local ip.
9 |
10 | ; AAAA RECORDS
11 | ipv6.domain AAAA ::1 ; This is an IPv6 domain.
12 |
13 | ; MX RECORDS
14 | @ MX 10 mail-gw1.example.net.
15 | @ MX 20 mail-gw2.example.net.
16 | @ MX 30 mail-gw3.example.net.
17 |
18 | mail IN TXT "THIS IS SOME TEXT; WITH A SEMICOLON"
19 |
20 | multicast APL (
21 | 1:192.168.0.0/23
22 | 2:2001:acad:1::/112
23 | !1:192.168.1.64/28
24 | !2:2001:acad:1::8/128
25 | )
26 |
--------------------------------------------------------------------------------
/tests/Resources/wire/wire_test.data2:
--------------------------------------------------------------------------------
1 | 1707 8180
2 | 0001 0005 0002 0002 027a 6202 6d78 0361
3 | 6f6c 0363 6f6d 0000 0100 01c0 0c00 0100
4 | 0100 000b a100 04c6 5110 21c0 0c00 0100
5 | 0100 000b a100 04c6 5110 22c0 0c00 0100
6 | 0100 000b a100 04c6 5110 23c0 0c00 0100
7 | 0100 000b a100 04c6 5110 24c0 0c00 0100
8 | 0100 000b a100 04c6 5110 2502 6d78 0341
9 | 4f4c 0363 6f6d 0000 0200 0100 000d 9900
10 | 0c06 646e 732d 3031 026e 73c0 72c0 6f00
11 | 0200 0100 000d 9900 0906 646e 732d 3032
12 | c08c c085 0001 0001 0000 0d99 0004 c651
13 | 11e8 c09d 0001 0001 0000 0d99 0004 cdbc
14 | 9de8
15 |
--------------------------------------------------------------------------------
/tests/Resources/wire/wire_test.data3:
--------------------------------------------------------------------------------
1 | 1706 8180
2 | 0001 0005 0002 0002 027a 6102 6d78 0361
3 | 6f6c 0363 6f6d 0000 0100 01c0 0c00 0100
4 | 0100 000b a100 04c6 5110 05c0 0c00 0100
5 | 0100 000b a100 04c6 5110 01c0 0c00 0100
6 | 0100 000b a100 04c6 5110 02c0 0c00 0100
7 | 0100 000b a100 04c6 5110 03c0 0c00 0100
8 | 0100 000b a100 04c6 5110 0402 6d78 0341
9 | 4f4c 0363 6f6d 0000 0200 0100 000d 9900
10 | 0c06 646e 732d 3031 026e 73c0 72c0 6f00
11 | 0200 0100 000d 9900 0906 646e 732d 3032
12 | c08c c085 0001 0001 0000 0d99 0004 c651
13 | 11e8 c09d 0001 0001 0000 0d99 0004 cdbc
14 | 9de8
15 |
--------------------------------------------------------------------------------
/.php-cs-fixer.dist.php:
--------------------------------------------------------------------------------
1 |
7 |
8 | For the full copyright and license information, please view the LICENSE
9 | file that was distributed with this source code.
10 | EOF;
11 |
12 | $finder = PhpCsFixer\Finder::create()
13 | ->in(__DIR__.'/lib')
14 | ->in(__DIR__.'/tests')
15 | ->exclude(__DIR__.'/tests/Resources')
16 | ->exclude(__DIR__.'/tests/Parser/Resources')
17 | ;
18 |
19 | $config = new PhpCsFixer\Config();
20 | $config->setRiskyAllowed(true)
21 | ->setRules([
22 | '@Symfony' => true,
23 | 'header_comment' => ['header' => $header],
24 | 'array_syntax' => ['syntax' => 'short'],
25 | 'ordered_imports' => ['sort_algorithm' => 'alpha'],
26 | 'void_return' => true,
27 | 'declare_strict_types' => true,
28 | ])
29 | ->setFinder($finder)
30 | ;
31 |
32 | return $config;
--------------------------------------------------------------------------------
/lib/Rdata/RdataTrait.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | trait RdataTrait
17 | {
18 | /**
19 | * Get the string representation of the Rdata type.
20 | *
21 | * @return string Rdata type, e.g. "A", "MX", "NS", etc.
22 | */
23 | public function getType(): string
24 | {
25 | /* @const TYPE */
26 | return static::TYPE;
27 | }
28 |
29 | /**
30 | * Get the integer type code of the Rdata type as defined by IANA.
31 | *
32 | * @return int IANA Rdata type code
33 | */
34 | public function getTypeCode(): int
35 | {
36 | /* @const TYPE_CODE */
37 | return static::TYPE_CODE;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/.github/workflows/PHP-8.yml:
--------------------------------------------------------------------------------
1 | name: PHP 8
2 |
3 | on:
4 | - push
5 | - pull_request
6 |
7 | jobs:
8 | run:
9 | runs-on: ubuntu-latest
10 | strategy:
11 | fail-fast: false
12 | matrix:
13 | php: ['8.0', '8.1', '8.2', '8.3']
14 | name: PHP ${{ matrix.php }}
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v4
18 |
19 | - name: Install PHP
20 | uses: shivammathur/setup-php@v2
21 | with:
22 | php-version: ${{ matrix.php }}
23 | extensions: gmp
24 |
25 | - name: Display versions
26 | run: |
27 | php -r 'foreach (get_loaded_extensions() as $extension) echo $extension . " " . phpversion($extension) . PHP_EOL;'
28 | php -i
29 |
30 | - name: Composer install
31 | run: composer install -n
32 |
33 | - name: PHPUnit
34 | run: vendor/bin/phpunit
35 |
36 | - name: Test Coverage
37 | run: vendor/bin/phpunit --coverage-text
38 |
--------------------------------------------------------------------------------
/tests/Parser/Resources/50.100.200.in-addr.arpa.db:
--------------------------------------------------------------------------------
1 | $ORIGIN 50.100.200.in-addr.arpa.
2 | $TTL 3600
3 |
4 | @ IN SOA (
5 | ns.acme.com. ; MNAME
6 | noc.acme.com. ; RNAME
7 | 2014110501 ; SERIAL
8 | 3600 ; REFRESH
9 | 14400 ; RETRY
10 | 604800 ; EXPIRE
11 | 3600 ; MINIMUM
12 | )
13 |
14 | ; NS RECORDS
15 | @ NS ns1.acme.com.
16 | @ NS ns2.acme.com.
17 |
18 | 1 IN 1080 PTR gw01.core.acme.com.
19 | 1 IN 1080 PTR gw02.core.acme.com.
20 |
21 | $TTL 1080
22 |
23 | 50 IN PTR mx1.acme.com.
24 | 52 IN PTR mx2.acme.com.
25 |
26 | 70 7200 IN PTR ns1.acme.com.
27 | 72 7200 IN PTR ns2.acme.com.
28 |
29 | 150 200 PTR smtp.example.com.
30 | 170 150 IN PTR netscape.com.
31 |
--------------------------------------------------------------------------------
/tests/Rdata/TypesTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Rdata\Types;
17 | use Badcow\DNS\Rdata\UnsupportedTypeException;
18 | use PHPUnit\Framework\TestCase;
19 |
20 | class TypesTest extends TestCase
21 | {
22 | /**
23 | * @throws UnsupportedTypeException
24 | */
25 | public function testGetTypeCode(): void
26 | {
27 | $this->assertEquals(1, Types::getTypeCode('A'));
28 | $this->assertEquals(1234, Types::getTypeCode('TYPE1234'));
29 |
30 | $this->expectException(UnsupportedTypeException::class);
31 | $this->expectExceptionMessage('RData type "XX" is not supported.');
32 | Types::getTypeCode('XX');
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/Edns/Option/CodesTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Edns\Option;
15 |
16 | use Badcow\DNS\Edns\Option\Codes;
17 | use Badcow\DNS\Edns\Option\UnsupportedOptionException;
18 | use PHPUnit\Framework\TestCase;
19 |
20 | class CodesTest extends TestCase
21 | {
22 | public function testIsValid(): void
23 | {
24 | $this->assertTrue(Codes::isValid(Codes::COOKIE));
25 | $this->assertTrue(Codes::isValid('COOKIE'));
26 | }
27 |
28 | public function testGetName(): void
29 | {
30 | $this->assertEquals('COOKIE', Codes::getName(Codes::COOKIE));
31 | $this->expectException(UnsupportedOptionException::class);
32 | $this->assertTrue(Codes::getName(1024));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib/Opcode.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS;
15 |
16 | /**
17 | * {@link https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-5}.
18 | */
19 | class Opcode
20 | {
21 | /**
22 | * [RFC1035].
23 | */
24 | public const QUERY = 0;
25 |
26 | /**
27 | * Inverse Query [RFC3425] (Obsolete).
28 | */
29 | public const IQUERY = 1;
30 |
31 | /**
32 | * [RFC1035].
33 | */
34 | public const STATUS = 2;
35 |
36 | /**
37 | * [RFC1996].
38 | */
39 | public const NOTIFY = 4;
40 |
41 | /**
42 | * [RFC2136].
43 | */
44 | public const UPDATE = 5;
45 |
46 | /**
47 | * DNS Stateful Operations [RFC8490].
48 | */
49 | public const DSO = 6;
50 | }
51 |
--------------------------------------------------------------------------------
/lib/Rdata/DecodeException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | class DecodeException extends \Exception
17 | {
18 | public function __construct(string $type, string $rdata, int $code = 0, ?\Throwable $previous = null)
19 | {
20 | $message = sprintf('Unable to decode %s record rdata from binary data "%s"', $type, self::binaryToHex($rdata));
21 | parent::__construct($message, $code, $previous);
22 | }
23 |
24 | /**
25 | * Convert a binary string into hexadecimal values.
26 | */
27 | public static function binaryToHex(string $rdata): string
28 | {
29 | $hex = array_map(function ($char) {
30 | return sprintf('0x%02x', ord($char));
31 | }, str_split($rdata));
32 |
33 | return implode(' ', $hex);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Badcow
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
--------------------------------------------------------------------------------
/tests/Edns/Option/FactoryTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Edns\Option;
15 |
16 | use Badcow\DNS\Edns\Option\Codes;
17 | use Badcow\DNS\Edns\Option\COOKIE;
18 | use Badcow\DNS\Edns\Option\Factory;
19 | use Badcow\DNS\Edns\Option\UnsupportedOptionException;
20 | use PHPUnit\Framework\TestCase;
21 |
22 | class FactoryTest extends TestCase
23 | {
24 | public function testNewOptionFromName(): void
25 | {
26 | $this->assertInstanceOf(COOKIE::class, Factory::newOptionFromName('COOKIE'));
27 |
28 | $this->expectException(UnsupportedOptionException::class);
29 | Factory::newOptionFromName('INVALID');
30 | }
31 |
32 | public function testIsOptionCodeImplemented(): void
33 | {
34 | $this->assertTrue(Factory::isOptionCodeImplemented(Codes::COOKIE));
35 | $this->assertFalse(Factory::isOptionCodeImplemented(1024));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/Parser/Comments.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Parser;
15 |
16 | class Comments
17 | {
18 | /**
19 | * No comments are parsed.
20 | */
21 | public const NONE = 0;
22 |
23 | /**
24 | * Inline comments that appear at the end of a resource record.
25 | */
26 | public const END_OF_ENTRY = 1;
27 |
28 | /**
29 | * Multi-line record comments: those comments that appear within multi-line brackets. E.g.
30 | * acme.org. IN MX (
31 | * 30 ;This comment will be parsed.
32 | * mail-gw3 ;So will this comment.
33 | * ).
34 | */
35 | public const MULTILINE = 2;
36 |
37 | /**
38 | * Orphan comments appear without a resource record. Usually these are section headers.
39 | */
40 | public const ORPHAN = 4;
41 |
42 | /**
43 | * Parse all comments.
44 | */
45 | public const ALL = 7;
46 | }
47 |
--------------------------------------------------------------------------------
/tests/Resources/wire/wire_test.data1:
--------------------------------------------------------------------------------
1 | # message header
2 | 000a #id=10
3 | 85 # QR=1 (response) OPCODE=0 AA=1 TC=0 RD=1
4 | 80 # RA=1 BIT9=0 AD=0 CD=0 RCODE=0
5 | 0001 #QDCOUNT
6 | 0003 #ANCOUNT
7 | 0000 #NSCOUNT
8 | 0003 #ARCOUNT
9 |
10 | #QUERY
11 | #[3] vix [3] com . NS IN
12 | 03 766978 03 636f6d 00 0002 0001 # question section: vix.com IN NS
13 |
14 | #ANSWERS
15 | #[12] NS IN 3600 rdlen=11 [5] isrv1 [2] p a [12] (vix.com.)
16 | c00c 0002 0001 00000e10 000b 05 69 73 72 76 31 02 70 61 c00c # vix.com IN NS 3600
17 |
18 | #[12] NS IN 3600 rdlen=9 [6] n s - e x t [12] (vix.com.)
19 | c00c 0002 0001 00000e10 0009 06 6e 73 2d 65 78 74 c00c
20 |
21 | #[12] NS IN 3600 rdlen=14 [3] n s 1 [4] g n a c [3] c o m [.]
22 | c00c 0002 0001 00000e10 000e 03 6e 73 31 04 67 6e 61 63 03 63 6f 6d 00
23 |
24 | #ADDITIONALS
25 | #[37] A IN 3600 rdlen=4 204.152.184.134 (37=isrv1.pa.vix.com.)
26 | c025 0001 0001 00000e10 0004 cc98b886
27 |
28 | #[60] A IN 3600 rdlen=4 204.152.184.64 (60=ns-ext.vix.com.)
29 | c03c 0001 0001 00000e10 0004 cc98b840
30 |
31 | #[81] A IN 172362 rdlen=4 198.151.248.246 (81=ns1.gnac.com.)
32 | c051 0001 0001 0002a14a 0004 c697f8f6
33 |
--------------------------------------------------------------------------------
/lib/Rdata/DNSKEY.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | /**
17 | * Class DNSKEY.
18 | *
19 | * {@link https://tools.ietf.org/html/rfc4034#section-2.1}
20 | */
21 | class DNSKEY extends KEY
22 | {
23 | public const TYPE = 'DNSKEY';
24 | public const TYPE_CODE = 48;
25 |
26 | /**
27 | * The Protocol Field MUST have value 3, and the DNSKEY RR MUST be
28 | * treated as invalid during signature verification if it is found to be
29 | * some value other than 3.
30 | * {@link https://tools.ietf.org/html/rfc4034#section-2.1.2}.
31 | *
32 | * @var int
33 | */
34 | protected $protocol = 3;
35 |
36 | public function setProtocol(int $protocol): void
37 | {
38 | if (3 !== $protocol) {
39 | throw new \InvalidArgumentException('DNSKEY RData: parameter can only be set to "3".');
40 | }
41 |
42 | parent::setProtocol($protocol);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/docs/Parser/Custom-Rdata-Handlers.md:
--------------------------------------------------------------------------------
1 | Using Custom RData Handlers
2 | ===========================
3 | Out-of-the-box, the library will handle most RData types that are regularly encountered. Occasionally, you may encounter
4 | an unsupported type. You can add your own RData handler method for the record type. For example, you may want to support
5 | the non-standard `SPF` record type, and return a `TXT` instance.
6 | ```php
7 | $spf = function (\ArrayIterator $iterator): Badcow\DNS\Rdata\TXT {
8 | $string = '';
9 | while ($iterator->valid()) {
10 | $string .= $iterator->current() . ' ';
11 | $iterator->next();
12 | }
13 | $string = trim($string, ' "'); //Remove whitespace and quotes
14 |
15 | $spf = new Badcow\DNS\Rdata\TXT;
16 | $spf->setText($string);
17 |
18 | return $spf;
19 | };
20 |
21 | $customHandlers = ['SPF' => $spf];
22 |
23 | $record = 'example.com. 7200 IN SPF "v=spf1 a mx ip4:69.64.153.131 include:_spf.google.com ~all"';
24 | $parser = new \Badcow\DNS\Parser\Parser($customHandlers);
25 | $zone = $parser->makeZone('example.com.', $record);
26 | ```
27 |
28 | You can also overwrite the default handlers if you wish, as long as your handler method returns an instance of
29 | `Badcow\DNS\Rdata\RdataInterface`.
30 |
--------------------------------------------------------------------------------
/tests/Resources/wire/wire_test.data5:
--------------------------------------------------------------------------------
1 | #This is the same as wire_test.data1 except all labels have been uncompressed.
2 |
3 | # MESSAGE HEADER
4 | 000a #id=10
5 | 85 # QR=1 (response) OPCODE=0 AA=1 TC=0 RD=1
6 | 80 # RA=1 BIT9=0 AD=0 CD=0 RCODE=0
7 | 0001 #QDCOUNT
8 | 0003 #ANCOUNT
9 | 0000 #NSCOUNT
10 | 0003 #ARCOUNT
11 |
12 | # QUESTION SECTION
13 | 03 766978 03 636f6d 00 0002 0001 #vix.com IN NS
14 |
15 | # ANSWER SECTION
16 | 03 766978 03 636f6d 00 0002 0001 00000e10 0012 05 69 73 72 76 31 02 70 61 03 76 69 78 03 63 6f 6d 00 # vix.com IN NS 3600 isrv1.pa.vix.com.
17 | 03 766978 03 636f6d 00 0002 0001 00000e10 0010 06 6e 73 2d 65 78 74 03 76 69 78 03 63 6f 6d 00 # vix.com IN NS 3600 ns-ext.vix.com.
18 | 03 766978 03 636f6d 00 0002 0001 00000e10 000e 03 6e 73 31 04 67 6e 61 63 03 63 6f 6d 00 # vix.com IN NS 3600 ns1.gnac.com.
19 |
20 | # ADDITIONAL SECTION
21 | 05 6973727631 02 7061 03 766978 03 636f6d 00 0001 0001 00000e10 0004 cc98b886 # isrv1.pa.vix.com. IN A 3600 204.152.184.134
22 | 06 6e732d657874 03 766978 03 636f6d 00 0001 0001 00000e10 0004 cc98b840 # ns-ext.vix.com. IN A 3600 204.152.184.64
23 | 03 6e7331 04 676e6163 03 636f6d 00 0001 0001 0002a14a 0004 c697f8f6 # ns1.gnac.com. IN A 172362 198.151.248.246
24 |
--------------------------------------------------------------------------------
/lib/Rdata/AAAA.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | use Badcow\DNS\Validator;
17 |
18 | /**
19 | * @see https://tools.ietf.org/html/rfc3596#section-2.1
20 | */
21 | class AAAA extends A
22 | {
23 | public const TYPE = 'AAAA';
24 | public const TYPE_CODE = 28;
25 |
26 | public function setAddress(string $address): void
27 | {
28 | if (!Validator::ipv6($address)) {
29 | throw new \InvalidArgumentException(sprintf('The address "%s" is not a valid IPv6 address.', $address));
30 | }
31 |
32 | $this->address = $address;
33 | }
34 |
35 | /**
36 | * @throws DecodeException
37 | */
38 | public function fromWire(string $rdata, int &$offset = 0, ?int $rdLength = null): void
39 | {
40 | if (false === $address = @inet_ntop(substr($rdata, $offset, 16))) {
41 | throw new DecodeException(static::TYPE, $rdata);
42 | }
43 | $offset += 16;
44 |
45 | $this->setAddress($address);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/docs/Validation.md:
--------------------------------------------------------------------------------
1 | Validation
2 | ==========
3 |
4 | Very little validation is done automatically when setting RData or Resource Record objects, so the onus is on the
5 | implementor to validate the resource data and the zone record. Contained within the library, however, are some useful
6 | validation tools. These are static functions contained within `Badcow\DNS\Validator` (here, simply referred to as
7 | `Validator`).
8 |
9 | ## Validate the Zone
10 | `Validator::zone()` will inspect a number of properties of a Zone, namely:
11 | * There is exactly one SOA record,
12 | * There are NS records, and
13 | * There is exactly one record class (while it is permissible to use obsolete classes such as CHAOS, in all modern
14 | contexts, this is always IN).
15 |
16 | The return value is a binary sum of error codes which can be determined using boolean operands. A valid zone will
17 | return `0`, AKA `Validator::ZONE_OKAY`. _Exempli gratia:_
18 | ```php
19 | $zone //Some Badcow\DNS\Zone
20 | if (Validator::zone($zone) & Validator::ZONE_NO_SOA) echo "There are no SOA Records";
21 | if (Validator::zone($zone) & Validator::ZONE_TOO_MANY_CLASSES) echo "There are too many classes.";
22 | ```
23 |
24 | The return codes are:
25 | * `ZONE_NO_SOA`
26 | * `ZONE_TOO_MANY_SOA`
27 | * `ZONE_NO_NS`
28 | * `ZONE_NO_CLASS`
29 | * `ZONE_TOO_MANY_CLASSES`
30 | * `ZONE_OKAY`
31 |
32 |
--------------------------------------------------------------------------------
/tests/AlgorithmsTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests;
15 |
16 | use Badcow\DNS\Algorithms;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class AlgorithmsTest extends TestCase
20 | {
21 | public function testGetMnemonic(): void
22 | {
23 | $this->assertEquals('RSASHA1', Algorithms::getMnemonic(5));
24 | }
25 |
26 | /**
27 | * @throws \InvalidArgumentException
28 | */
29 | public function testGetMnemonicThrowsExceptionOnInvalidAlgorithm(): void
30 | {
31 | $this->expectException(\InvalidArgumentException::class);
32 | $this->expectExceptionMessage('"1337" is not a valid algorithm.');
33 | Algorithms::getMnemonic(1337);
34 | }
35 |
36 | /**
37 | * @throws \InvalidArgumentException
38 | */
39 | public function testGetAlgorithmValueThrowsExceptionOnInvalidMnemonic(): void
40 | {
41 | $this->expectException(\InvalidArgumentException::class);
42 | $this->expectExceptionMessage('"INVALID_MNEMONIC" is not a valid algorithm mnemonic.');
43 | Algorithms::getAlgorithmValue('INVALID_MNEMONIC');
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/Parser/Resources/testCollapseMultilines_sample.txt:
--------------------------------------------------------------------------------
1 | $ORIGIN example.com.
2 | $TTL 1337
3 | $INCLUDE hq.example.com.txt
4 | @ IN SOA (
5 | example.com. ; MNAME
6 | post.example.com. ; RNAME
7 | 2014110501 ; SERIAL
8 | 3600 ; REFRESH
9 | 14400 ; RETRY
10 | 604800 ; EXPIRE
11 | 3600 ; MINIMUM
12 | ); This is my Start of Authority Record; AKA SOA.
13 |
14 | ; NS RECORDS
15 | @ NS ns1.nameserver.com.
16 | @ NS ns2.nameserver.com.
17 |
18 | info TXT "This is some additional \"information\""
19 |
20 | ; A RECORDS
21 | sub.domain A 192.168.1.42 ; This is a local ip.
22 |
23 | ; AAAA RECORDS
24 | ipv6.domain AAAA ::1 ; This is an IPv6 domain.
25 |
26 | ; MX RECORDS
27 | @ MX 10 mail-gw1.example.net.
28 | @ MX 20 mail-gw2.example.net.
29 | @ MX 30 mail-gw3.example.net.
30 |
31 | mail IN TXT "THIS IS SOME TEXT; WITH A SEMICOLON"
32 |
33 | multicast APL (
34 | 1:192.168.0.0/23
35 | 2:2001:acad:1::/112
36 | !1:192.168.1.64/28
37 | !2:2001:acad:1::8/128
38 | )
39 |
--------------------------------------------------------------------------------
/tests/Rdata/CdnskeyTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Algorithms;
17 | use Badcow\DNS\Rdata\CDNSKEY;
18 | use Badcow\DNS\Rdata\Factory;
19 | use PHPUnit\Framework\TestCase;
20 |
21 | class CdnskeyTest extends TestCase
22 | {
23 | /**
24 | * @var string
25 | */
26 | private static $publicKey = 'AQPSKmynfzW4kyBv015MUG2DeIQ3Cbl+BBZH4b/0PY1kxkmvHjcZc8nokfzj31GajIQKY+5CptLr3buXA10hWqTkF7H6RfoRqXQeogmMHfpftf6zMv1LyBUgia7za6ZEzOJBOztyvhjL742iU/TpPSEDhm2SNKLijfUppn1UaNvv4w==';
27 |
28 | public function testFactory(): void
29 | {
30 | $cdnskey = Factory::CDNSKEY(256, Algorithms::RSASHA1, base64_decode(self::$publicKey));
31 | $output = '256 3 5 '.self::$publicKey;
32 |
33 | $this->assertInstanceOf(CDNSKEY::class, $cdnskey);
34 | $this->assertEquals(256, $cdnskey->getFlags());
35 | $this->assertEquals(5, $cdnskey->getAlgorithm());
36 | $this->assertEquals(base64_decode(self::$publicKey), $cdnskey->getPublicKey());
37 | $this->assertEquals(3, $cdnskey->getProtocol());
38 | $this->assertEquals($output, $cdnskey->toText());
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/ClassesTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests;
15 |
16 | use Badcow\DNS\Classes;
17 |
18 | class ClassesTest extends \PHPUnit\Framework\TestCase
19 | {
20 | public function testIsValidClass(): void
21 | {
22 | $this->assertTrue(Classes::isValid('IN'));
23 | $this->assertTrue(Classes::isValid('HS'));
24 | $this->assertTrue(Classes::isValid('CH'));
25 |
26 | $this->assertFalse(Classes::isValid('INTERNET'));
27 | $this->assertFalse(Classes::isValid('in'));
28 | $this->assertFalse(Classes::isValid('In'));
29 | $this->assertFalse(Classes::isValid('hS'));
30 | }
31 |
32 | public function testGetClassId(): void
33 | {
34 | $this->assertEquals(1, Classes::getClassId('IN'));
35 | $this->assertEquals(4, Classes::getClassId('HS'));
36 | $this->assertEquals(3, Classes::getClassId('CH'));
37 | }
38 |
39 | public function testGetClassIdThrowsExceptionForUndefinedClass(): void
40 | {
41 | $this->expectException(\InvalidArgumentException::class);
42 | $this->expectExceptionMessage('Class "XX" is not a valid DNS class.');
43 | Classes::getClassId('XX');
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/lib/Parser/ParseException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Parser;
15 |
16 | class ParseException extends \Exception
17 | {
18 | /**
19 | * @var StringIterator
20 | */
21 | private $stringIterator;
22 |
23 | /**
24 | * ParseException constructor.
25 | */
26 | public function __construct(string $message = '', ?StringIterator $stringIterator = null, \Throwable $previous = null)
27 | {
28 | if (null !== $stringIterator) {
29 | $this->stringIterator = $stringIterator;
30 | $message .= sprintf(' [Line no: %d]', $this->getLineNumber());
31 | }
32 |
33 | parent::__construct($message, 0, $previous);
34 | }
35 |
36 | /**
37 | * Get line number of current entry on the StringIterator.
38 | */
39 | private function getLineNumber(): int
40 | {
41 | $pos = $this->stringIterator->key();
42 | $this->stringIterator->rewind();
43 | $lineNo = 1;
44 |
45 | while ($this->stringIterator->key() < $pos) {
46 | if ($this->stringIterator->is(Tokens::LINE_FEED)) {
47 | ++$lineNo;
48 | }
49 | $this->stringIterator->next();
50 | }
51 |
52 | return $lineNo;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/tests/Rdata/FactoryTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Rdata\Factory;
17 | use Badcow\DNS\Rdata\UnsupportedTypeException;
18 | use PHPUnit\Framework\TestCase;
19 |
20 | class FactoryTest extends TestCase
21 | {
22 | public function getTestData(): array
23 | {
24 | $namespace = '\\Badcow\\DNS\\Rdata\\';
25 |
26 | return [
27 | ['CNAME', 5, $namespace.'CNAME'],
28 | ['AAAA', 28, $namespace.'AAAA'],
29 | ['RRSIG', 46, $namespace.'RRSIG'],
30 | ];
31 | }
32 |
33 | /**
34 | * @dataProvider getTestData
35 | *
36 | * @throws UnsupportedTypeException
37 | */
38 | public function testNewRdataFromNameAndId(string $type, int $typeCode, string $classname): void
39 | {
40 | $this->assertInstanceOf($classname, Factory::newRdataFromName($type));
41 | $this->assertInstanceOf($classname, Factory::newRdataFromId($typeCode));
42 | }
43 |
44 | /**
45 | * @throws UnsupportedTypeException
46 | */
47 | public function testNewRdataFromNameThrowsExceptionForUnknownType(): void
48 | {
49 | $this->expectException(UnsupportedTypeException::class);
50 | Factory::newRdataFromName('rsig');
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/Parser/Resources/testConvolutedZone_sample.txt:
--------------------------------------------------------------------------------
1 | $origin example.com.
2 | $ttl 3600
3 | @ 300 IN SOA (
4 | ns1.beget.ru. hostmaster.beget.ru. 2015060403
5 | 300 600 86400 300
6 | )
7 | @ 600 IN A 5.101.153.38
8 | @ 600 IN AAAA ::ffff:192.168.10.23
9 | @ 300 IN NS ns1.beget.ru.
10 | _xmpp-server._tcp.icq.beget.ru 600 IN SRV 10 0 5247 jabber
11 | autoconfig 600 IN CNAME cf-ssl00000-protected.example.com.
12 | arhangelsk 600 IN MX 10 mx2.beget.ru.
13 | @ 600 IN TXT "Some text" " another Some text"
14 | test 600 IN TXT (;11111
15 | "Some text" ; or like this
16 | " another Some text"
17 | )
18 | testtxt 600 IN TXT ("v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZKI3U+9acu3NfEy0NJHIPydxnPLPpnAJ7"
19 | "k2JdrsLqAK1uouMudHI20pgE8RMldB/TeWKXYoRidcGCZWXleUzldDTwZAMDQNpdH1uuxym0VhoZpPbI1RXwpgHRTbCk49VqlC")
20 | testmx 600 IN MX 20 @;345345
21 | 300 IN MX 30 mail.com.
22 | IN MX 35 mail-gw3
23 | MX 40 mail-gw4
24 | testmx2 600 IN MX 20 .;23''33'123;;123;
25 | _domainkeytxt 600 IN TXT "t=y;o=~;\0";312
26 | www 600 IN TXT "@ A 79.125.10.157 "
27 | txt3 600 IN TXT "www CNAME ;;;;'werwerwer'\0010"
28 | nstest 300 IN NS ns1
29 | nstest2 300 IN NS 85.249.229.194;111
30 | xn----7sbfndkfpirgcajeli2a4pnc.xn----7sbbfcqfo2cfcagacemif0ap5q 300 IN NS 1.1.1.1
31 | casino 600 IN NS @;234
32 | bonus 600 IN CNAME @;111
33 | ;234234;234;234234234234
34 | * 600 IN CNAME s;111
35 | @ 600 IN SRV 10 0 5269 @ ;wer;wer;wer;wer;;
36 | xmpp 600 IN SRV 10 0 5222 81.211.107.230. ;;;werwerwer
37 | www222 0 IN CNAME lifun.ru.
38 | 46.20.191.35 3600 IN PTR office;1231
39 | ;qqq 300 IN OLE "omg"
40 |
--------------------------------------------------------------------------------
/lib/Edns/Option/OptionInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Edns\Option;
15 |
16 | interface OptionInterface
17 | {
18 | /**
19 | * Get the string representation of the Option type.
20 | *
21 | * @return string Option name type, e.g. "COOKIE", "CLIENT_SUBNET", etc.
22 | */
23 | public function getName(): string;
24 |
25 | /**
26 | * Get the integer name code of the Option as defined by IANA.
27 | *
28 | * @return int IANA Option type code
29 | */
30 | public function getCode(): int;
31 |
32 | /**
33 | * Return a DNS Server response formatted representation of the Option.
34 | *
35 | * @return string packed binary form of Option
36 | */
37 | public function toWire(): string;
38 |
39 | /**
40 | * Populate Option from its wire representation.
41 | *
42 | * @param string $optionValue packed binary form of Option
43 | * @param int $offset the current offset or pointer, position of the start of Option relative to the whole $optionValue string
44 | * @param int|null $optionLength the length of the Option string, if null it is taken to be the whole $optionValue parameter string
45 | */
46 | public function fromWire(string $optionValue, int &$offset = 0, ?int $optionLength = null): void;
47 | }
48 |
--------------------------------------------------------------------------------
/lib/Rdata/CNAME.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | use Badcow\DNS\Message;
17 |
18 | /**
19 | * @see https://tools.ietf.org/html/rfc1035#section-3.3.1
20 | */
21 | class CNAME implements RdataInterface
22 | {
23 | use RdataTrait;
24 |
25 | public const TYPE = 'CNAME';
26 | public const TYPE_CODE = 5;
27 |
28 | /**
29 | * @var string|null
30 | */
31 | protected $target;
32 |
33 | public function setTarget(string $target): void
34 | {
35 | $this->target = $target;
36 | }
37 |
38 | /**
39 | * @return string
40 | */
41 | public function getTarget(): ?string
42 | {
43 | return $this->target;
44 | }
45 |
46 | public function toText(): string
47 | {
48 | return $this->target ?? '';
49 | }
50 |
51 | public function toWire(): string
52 | {
53 | if (null === $this->target) {
54 | throw new \InvalidArgumentException('Target must be set.');
55 | }
56 |
57 | return Message::encodeName($this->target);
58 | }
59 |
60 | public function fromText(string $text): void
61 | {
62 | $this->setTarget($text);
63 | }
64 |
65 | public function fromWire(string $rdata, int &$offset = 0, ?int $rdLength = null): void
66 | {
67 | $this->setTarget(Message::decodeName($rdata, $offset));
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/lib/Parser/ResourceRecordIterator.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Parser;
15 |
16 | class ResourceRecordIterator extends \ArrayIterator
17 | {
18 | public function __construct(string $resourceRecord)
19 | {
20 | parent::__construct(explode(Tokens::SPACE, $resourceRecord));
21 | }
22 |
23 | /**
24 | * Return pointer to previous position.
25 | */
26 | public function prev(): void
27 | {
28 | if (!$this->valid()) {
29 | $this->end();
30 |
31 | return;
32 | }
33 |
34 | $this->seek((int) $this->key() - 1);
35 | }
36 |
37 | /**
38 | * Set pointer to the end of the array.
39 | */
40 | public function end(): void
41 | {
42 | $lastPos = $this->count() - 1;
43 | $this->seek($lastPos);
44 | }
45 |
46 | /**
47 | * Get all the remaining values of an iterator as an array. This will move the pointer to the end of the array.
48 | */
49 | public function getRemainingAsString(): string
50 | {
51 | $values = [];
52 | while ($this->valid()) {
53 | $values[] = $this->current();
54 | $this->next();
55 | }
56 |
57 | return implode(Tokens::SPACE, $values);
58 | }
59 |
60 | public function __toString(): string
61 | {
62 | return implode(Tokens::SPACE, $this->getArrayCopy());
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tests/Rdata/NsTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Rdata\NS;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class NsTest extends TestCase
20 | {
21 | public function testSetNsdname(): void
22 | {
23 | $target = 'foo.example.com.';
24 | $ns = new NS();
25 | $ns->setTarget($target);
26 |
27 | $this->assertEquals($target, $ns->getTarget());
28 | }
29 |
30 | public function testOutput(): void
31 | {
32 | $target = 'foo.example.com.';
33 | $ns = new NS();
34 | $ns->setTarget($target);
35 |
36 | $this->assertEquals($target, $ns->toText());
37 | $this->assertEquals($target, $ns->toText());
38 | }
39 |
40 | public function testFromText(): void
41 | {
42 | $text = 'host.example.com.';
43 | /** @var NS $cname */
44 | $cname = new NS();
45 | $cname->fromText($text);
46 |
47 | $this->assertEquals($text, $cname->getTarget());
48 | }
49 |
50 | public function testWire(): void
51 | {
52 | $host = 'host.example.com.';
53 | $expectation = chr(4).'host'.chr(7).'example'.chr(3).'com'.chr(0);
54 |
55 | /** @var NS $ns */
56 | $ns = new NS();
57 | $ns->fromWire($expectation);
58 |
59 | $this->assertEquals($expectation, $ns->toWire());
60 | $this->assertEquals($host, $ns->getTarget());
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/Rdata/UnknownTypeTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Rdata\UnknownType;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class UnknownTypeTest extends TestCase
20 | {
21 | public function testToText(): void
22 | {
23 | $expectation = '\# 32 9aa065581e1a247d5e884a44adfa7cb4a849c7b90ade83c8fb9eae5984ea7fba';
24 | $uk = new UnknownType();
25 | $uk->setTypeCode(1234);
26 | $uk->setData(hex2bin('9aa065581e1a247d5e884a44adfa7cb4a849c7b90ade83c8fb9eae5984ea7fba'));
27 |
28 | $this->assertEquals($expectation, $uk->toText());
29 | }
30 |
31 | public function testFromText(): void
32 | {
33 | $uk = new UnknownType();
34 | $uk->fromText('\# 32 9aa065581e1a247d5e884a44adfa7cb4a849c7b90ade83c8fb9eae5984ea7fba');
35 | $this->assertEquals(hex2bin('9aa065581e1a247d5e884a44adfa7cb4a849c7b90ade83c8fb9eae5984ea7fba'), $uk->getData());
36 | }
37 |
38 | public function testWire(): void
39 | {
40 | $expectation = '\# 32 9aa065581e1a247d5e884a44adfa7cb4a849c7b90ade83c8fb9eae5984ea7fba';
41 | $wireFormat = hex2bin('9aa065581e1a247d5e884a44adfa7cb4a849c7b90ade83c8fb9eae5984ea7fba');
42 | $uk = new UnknownType();
43 | $uk->fromWire($wireFormat);
44 |
45 | $this->assertEquals($expectation, $uk->toText());
46 | $this->assertEquals($wireFormat, $uk->toWire());
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/Rdata/CnameTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Rdata\CNAME;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class CnameTest extends TestCase
20 | {
21 | public function testOutput(): void
22 | {
23 | $target = 'foo.example.com.';
24 | $cname = new CNAME();
25 | $cname->setTarget($target);
26 |
27 | $this->assertEquals($target, $cname->toText());
28 | }
29 |
30 | public function testFromText(): void
31 | {
32 | $text = 'host.example.com.';
33 | /** @var CNAME $cname */
34 | $cname = new CNAME();
35 | $cname->fromText($text);
36 |
37 | $this->assertEquals($text, $cname->getTarget());
38 | }
39 |
40 | public function testWire(): void
41 | {
42 | $host = 'host.example.com.';
43 | $expectation = chr(4).'host'.chr(7).'example'.chr(3).'com'.chr(0);
44 |
45 | /** @var CNAME $cname */
46 | $cname = new CNAME();
47 | $cname->fromWire($expectation);
48 |
49 | $this->assertEquals($expectation, $cname->toWire());
50 | $this->assertEquals($host, $cname->getTarget());
51 |
52 | //Test that toWire() will throw an exception if no target is set.
53 | $cname = new CNAME();
54 | $this->expectException(\InvalidArgumentException::class);
55 | $this->expectExceptionMessage('Target must be set.');
56 | $cname->toWire();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/Rdata/DnameTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Rdata\DNAME;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class DnameTest extends TestCase
20 | {
21 | public function testSetTarget(): void
22 | {
23 | $target = 'foo.example.com.';
24 | $dname = new DNAME();
25 | $dname->setTarget($target);
26 |
27 | $this->assertEquals($target, $dname->getTarget());
28 | }
29 |
30 | public function testOutput(): void
31 | {
32 | $target = 'foo.example.com.';
33 | $dname = new DNAME();
34 | $dname->setTarget($target);
35 |
36 | $this->assertEquals($target, $dname->toText());
37 | $this->assertEquals($target, $dname->toText());
38 | }
39 |
40 | public function testFromText(): void
41 | {
42 | $text = 'host.example.com.';
43 | /** @var DNAME $cname */
44 | $cname = new DNAME();
45 | $cname->fromText($text);
46 |
47 | $this->assertEquals($text, $cname->getTarget());
48 | }
49 |
50 | public function testWire(): void
51 | {
52 | $host = 'host.example.com.';
53 | $expectation = chr(4).'host'.chr(7).'example'.chr(3).'com'.chr(0);
54 |
55 | /** @var DNAME $dname */
56 | $dname = new DNAME();
57 | $dname->fromWire($expectation);
58 |
59 | $this->assertEquals($expectation, $dname->toWire());
60 | $this->assertEquals($host, $dname->getTarget());
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/Edns/Option/UnknownOptionTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Edns\Option;
15 |
16 | use Badcow\DNS\Edns\Option\UnknownOption;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class UnknownOptionTest extends TestCase
20 | {
21 | /**
22 | * @var UnknownOption
23 | */
24 | private $option;
25 |
26 | public function setUp(): void
27 | {
28 | $this->option = new UnknownOption();
29 | }
30 |
31 | public function testGetterSetters(): void
32 | {
33 | $this->option->setOptionCode(123);
34 | $this->assertEquals(123, $this->option->getNameCode());
35 | $this->assertEquals('OPTION123', $this->option->getName());
36 | }
37 |
38 | public function testToWire(): void
39 | {
40 | $noData = new UnknownOption();
41 | $this->assertEquals('', $noData->toWire());
42 |
43 | $withData = new UnknownOption();
44 | $withData->setData('HelloWorld');
45 | $this->assertEquals('HelloWorld', $withData->toWire());
46 | }
47 |
48 | public function testFromWire1(): void
49 | {
50 | $wire = '';
51 | $noData = new UnknownOption();
52 | $noData->fromWire($wire);
53 | $this->assertEmpty($noData->getData());
54 | }
55 |
56 | public function testFromWire2(): void
57 | {
58 | $wire = 'HelloWorld';
59 | $withData = new UnknownOption();
60 | $withData->fromWire($wire);
61 | $this->assertEquals('HelloWorld', $withData->getData());
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tests/Resources/google.com.cer:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIEvjCCA6agAwIBAgIQQ2AE7r1PEHwCAAAAAEiVEDANBgkqhkiG9w0BAQsFADBC
3 | MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMRMw
4 | EQYDVQQDEwpHVFMgQ0EgMU8xMB4XDTE5MTAxNjEyMjgzMloXDTIwMDEwODEyMjgz
5 | MlowaDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcT
6 | DU1vdW50YWluIFZpZXcxEzARBgNVBAoTCkdvb2dsZSBMTEMxFzAVBgNVBAMTDnd3
7 | dy5nb29nbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEnoRygN85bi1T
8 | +J+CF22EIRSdS2JNyMvmHygVx3irNicagWKZpvE7b4WamxYg2AvqBsdvnPneOxHR
9 | Jc6Q0AJdQaOCAlMwggJPMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEF
10 | BQcDATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTDmKwDi3Plr7hI6f+eDounoVvH
11 | 0DAfBgNVHSMEGDAWgBSY0fhuEOvPm+xgnxiQG6DrfQn9KzBkBggrBgEFBQcBAQRY
12 | MFYwJwYIKwYBBQUHMAGGG2h0dHA6Ly9vY3NwLnBraS5nb29nL2d0czFvMTArBggr
13 | BgEFBQcwAoYfaHR0cDovL3BraS5nb29nL2dzcjIvR1RTMU8xLmNydDAZBgNVHREE
14 | EjAQgg53d3cuZ29vZ2xlLmNvbTAhBgNVHSAEGjAYMAgGBmeBDAECAjAMBgorBgEE
15 | AdZ5AgUDMC8GA1UdHwQoMCYwJKAioCCGHmh0dHA6Ly9jcmwucGtpLmdvb2cvR1RT
16 | MU8xLmNybDCCAQMGCisGAQQB1nkCBAIEgfQEgfEA7wB1ALIeBcyLos2KIE6HZvkr
17 | uYolIGdr2vpw57JJUy3vi5BeAAABbdTBfH8AAAQDAEYwRAIgSt1msvylbbmaFBkF
18 | /mOKZW2ehZMW6gzrGPZN9dBBizoCICpT4KMdCJ55KlsYtXQdNKVewym61xy78Vap
19 | NhNzKoaDAHYAXqdz+d9WwOe1Nkh90EngMnqRmgyEoRIShBh1loFxRVgAAAFt1MF8
20 | lAAABAMARzBFAiEAr0C6rbCg/25yMzPNPyfmq0/gBErlWq20xzTMEk/b5EgCIDe3
21 | syzJYupXo7DjkfK8x/wZTRTeJeSas5m8BV1scV5CMA0GCSqGSIb3DQEBCwUAA4IB
22 | AQAKPpbaFgWefJfX3OKcEcX7ZCtCKf1PSx9mH5jN4NfKgV9UYmtUugLmqGcAXsSq
23 | dHYoZIKoJQ4DAhQ09BeWVwvknScj34fFIDD8V+zVTtgc/NSHeUgA+TzhAdeyCmce
24 | KglWSkywIV/h0rJgvA31HKmUCYDZypRbsVpqVFOwC1FMNXDE2YlyKXBLh8HzS4nv
25 | R+pcRM60RwEzi/SLkRoUeiG6wS0/V7xbPubMcwrpOj4InYWUDFVaL8IfHj4nSh8s
26 | bcqBGGPXLa/R42PJf1SJIGEgOzHaHp1atY8XLTc/zOYL/yb7zP/bPXGmdziKomxI
27 | gjO/YnjQ4O64SucdYAlAp5cJ
28 | -----END CERTIFICATE-----
29 |
--------------------------------------------------------------------------------
/lib/Edns/Option/TCP_KEEPALIVE.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Edns\Option;
15 |
16 | /**
17 | * @see https://www.rfc-editor.org/rfc/rfc7828.html#section-3.1
18 | */
19 | class TCP_KEEPALIVE implements OptionInterface
20 | {
21 | use OptionTrait;
22 |
23 | public const NAME = 'TCP_KEEPALIVE';
24 | public const CODE = 11;
25 |
26 | /**
27 | * @var int|null
28 | */
29 | protected $timeout;
30 |
31 | public function getTimeout(): ?int
32 | {
33 | return $this->timeout;
34 | }
35 |
36 | public function setTimeout(?int $timeout): void
37 | {
38 | $this->timeout = $timeout;
39 | }
40 |
41 | public function toWire(): string
42 | {
43 | if (null === $this->timeout) {
44 | return '';
45 | }
46 |
47 | return pack('n', $this->timeout);
48 | }
49 |
50 | public function fromWire(string $optionValue, int &$offset = 0, ?int $optionLength = null): void
51 | {
52 | $optionLength = $optionLength ?? strlen($optionValue);
53 | if (0 !== $optionLength and 2 !== $optionLength) {
54 | throw new DecodeException(self::NAME, $optionValue);
55 | }
56 | if (2 === $optionLength) {
57 | $integers = unpack('ntimeout', $optionValue, $offset);
58 | if (false === $integers) {
59 | throw new DecodeException(self::NAME, $optionValue);
60 | }
61 | $offset += 2;
62 | $this->timeout = $integers['timeout'];
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/docs/Parser/Basic-Usage.md:
--------------------------------------------------------------------------------
1 | Basic Usage
2 | ===========
3 |
4 | ```php
5 | $file = file_get_contents('/path/to/example.com.txt');
6 |
7 | $zone = Badcow\DNS\Parser\Parser::parse('example.com.', $file);
8 | ```
9 |
10 | Simple as that.
11 |
12 |
13 | ## Example
14 |
15 | ### BIND Record
16 | ```text
17 | $ORIGIN example.com.
18 | $TTL 3600
19 | @ IN SOA (
20 | example.com. ; MNAME
21 | post.example.com. ; RNAME
22 | 2014110501 ; SERIAL
23 | 3600 ; REFRESH
24 | 14400 ; RETRY
25 | 604800 ; EXPIRE
26 | 3600 ; MINIMUM
27 | )
28 |
29 | ; NS RECORDS
30 | @ NS ns1.nameserver.com.
31 | @ NS ns2.nameserver.com.
32 |
33 | info TXT "This is some additional \"information\""
34 |
35 | ; A RECORDS
36 | sub.domain A 192.168.1.42 ; This is a local ip.
37 |
38 | ; AAAA RECORDS
39 | ipv6.domain AAAA ::1 ; This is an IPv6 domain.
40 |
41 | ; MX RECORDS
42 | @ MX 10 mail-gw1.example.net.
43 | @ MX 20 mail-gw2.example.net.
44 | @ MX 30 mail-gw3.example.net.
45 |
46 | mail IN TXT "THIS IS SOME TEXT; WITH A SEMICOLON"
47 | ```
48 |
49 | ### Processing the record
50 | ```php
51 | getName(); //Returns example.com.
59 | foreach ($zone->getResourceRecords() as $record) {
60 | $record->getName();
61 | $record->getClass();
62 | $record->getTtl();
63 | $record->getRdata()->toText();
64 | }
65 | ```
--------------------------------------------------------------------------------
/tests/Edns/Option/CLIENT_SUBNETTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Edns\Option;
15 |
16 | use Badcow\DNS\Edns\Option\CLIENT_SUBNET;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class CLIENT_SUBNETTest extends TestCase
20 | {
21 | /**
22 | * @var CLIENT_SUBNET
23 | */
24 | private $option;
25 |
26 | public function setUp(): void
27 | {
28 | $this->option = new CLIENT_SUBNET();
29 | }
30 |
31 | public function testGetterSetters(): void
32 | {
33 | $this->assertEquals('CLIENT_SUBNET', $this->option->getName());
34 | }
35 |
36 | public function testToWire(): void
37 | {
38 | $address = '200.100.50.1';
39 | $this->option->setFamily(CLIENT_SUBNET::FAMILY_IPV4); // 0x0001
40 | $this->option->setSourceNetmask(24); // 0x18
41 | $this->option->setScopeNetmask(22); // 0x16
42 | $this->option->setAddress($address);
43 | $expectation = "\x00\x01\x18\x16".inet_pton($address);
44 |
45 | $this->assertEquals($expectation, $this->option->toWire());
46 | }
47 |
48 | public function testFromWire(): void
49 | {
50 | $address = '200.100.50.1';
51 | $wire = "\x00\x01\x18\x16".inet_pton($address);
52 | $option = new CLIENT_SUBNET();
53 | $option->fromWire($wire);
54 | $this->assertEquals(CLIENT_SUBNET::FAMILY_IPV4, $option->getFamily());
55 | $this->assertEquals(24, $option->getSourceNetmask());
56 | $this->assertEquals(22, $option->getScopeNetmask());
57 | $this->assertEquals($address, $option->getAddress());
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/Parser/CustomHandlerTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Parser;
15 |
16 | use Badcow\DNS\Classes;
17 | use Badcow\DNS\Parser\ParseException;
18 | use Badcow\DNS\Parser\Parser;
19 | use Badcow\DNS\Rdata\TXT;
20 | use PHPUnit\Framework\TestCase;
21 |
22 | class CustomHandlerTest extends TestCase
23 | {
24 | public function spfHandler(\ArrayIterator $iterator): TXT
25 | {
26 | $string = '';
27 | while ($iterator->valid()) {
28 | $string .= $iterator->current().' ';
29 | $iterator->next();
30 | }
31 | $string = trim($string, ' "'); //Remove whitespace and quotes
32 |
33 | $spf = new TXT();
34 | $spf->setText($string);
35 |
36 | return $spf;
37 | }
38 |
39 | /**
40 | * @throws ParseException
41 | */
42 | public function testCustomHandler(): void
43 | {
44 | $customHandlers = ['SPF' => [$this, 'spfHandler']];
45 |
46 | $record = 'example.com. 7200 IN SPF "v=spf1 a mx ip4:69.64.153.131 include:_spf.google.com ~all"';
47 | $parser = new Parser($customHandlers);
48 | $zone = $parser->makeZone('example.com.', $record);
49 | $rr = $zone->getResourceRecords()[0];
50 |
51 | $this->assertEquals('TXT', $rr->getType());
52 | $this->assertEquals('example.com.', $rr->getName());
53 | $this->assertEquals(7200, $rr->getTtl());
54 | $this->assertEquals(Classes::INTERNET, $rr->getClass());
55 | $this->assertNotNull($rr->getRdata());
56 | $this->assertEquals('v=spf1 a mx ip4:69.64.153.131 include:_spf.google.com ~all', $rr->getRdata()->getText());
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/Rdata/SigTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Algorithms;
17 | use Badcow\DNS\Rdata\A;
18 | use Badcow\DNS\Rdata\Factory;
19 | use PHPUnit\Framework\TestCase;
20 |
21 | class SigTest extends TestCase
22 | {
23 | public function testFactory(): void
24 | {
25 | $signature = base64_decode('oJB1W6WNGv+ldvQ3WDG0MQkg5IEhjRip8WTrPYGv07h108dUKGMeDPKijVCHX3DDKdfb+v6oB9wfuh3DTJXUA'.
26 | 'fI/M0zmO/zz8bW0Rznl8O3tGNazPwQKkRN20XPXV6nwwfoXmJQbsLNrLfkGJ5D6fwFm8nN+6pBzeDQfsS3Ap3o=');
27 |
28 | $sig = Factory::SIG(
29 | A::TYPE,
30 | Algorithms::RSASHA1,
31 | 3,
32 | 86400,
33 | \DateTime::createFromFormat('Ymd H:i:s', '20220101 00:00:00'),
34 | \DateTime::createFromFormat('Ymd H:i:s', '20180101 00:00:00'),
35 | 2642,
36 | 'example.com.',
37 | $signature
38 | );
39 |
40 | $this->assertEquals(A::TYPE, $sig->getTypeCovered());
41 | $this->assertEquals(Algorithms::RSASHA1, $sig->getAlgorithm());
42 | $this->assertEquals(3, $sig->getLabels());
43 | $this->assertEquals(86400, $sig->getOriginalTtl());
44 | $this->assertEquals(\DateTime::createFromFormat('Ymd H:i:s', '20220101 00:00:00'), $sig->getSignatureExpiration());
45 | $this->assertEquals(\DateTime::createFromFormat('Ymd H:i:s', '20180101 00:00:00'), $sig->getSignatureInception());
46 | $this->assertEquals(2642, $sig->getKeyTag());
47 | $this->assertEquals('example.com.', $sig->getSignersName());
48 | $this->assertEquals($signature, $sig->getSignature());
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/lib/Rdata/RdataInterface.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | interface RdataInterface
17 | {
18 | /**
19 | * Get the string representation of the Rdata type.
20 | *
21 | * @return string Rdata type, e.g. "A", "MX", "NS", etc.
22 | */
23 | public function getType(): string;
24 |
25 | /**
26 | * Get the integer type code of the Rdata type as defined by IANA.
27 | *
28 | * @return int IANA Rdata type code
29 | */
30 | public function getTypeCode(): int;
31 |
32 | /**
33 | * Return the string representation of the Rdata.
34 | *
35 | * @return string formatted Rdata output as would appear in BIND records
36 | */
37 | public function toText(): string;
38 |
39 | /**
40 | * Return a DNS Server response formatted representation of the Rdata.
41 | *
42 | * @return string packed binary form of Rdata
43 | */
44 | public function toWire(): string;
45 |
46 | /**
47 | * Populate Rdata object from its textual representation.
48 | *
49 | * @param string $text Rendered Rdata text to populate object
50 | */
51 | public function fromText(string $text): void;
52 |
53 | /**
54 | * Populate Rdata from its wire representation.
55 | *
56 | * @param string $rdata packed binary form of Rdata
57 | * @param int $offset the current offset or pointer, position of the start of Rdata relative to the whole $rdata string
58 | * @param int|null $rdLength the length of the Rdata string, if null it is taken to be the whole $rdata parameter string
59 | */
60 | public function fromWire(string $rdata, int &$offset = 0, ?int $rdLength = null): void;
61 | }
62 |
--------------------------------------------------------------------------------
/tests/Edns/Option/TCP_KEEPALIVETest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Edns\Option;
15 |
16 | use Badcow\DNS\Edns\Option\DecodeException;
17 | use Badcow\DNS\Edns\Option\TCP_KEEPALIVE;
18 | use PHPUnit\Framework\TestCase;
19 |
20 | class TCP_KEEPALIVETest extends TestCase
21 | {
22 | /**
23 | * @var TCP_KEEPALIVE
24 | */
25 | private $option;
26 |
27 | public function setUp(): void
28 | {
29 | $this->option = new TCP_KEEPALIVE();
30 | }
31 |
32 | public function testGetterSetters(): void
33 | {
34 | $this->assertEquals('TCP_KEEPALIVE', $this->option->getName());
35 | }
36 |
37 | public function testToWire(): void
38 | {
39 | $noLimit = new TCP_KEEPALIVE();
40 | $this->assertEquals('', $noLimit->toWire());
41 |
42 | $withLimit = new TCP_KEEPALIVE();
43 | $withLimit->setTimeout(1000);
44 | $this->assertEquals("\x03\xE8", $withLimit->toWire());
45 | }
46 |
47 | public function testFromWire1(): void
48 | {
49 | $wire = '';
50 | $noLimit = new TCP_KEEPALIVE();
51 | $noLimit->fromWire($wire);
52 | $this->assertNull($noLimit->getTimeout());
53 | }
54 |
55 | public function testFromWire2(): void
56 | {
57 | $this->expectException(DecodeException::class);
58 | $wire = "\xFF\xFF\xFF";
59 | $wrongTimeout = new TCP_KEEPALIVE();
60 | $wrongTimeout->fromWire($wire);
61 | }
62 |
63 | public function testFromWire3(): void
64 | {
65 | $wire = "\x07\xD0";
66 | $withLimit = new TCP_KEEPALIVE();
67 | $withLimit->fromWire($wire);
68 | $this->assertEquals(2000, $withLimit->getTimeout());
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/tests/Rdata/SpfTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Rdata\Factory;
17 | use Badcow\DNS\Rdata\SPF;
18 | use PHPUnit\Framework\TestCase;
19 |
20 | class SpfTest extends TestCase
21 | {
22 | public function testFromText(): void
23 | {
24 | $text = '"v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.123 a -all"';
25 |
26 | /** @var SPF $spf */
27 | $spf = new SPF();
28 | $spf->fromText($text);
29 |
30 | $this->assertEquals('SPF', $spf->getType());
31 | $this->assertEquals('v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.123 a -all', $spf->getText());
32 | }
33 |
34 | public function testToText(): void
35 | {
36 | $spf = new SPF();
37 | $spf->setText('v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.123 a -all');
38 |
39 | $this->assertEquals('"v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.123 a -all"', $spf->toText());
40 | $this->assertEquals('SPF', $spf->getType());
41 | }
42 |
43 | public function testWire(): void
44 | {
45 | $wireFormat = chr(49).'v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.123 a -all';
46 | $offset = 1;
47 | $rdLength = 49;
48 |
49 | $spf = new SPF();
50 | $spf->setText('v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.123 a -all');
51 |
52 | $fromWire = new SPF();
53 | $fromWire->fromWire($wireFormat, $offset, $rdLength);
54 | $this->assertEquals($spf, $fromWire);
55 | }
56 |
57 | public function testFactory(): void
58 | {
59 | $wireFormat = 'v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.123 a -all';
60 |
61 | $spf = new SPF();
62 | $spf->setText($wireFormat);
63 |
64 | $this->assertEquals($spf, Factory::SPF($wireFormat));
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/lib/Edns/Option/UnknownOption.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Edns\Option;
15 |
16 | class UnknownOption implements OptionInterface
17 | {
18 | /**
19 | * @var int
20 | */
21 | private $optionCode;
22 |
23 | /**
24 | * @var string|null
25 | */
26 | private $data;
27 |
28 | /**
29 | * @var string|null
30 | */
31 | private $name;
32 |
33 | public function setOptionCode(int $optionCode): void
34 | {
35 | $this->optionCode = $optionCode;
36 | }
37 |
38 | public function getNameCode(): int
39 | {
40 | return $this->optionCode;
41 | }
42 |
43 | public function getName(): string
44 | {
45 | if (null !== $this->name) {
46 | return $this->name;
47 | }
48 |
49 | return 'OPTION'.$this->optionCode;
50 | }
51 |
52 | public function setName(string $name): void
53 | {
54 | $this->name = $name;
55 | }
56 |
57 | /**
58 | * @return string
59 | */
60 | public function getData(): ?string
61 | {
62 | return $this->data;
63 | }
64 |
65 | /**
66 | * @param string $data
67 | */
68 | public function setData(?string $data): void
69 | {
70 | $this->data = $data;
71 | }
72 |
73 | public function getCode(): int
74 | {
75 | return $this->optionCode;
76 | }
77 |
78 | public function toWire(): string
79 | {
80 | return $this->data ?? '';
81 | }
82 |
83 | public function fromWire(string $optionValue, int &$offset = 0, ?int $optionLength = null): void
84 | {
85 | $optionLength = $optionLength ?? strlen($optionValue);
86 | $this->setData(substr($optionValue, $offset, $optionLength));
87 | $offset += $optionLength;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/lib/Edns/Option/Factory.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Edns\Option;
15 |
16 | class Factory
17 | {
18 | /**
19 | * Creates a new Option object from a name.
20 | *
21 | * @throws UnsupportedOptionException
22 | */
23 | public static function newOptionFromName(string $name): OptionInterface
24 | {
25 | if (!self::isOptionImplemented($name)) {
26 | throw new UnsupportedOptionException($name);
27 | }
28 |
29 | $className = self::getOptionClassName($name);
30 | $optionInterface = new $className();
31 | if (!$optionInterface instanceof OptionInterface) {
32 | throw new \UnexpectedValueException(sprintf('Badcow\DNS\Edns\Option expected; "%s" instantiated.', gettype($optionInterface)));
33 | }
34 |
35 | return $optionInterface;
36 | }
37 |
38 | /**
39 | * @throws UnsupportedOptionException
40 | */
41 | public static function newOptionFromId(int $id): OptionInterface
42 | {
43 | return self::newOptionFromName(Codes::getName($id));
44 | }
45 |
46 | public static function isOptionImplemented(string $name): bool
47 | {
48 | return class_exists(self::getOptionClassName($name));
49 | }
50 |
51 | public static function isOptionCodeImplemented(int $optionCode): bool
52 | {
53 | try {
54 | return self::isOptionImplemented(Codes::getName($optionCode));
55 | } catch (UnsupportedOptionException $e) {
56 | return false;
57 | }
58 | }
59 |
60 | /**
61 | * Get the fully qualified class name of the Option class for $option.
62 | */
63 | public static function getOptionClassName(string $option): string
64 | {
65 | return __NAMESPACE__.'\\'.strtoupper($option);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/tests/Parser/ResourceRecordIteratorTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Parser;
15 |
16 | use Badcow\DNS\Parser\ResourceRecordIterator;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class ResourceRecordIteratorTest extends TestCase
20 | {
21 | public function testNavigation(): void
22 | {
23 | $string = 'this is a string consisting of some words';
24 | $iterator = new ResourceRecordIterator($string);
25 |
26 | $this->assertEquals(0, $iterator->key());
27 |
28 | $iterator->end();
29 | $this->assertEquals('words', $iterator->current());
30 |
31 | $iterator->next();
32 | $this->assertFalse($iterator->valid());
33 |
34 | $iterator->prev();
35 | $this->assertEquals('words', $iterator->current());
36 |
37 | $iterator->seek(2);
38 | $this->assertEquals('a', $iterator->current());
39 |
40 | $iterator->prev();
41 | $this->assertEquals('is', $iterator->current());
42 |
43 | $iterator->seek(0);
44 | $this->assertEquals('this', $iterator->current());
45 |
46 | $this->expectException(\OutOfBoundsException::class);
47 | $iterator->prev();
48 | }
49 |
50 | public function testGetRemainingAsString(): void
51 | {
52 | $string = 'this is a string consisting of some words';
53 | $iterator = new ResourceRecordIterator($string);
54 | $iterator->seek(3);
55 |
56 | $this->assertEquals('string consisting of some words', $iterator->getRemainingAsString());
57 | }
58 |
59 | public function testToString(): void
60 | {
61 | $string = 'this is a string consisting of some words';
62 | $iterator = new ResourceRecordIterator($string);
63 |
64 | $this->assertEquals($string, (string) $iterator);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/tests/Rdata/RpTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Rdata\Factory;
17 | use Badcow\DNS\Rdata\RP;
18 | use PHPUnit\Framework\TestCase;
19 |
20 | class RpTest extends TestCase
21 | {
22 | public function testFactory(): void
23 | {
24 | $expectation = 'louie.trantor.umd.edu. lam1.people.umd.edu.';
25 | $rp = Factory::RP('louie.trantor.umd.edu.', 'lam1.people.umd.edu.');
26 |
27 | $this->assertEquals($expectation, $rp->toText());
28 | }
29 |
30 | public function testFromText(): void
31 | {
32 | $rp = new RP();
33 | $rp->fromText('louie.trantor.umd.edu. lam1.people.umd.edu.');
34 |
35 | $this->assertEquals('louie.trantor.umd.edu.', $rp->getMailboxDomainName());
36 | $this->assertEquals('lam1.people.umd.edu.', $rp->getTxtDomainName());
37 | }
38 |
39 | public function testToText(): void
40 | {
41 | $expectation = 'louie.trantor.umd.edu. lam1.people.umd.edu.';
42 | $rp = new RP();
43 | $rp->setMailboxDomainName('louie.trantor.umd.edu.');
44 | $rp->setTxtDomainName('lam1.people.umd.edu.');
45 |
46 | $this->assertEquals($expectation, $rp->toText());
47 | }
48 |
49 | public function testWire(): void
50 | {
51 | $expectation = chr(5).'louie'.chr(7).'trantor'.chr(3).'umd'.chr(3).'edu'.chr(0).
52 | chr(4).'lam1'.chr(6).'people'.chr(3).'umd'.chr(3).'edu'.chr(0);
53 |
54 | $rp = new RP();
55 | $rp->setMailboxDomainName('louie.trantor.umd.edu.');
56 | $rp->setTxtDomainName('lam1.people.umd.edu.');
57 |
58 | $this->assertEquals($expectation, $rp->toWire());
59 | $fromWire = new RP();
60 | $fromWire->fromWire($expectation);
61 | $this->assertEquals($rp, $fromWire);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lib/Parser/StringIterator.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Parser;
15 |
16 | class StringIterator extends \ArrayIterator
17 | {
18 | /**
19 | * StringIterator constructor.
20 | */
21 | public function __construct(string $string = '')
22 | {
23 | parent::__construct(str_split($string));
24 | }
25 |
26 | /**
27 | * Test if current character is equal to a value, or (if $value is an array) is one of the values in the array.
28 | *
29 | * @param string|array $value test if current character is equal to, or is in, $value
30 | *
31 | * @return bool true if current character is, or is one of, the values
32 | */
33 | public function is($value): bool
34 | {
35 | if (is_array($value)) {
36 | return in_array($this->current(), $value);
37 | }
38 |
39 | return (string) $value === $this->current();
40 | }
41 |
42 | /**
43 | * Test if current character is not equal to a value, or (if $value is an array) is not any of the values in the array.
44 | *
45 | * @param string|array $value test if current character is not equal to, or is not any of, $value
46 | *
47 | * @return bool true if current character is not, or is not one of, the values
48 | */
49 | public function isNot($value): bool
50 | {
51 | return !$this->is($value);
52 | }
53 |
54 | public function getRemainingAsString(): string
55 | {
56 | $string = '';
57 | while ($this->valid()) {
58 | $string .= $this->current();
59 | $this->next();
60 | }
61 |
62 | return $string;
63 | }
64 |
65 | /**
66 | * @return string
67 | */
68 | public function __toString()
69 | {
70 | return implode('', $this->getArrayCopy());
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/tests/Rdata/CdsTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Algorithms;
17 | use Badcow\DNS\Rdata\CDS;
18 | use Badcow\DNS\Rdata\Factory;
19 | use PHPUnit\Framework\TestCase;
20 |
21 | class CdsTest extends TestCase
22 | {
23 | private static $digest = '2BB183AF5F22588179A53B0A98631FAD1A292118';
24 |
25 | public function testFactory(): void
26 | {
27 | $keyTag = 60485;
28 | $ds = Factory::CDS($keyTag, Algorithms::RSASHA1, hex2bin(self::$digest), CDS::DIGEST_SHA1);
29 |
30 | $this->assertEquals($keyTag, $ds->getKeyTag());
31 | $this->assertEquals(Algorithms::RSASHA1, $ds->getAlgorithm());
32 | $this->assertEquals(hex2bin(self::$digest), $ds->getDigest());
33 | $this->assertEquals(CDS::DIGEST_SHA1, $ds->getDigestType());
34 | }
35 |
36 | public function testFromText(): void
37 | {
38 | $expectation = new CDS();
39 | $expectation->setKeyTag(60485);
40 | $expectation->setAlgorithm(Algorithms::RSASHA1);
41 | $expectation->setDigestType(CDS::DIGEST_SHA1);
42 | $expectation->setDigest(hex2bin(self::$digest));
43 |
44 | $cds = new CDS();
45 | $cds->fromText('60485 5 1 '.self::$digest);
46 | $this->assertInstanceOf(CDS::class, $cds);
47 | $this->assertEquals($expectation, $cds);
48 | }
49 |
50 | public function testWire(): void
51 | {
52 | $cds = new CDS();
53 | $cds->setKeyTag(60485);
54 | $cds->setAlgorithm(Algorithms::RSASHA1);
55 | $cds->setDigestType(CDS::DIGEST_SHA1);
56 | $cds->setDigest(hex2bin(self::$digest));
57 | $wireFormat = $cds->toWire();
58 | $fromWire = new CDS();
59 | $fromWire->fromWire($wireFormat);
60 |
61 | $this->assertInstanceOf(CDS::class, $fromWire);
62 | $this->assertEquals($cds, $fromWire);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tests/Rdata/DlvTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Algorithms;
17 | use Badcow\DNS\Rdata\DLV;
18 | use Badcow\DNS\Rdata\Factory;
19 | use PHPUnit\Framework\TestCase;
20 |
21 | class DlvTest extends TestCase
22 | {
23 | private static $digest = '2BB183AF5F22588179A53B0A98631FAD1A292118';
24 |
25 | public function testFactory(): void
26 | {
27 | $keyTag = 60485;
28 | $ds = Factory::DLV($keyTag, Algorithms::RSASHA1, hex2bin(self::$digest), DLV::DIGEST_SHA1);
29 |
30 | $this->assertEquals($keyTag, $ds->getKeyTag());
31 | $this->assertEquals(Algorithms::RSASHA1, $ds->getAlgorithm());
32 | $this->assertEquals(hex2bin(self::$digest), $ds->getDigest());
33 | $this->assertEquals(DLV::DIGEST_SHA1, $ds->getDigestType());
34 | }
35 |
36 | public function testFromText(): void
37 | {
38 | $expectation = new DLV();
39 | $expectation->setKeyTag(60485);
40 | $expectation->setAlgorithm(Algorithms::RSASHA1);
41 | $expectation->setDigestType(DLV::DIGEST_SHA1);
42 | $expectation->setDigest(hex2bin(self::$digest));
43 |
44 | $dlv = new DLV();
45 | $dlv->fromText('60485 5 1 '.self::$digest);
46 | $this->assertInstanceOf(DLV::class, $dlv);
47 | $this->assertEquals($expectation, $dlv);
48 | }
49 |
50 | public function testWire(): void
51 | {
52 | $dlv = new DLV();
53 | $dlv->setKeyTag(60485);
54 | $dlv->setAlgorithm(Algorithms::RSASHA1);
55 | $dlv->setDigestType(DLV::DIGEST_SHA1);
56 | $dlv->setDigest(hex2bin(self::$digest));
57 | $wireFormat = $dlv->toWire();
58 | $fromWire = new DLV();
59 | $fromWire->fromWire($wireFormat);
60 |
61 | $this->assertInstanceOf(DLV::class, $fromWire);
62 | $this->assertEquals($dlv, $fromWire);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/lib/Rdata/RP.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | use Badcow\DNS\Message;
17 | use Badcow\DNS\Parser\Tokens;
18 |
19 | /**
20 | * {@link https://tools.ietf.org/html/rfc1183}.
21 | */
22 | class RP implements RdataInterface
23 | {
24 | use RdataTrait;
25 |
26 | public const TYPE = 'RP';
27 | public const TYPE_CODE = 17;
28 |
29 | /**
30 | * @var string
31 | */
32 | private $mailboxDomainName;
33 |
34 | /**
35 | * @var string
36 | */
37 | private $txtDomainName;
38 |
39 | public function getMailboxDomainName(): string
40 | {
41 | return $this->mailboxDomainName;
42 | }
43 |
44 | public function setMailboxDomainName(string $mailboxDomainName): void
45 | {
46 | $this->mailboxDomainName = $mailboxDomainName;
47 | }
48 |
49 | public function getTxtDomainName(): string
50 | {
51 | return $this->txtDomainName;
52 | }
53 |
54 | public function setTxtDomainName(string $txtDomainName): void
55 | {
56 | $this->txtDomainName = $txtDomainName;
57 | }
58 |
59 | public function toText(): string
60 | {
61 | return sprintf('%s %s', $this->mailboxDomainName, $this->txtDomainName);
62 | }
63 |
64 | public function toWire(): string
65 | {
66 | return Message::encodeName($this->mailboxDomainName).Message::encodeName($this->txtDomainName);
67 | }
68 |
69 | public function fromText(string $text): void
70 | {
71 | $rdata = explode(Tokens::SPACE, $text);
72 | $this->setMailboxDomainName($rdata[0]);
73 | $this->setTxtDomainName($rdata[1]);
74 | }
75 |
76 | public function fromWire(string $rdata, int &$offset = 0, ?int $rdLength = null): void
77 | {
78 | $this->setMailboxDomainName(Message::decodeName($rdata, $offset));
79 | $this->setTxtDomainName(Message::decodeName($rdata, $offset));
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/tests/Resources/example.com_filled-out.txt:
--------------------------------------------------------------------------------
1 | $ORIGIN example.com.
2 | $TTL 3600
3 | example.com. 3600 IN SOA (
4 | example.com. ; MNAME
5 | post.example.com. ; RNAME
6 | 2014110501 ; SERIAL
7 | 3600 ; REFRESH
8 | 14400 ; RETRY
9 | 604800 ; EXPIRE
10 | 3600 ; MINIMUM
11 | )
12 |
13 | ; NS RECORDS
14 | example.com. 3600 IN NS ns1.nameserver.com.
15 | example.com. 3600 IN NS ns2.nameserver.com.
16 |
17 | ; A RECORDS
18 | sub.domain.example.com. 3600 IN A 192.168.1.42; This is a local ip.
19 |
20 | ; AAAA RECORDS
21 | ipv6.domain.example.com. 3600 IN AAAA 0000:0000:0000:0000:0000:0000:0000:0001; This is an IPv6 domain.
22 |
23 | ; MX RECORDS
24 | example.com. 3600 IN MX 10 mail-gw1.example.net.
25 | example.com. 3600 IN MX 20 mail-gw2.example.net.
26 | example.com. 3600 IN MX 30 mail-gw3.example.net.
27 |
28 | ; LOC RECORDS
29 | canberra.example.com. 3600 IN LOC (
30 | 35 18 27.000 S ; LATITUDE
31 | 149 7 27.840 E ; LONGITUDE
32 | 500.00m ; ALTITUDE
33 | 20.12m ; SIZE
34 | 200.30m ; HORIZONTAL PRECISION
35 | 300.10m ; VERTICAL PRECISION
36 | ); This is Canberra
37 |
38 | ; SRV RECORDS
39 | _ftp._tcp.example.com. 3600 IN SRV 10 10 21 files.example.com.
40 |
41 | ; APL RECORDS
42 | multicast.example.com. 3600 IN APL (
43 | 1:192.168.0.0/23
44 | 2:2001:acad:1::/112
45 | !1:192.168.1.64/28
46 | !2:2001:acad:1::8/128
47 | )
48 |
--------------------------------------------------------------------------------
/lib/Classes.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS;
15 |
16 | class Classes
17 | {
18 | public const INTERNET = 'IN';
19 | public const CSNET = 'CS';
20 | public const CHAOS = 'CH';
21 | public const HESIOD = 'HS';
22 |
23 | /**
24 | * @var array
25 | */
26 | public static $classes = [
27 | self::CHAOS => 'CHAOS',
28 | self::CSNET => 'CSNET',
29 | self::HESIOD => 'Hesiod',
30 | self::INTERNET => 'Internet',
31 | ];
32 |
33 | public const CLASS_IDS = [
34 | self::CHAOS => 3,
35 | self::CSNET => 2,
36 | self::HESIOD => 4,
37 | self::INTERNET => 1,
38 | ];
39 |
40 | /**
41 | * @const string[]
42 | */
43 | public const IDS_CLASSES = [
44 | 1 => 'IN',
45 | 2 => 'CS',
46 | 3 => 'CH',
47 | 4 => 'HS',
48 | ];
49 |
50 | /**
51 | * Determine if a class is valid.
52 | */
53 | public static function isValid(string $class): bool
54 | {
55 | if (array_key_exists($class, self::$classes)) {
56 | return true;
57 | }
58 |
59 | return 1 === preg_match('/^CLASS\d+$/', $class);
60 | }
61 |
62 | /**
63 | * @throws \InvalidArgumentException
64 | */
65 | public static function getClassId(string $className): int
66 | {
67 | if (!self::isValid($className)) {
68 | throw new \InvalidArgumentException(sprintf('Class "%s" is not a valid DNS class.', $className));
69 | }
70 |
71 | if (1 === preg_match('/^CLASS(\d+)$/', $className, $matches)) {
72 | return (int) $matches[1];
73 | }
74 |
75 | return self::CLASS_IDS[$className];
76 | }
77 |
78 | public static function getClassName(int $classId): string
79 | {
80 | if (array_key_exists($classId, self::IDS_CLASSES)) {
81 | return self::IDS_CLASSES[$classId];
82 | }
83 |
84 | return 'CLASS'.$classId;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/tests/QuestionTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests;
15 |
16 | use Badcow\DNS\Question;
17 | use Badcow\DNS\Rdata\UnsupportedTypeException;
18 | use PHPUnit\Framework\TestCase;
19 |
20 | class QuestionTest extends TestCase
21 | {
22 | /**
23 | * @throws UnsupportedTypeException
24 | */
25 | public function testToWire(): void
26 | {
27 | $q = new Question();
28 | $q->setName('example.com.');
29 | $q->setType('NS');
30 | $q->setClass('IN');
31 |
32 | $expectation = chr(7).'example'.chr(3).'com'.chr(0).pack('nn', 2, 1);
33 |
34 | $this->assertEquals($expectation, $q->toWire());
35 | }
36 |
37 | public function testSetClassThrowsException(): void
38 | {
39 | $this->expectException(\InvalidArgumentException::class);
40 | $this->expectExceptionMessage('Invalid class: "65536".');
41 | $q = new Question();
42 | $q->setClassId(65536);
43 | }
44 |
45 | public function testSetNameThrowsException(): void
46 | {
47 | $this->expectException(\InvalidArgumentException::class);
48 | $this->expectExceptionMessage('"abc123.com" is not a fully qualified domain name.');
49 | $q = new Question();
50 | $q->setName('abc123.com');
51 | }
52 |
53 | public function testFromWire(): void
54 | {
55 | $wireFormat = chr(7).'example'.chr(3).'com'.chr(0).pack('nn', 2, 1);
56 |
57 | $q = Question::fromWire($wireFormat);
58 |
59 | $this->assertEquals('example.com.', $q->getName());
60 | $this->assertEquals('NS', $q->getType());
61 | $this->assertEquals('IN', $q->getClass());
62 | }
63 |
64 | public function testSetTypeCodeThrowsException(): void
65 | {
66 | $this->expectException(\DomainException::class);
67 | $this->expectExceptionMessage('TypeCode must be an unsigned 16-bit integer. "65536" given.');
68 | $q = new Question();
69 | $q->setTypeCode(65536);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/docs/Message.md:
--------------------------------------------------------------------------------
1 | Message
2 | =======
3 | Badcow DNS can parse a DNS message and create PHP objects representing the message.
4 |
5 | ## Example
6 | ### Parsing a message
7 | Say we have a message as a binary encoded string. The message is represented in hexadecimal below:
8 | ```
9 | 000a 8580 0001 0003 0000 0003 0376 6978 0363 6f6d 0000 0200 01c0 0c00 0200 0100 000e 1000 0b05 6973 7276 3102 7061 c00c
10 | c00c 0002 0001 0000 0e10 0009 066e 732d 6578 74c0 0cc0 0c00 0200 0100 000e 1000 0e03 6e73 3104 676e 6163 0363 6f6d 00c0
11 | 2500 0100 0100 000e 1000 04cc 98b8 86c0 3c00 0100 0100 000e 1000 04cc 98b8 40c0 5100 0100 0100 02a1 4a00 04c6 97f8 f6
12 | ```
13 | _Note: It is not necessary for you to be familiar with the encoding of a DNS message packet, but an understanding of the key
14 | components of a DNS message is of great assistance. [Please see RFC1035 section 4](https://tools.ietf.org/html/rfc1035#section-4)
15 | for details of the message format._
16 |
17 | The above message can be parsed into a PHP object...
18 | ```php
19 | $binaryMessage = '...'; // Some binary string
20 | $message = Badcow\DNS\Message::fromWire($binaryMessage); //This will return a Badcow\DNS\Message object.
21 | echo $message->getId(); //The message ID, in this case: 10.
22 | echo $message->isResponse() ? 'Response' : 'Query'; //Whether the message is a response or query.
23 |
24 | //Iterate over the QUESTION section of the message. These are a collection of Badcow\DNS\Question objects.
25 | foreach ($message->getQuestions() as $question) {
26 | echo $question->getName();
27 | echo $question->getType();
28 | }
29 |
30 | //Iterate of the ANSWER section. These are \Badcow\DNS\ResourceRecord objects.
31 | foreach ($message->getAnswers() as $answer) {
32 | echo $answer->getName();
33 | echo $answer->getType();
34 | echo $answer->getRdata()->toText(); //The rdata
35 | }
36 | ```
37 |
38 | ### Encoding a message
39 | ```php
40 | $question = new \Badcow\DNS\Question();
41 | $question->setName('example.com.');
42 | $question->setClass('IN');
43 | $question->setType('A');
44 |
45 | $message = new \Badcow\DNS\Message();
46 | $message->setId(123);
47 | $message->setQuery(true);
48 |
49 | $message->setRecursionDesired(true);
50 | $message->addQuestion($question);
51 |
52 | $binaryMessage = $message->toWire();
53 |
54 | ```
--------------------------------------------------------------------------------
/docs/Parser/Include-Directive.md:
--------------------------------------------------------------------------------
1 | $INCLUDE Directive
2 | ==================
3 | [RFC-1035](https://www.ietf.org/rfc/rfc1035.txt) section 5.1 permits the `$INCLUDE` control entry for importing external
4 | files:
5 | ```
6 | $INCLUDE [] []
7 | ```
8 | In the specification, the `file-name` can be absolute or relative; the `domain-name` is optional and, if set, specifies
9 | the `$ORIGIN` to be used for the imported sub-zone. _Exempli gratia:_
10 | ```
11 | $INCLUDE headquarters.example.com.db headquarters.example.com. ;This imports the headquarters subdomain.
12 | ```
13 | The Badcow DNS Parser will import these files if you have specified a class implementing the `Badcow\DNS\Parser\ZoneFileFetcherInterface`.
14 | This class is then passed to the parser as the second argument on its constructor:
15 | ```
16 | \Badcow\DNS\Parser\Parser::__construct(array $rdataHandlers, ZoneFileFetcherInterface $fetcher);
17 | ```
18 | The interface has only one method to be implemented, `ZoneFileFetcherInterface::fetch(string $path)`. This is called within
19 | the parser to fetch included files. This has been implemented to ensure that arbitrary files cannot be included by a zone.
20 | Additionally, it may be the case that the zone files are not kept on a local disk, so a zoneFileFetcher can be designed
21 | to grab files from any location (an SMB server, for example).
22 |
23 | ## Example
24 | The following zone file includes some subdomains at the end:
25 | ```
26 | ;/home/dns/zones/testdomain.geek.db
27 | $ORIGIN testdomain.geek.
28 | $TTL 7200
29 | @ IN SOA testdomain.geek. post.testdomain.geek. 2014110501 3600 14400 604800 3600
30 | @ IN NS ns1.nameserver.com.
31 | @ IN NS ns2.nameserver.com.
32 |
33 | $INCLUDE email-domains.txt mail.testdomain.geek.
34 | ```
35 |
36 | ```php
37 | makeZone('testdomain.geek.', $zoneFile);
53 | ```
--------------------------------------------------------------------------------
/tests/Rdata/OptTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Edns\Option\OptionInterface;
17 | use Badcow\DNS\Edns\Option\TCP_KEEPALIVE;
18 | use Badcow\DNS\Edns\Option\UnknownOption;
19 | use Badcow\DNS\Rdata\DecodeException;
20 | use Badcow\DNS\Rdata\OPT;
21 | use PHPUnit\Framework\TestCase;
22 |
23 | class OptTest extends TestCase
24 | {
25 | public function testOutput(): void
26 | {
27 | $opt = new OPT();
28 | $this->assertEmpty($opt->toText());
29 | }
30 |
31 | public function testFromText(): void
32 | {
33 | $this->expectException(\Exception::class);
34 | $text = '';
35 | $opt = new OPT();
36 | $opt->fromText($text);
37 | }
38 |
39 | public function testFromWire1(): void
40 | {
41 | $wire = "\x00\x0B\x00\x00\x00\xFF\x00\x01A";
42 |
43 | $opt = new OPT();
44 | $opt->fromWire($wire);
45 |
46 | $options = $opt->getOptions();
47 | $this->assertCount(2, $options);
48 | $this->assertContainsOnlyInstancesOf(OptionInterface::class, $options);
49 | $this->assertInstanceOf(TCP_KEEPALIVE::class, $options[0]);
50 | $this->assertInstanceOf(UnknownOption::class, $options[1]);
51 | $this->assertEquals(255, $options[1]->getNameCode());
52 | }
53 |
54 | public function testFromWire2(): void
55 | {
56 | $this->expectException(DecodeException::class);
57 | $wire = "\x00\x00";
58 | $opt = new OPT();
59 | $opt->fromWire($wire);
60 | }
61 |
62 | public function testToWire(): void
63 | {
64 | $options = [];
65 | $options[0] = new TCP_KEEPALIVE();
66 | $options[1] = new UnknownOption();
67 | $options[1]->setOptionCode(255);
68 | $options[1]->setData('A');
69 |
70 | $opt = new OPT();
71 | $this->assertEmpty($opt->toWire());
72 | $opt->setOptions($options);
73 |
74 | $wire = "\x00\x0B\x00\x00\x00\xFF\x00\x01A";
75 | $this->assertEquals($wire, $opt->toWire());
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/lib/Rdata/A.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | use Badcow\DNS\Validator;
17 |
18 | /**
19 | * @see https://tools.ietf.org/html/rfc1035#section-3.4.1
20 | */
21 | class A implements RdataInterface
22 | {
23 | use RdataTrait;
24 |
25 | public const TYPE = 'A';
26 | public const TYPE_CODE = 1;
27 |
28 | /**
29 | * @var string|null
30 | */
31 | protected $address;
32 |
33 | public function setAddress(string $address): void
34 | {
35 | if (!Validator::ipv4($address)) {
36 | throw new \InvalidArgumentException(sprintf('The address "%s" is not a valid IPv4 address.', $address));
37 | }
38 |
39 | $this->address = $address;
40 | }
41 |
42 | /**
43 | * @return string
44 | */
45 | public function getAddress(): ?string
46 | {
47 | return $this->address;
48 | }
49 |
50 | public function toText(): string
51 | {
52 | return $this->address ?? '';
53 | }
54 |
55 | /**
56 | * @throws \InvalidArgumentException
57 | */
58 | public function toWire(): string
59 | {
60 | if (!isset($this->address)) {
61 | throw new \InvalidArgumentException('No IP address has been set.');
62 | }
63 |
64 | if (false === $encoded = @inet_pton($this->address)) {
65 | throw new \InvalidArgumentException(sprintf('The IP address "%s" cannot be encoded. Check that it is a valid IP address.', $this->address));
66 | }
67 |
68 | return $encoded;
69 | }
70 |
71 | public function fromText(string $text): void
72 | {
73 | $this->setAddress($text);
74 | }
75 |
76 | /**
77 | * @throws DecodeException
78 | */
79 | public function fromWire(string $rdata, int &$offset = 0, ?int $rdLength = null): void
80 | {
81 | if (false === $address = @inet_ntop(substr($rdata, $offset, 4))) {
82 | throw new DecodeException(static::TYPE, $rdata);
83 | }
84 | $offset += 4;
85 |
86 | $this->setAddress($address);
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/docs/Parser/Unkown-Types.md:
--------------------------------------------------------------------------------
1 | Parsing Unknown or Unsupported Types
2 | ====================================
3 |
4 | PolymorphicRdata Class
5 | ----------------------
6 |
7 | In DNS records, there may be rdata types that are not implemented in this library (though these should now be few). For
8 | example:
9 | ```text
10 | files.example.com. IN 3600 MD UDEL.ARPA.
11 | ```
12 | The above entry refers to the obsolete `MD` resource data type. Whilst `MD` is a valid rdata type, it has not been implemented
13 | in the Badcow DNS Library. When this `MD` record is parsed, it will parsed into an instance of `Badcow\DNS\Rdata\PolymorphicRdata`.
14 |
15 | The `PolymorphicRdata` class is used for any *valid* rdata type that has not been implemented by the library and whose records
16 | have a valid text representation. When this type is outputted to text, the data will remain unchanged, but the data cannot
17 | be outputted in a wire format.
18 |
19 | If, however, you are using a wholly unknown Rdata type (i.e. one which has not been specified by IANA) then the parser will
20 | throw a `ParseException`. In this case, if the rdata type is valid (or apart of some specific functionality not otherwise
21 | defined) it may be useful define a [custom rdata handler](Custom-Rdata-Handlers.md).
22 |
23 | UnknownType Class
24 | ------------------
25 | The same record from above can be represented in a text file as:
26 | ```text
27 | files.example.com. IN 3600 TYPE3 \# 11 04 55 44 45 4c 04 41 52 50 41 00
28 | ```
29 | This representation conforms with _RFC 3597 - Handling of Unknown DNS Resource Record (RR) Types_. In this case, the record
30 | will be parsed into an instance of `Badcow\DNS\Rdata\UnknownType`.
31 |
32 | The `UnknownType` class is used to handle records that are formatted in this way. This class can output the _wire format_
33 | of the record and the text representation above. This is the preferred way of handling unknown, unsupported, or otherwise
34 | "made up" rdata types.
35 |
36 | If the type is implemented, then the parser will render an instance of that type instead of the `UnknownType` class.
37 | _Exempli Gratia_:
38 | ```text
39 | files.example.com. IN 3600 TYPE1 \# 4 c0 a8 01 64
40 | ```
41 | The above record type, TYPE1, is a known and supported type - an `A` record with the four octets (bytes) of the IP address
42 | `192.168.1.100`. This record will be parsed to an instance of `Badcow\DNS\Rdata\A` and *NOT* as an `UnknownType` class.
--------------------------------------------------------------------------------
/lib/Edns/Option/COOKIE.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Edns\Option;
15 |
16 | class COOKIE implements OptionInterface
17 | {
18 | use OptionTrait;
19 |
20 | public const NAME = 'COOKIE';
21 | public const CODE = 10;
22 |
23 | /**
24 | * @var string|null
25 | */
26 | protected $clientCookie;
27 |
28 | /**
29 | * @var string|null
30 | */
31 | protected $serverCookie;
32 |
33 | public function getClientCookie(): ?string
34 | {
35 | return $this->clientCookie;
36 | }
37 |
38 | public function setClientCookie(?string $clientCookie): void
39 | {
40 | if (null !== $clientCookie and 8 != strlen($clientCookie)) {
41 | throw new \InvalidArgumentException('Length of client cookie must be 8 bytes');
42 | }
43 | $this->clientCookie = $clientCookie;
44 | }
45 |
46 | public function getServerCookie(): ?string
47 | {
48 | return $this->serverCookie;
49 | }
50 |
51 | public function setServerCookie(?string $serverCookie): void
52 | {
53 | if (null !== $serverCookie) {
54 | $length = strlen($serverCookie);
55 | if ($length < 8 or $length > 32) {
56 | throw new \InvalidArgumentException('Length of server cookie must be between 8 to 32 bytes');
57 | }
58 | }
59 | $this->serverCookie = $serverCookie;
60 | }
61 |
62 | public function toWire(): string
63 | {
64 | return $this->clientCookie.$this->serverCookie;
65 | }
66 |
67 | public function fromWire(string $optionValue, int &$offset = 0, ?int $optionLength = null): void
68 | {
69 | $optionLength = $optionLength ?? strlen($optionValue);
70 | if ($optionLength < 8 or (8 != $optionLength and ($optionLength < 16 or $optionLength > 40))) {
71 | throw new DecodeException(static::NAME, $optionValue);
72 | }
73 | $this->clientCookie = substr($optionValue, $offset, 8);
74 | $offset += 8;
75 | if ($optionLength > 8) {
76 | $this->serverCookie = substr($optionValue, $offset, $optionLength - 8);
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/tests/Rdata/TaTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Algorithms;
17 | use Badcow\DNS\Rdata\Factory;
18 | use Badcow\DNS\Rdata\TA;
19 | use PHPUnit\Framework\TestCase;
20 |
21 | class TaTest extends TestCase
22 | {
23 | private static $digest = '2BB183AF5F22588179A53B0A98631FAD1A292118';
24 |
25 | public function testOutput(): void
26 | {
27 | $expectation = '60485 5 1 '.self::$digest;
28 |
29 | $ta = new TA();
30 | $ta->setKeyTag(60485);
31 | $ta->setAlgorithm(Algorithms::RSASHA1);
32 | $ta->setDigestType(TA::DIGEST_SHA1);
33 | $ta->setDigest(hex2bin(self::$digest));
34 |
35 | $this->assertEquals($expectation, $ta->toText());
36 | }
37 |
38 | public function testFactory(): void
39 | {
40 | $keyTag = 60485;
41 | $ta = Factory::TA($keyTag, Algorithms::RSASHA1, self::$digest, TA::DIGEST_SHA1);
42 |
43 | $this->assertEquals($keyTag, $ta->getKeyTag());
44 | $this->assertEquals(Algorithms::RSASHA1, $ta->getAlgorithm());
45 | $this->assertEquals(self::$digest, $ta->getDigest());
46 | $this->assertEquals(TA::DIGEST_SHA1, $ta->getDigestType());
47 | }
48 |
49 | public function testFromText(): void
50 | {
51 | $expectation = new TA();
52 | $expectation->setKeyTag(60485);
53 | $expectation->setAlgorithm(Algorithms::RSASHA1);
54 | $expectation->setDigestType(TA::DIGEST_SHA1);
55 | $expectation->setDigest(hex2bin(self::$digest));
56 |
57 | $fromText = new TA();
58 | $fromText->fromText('60485 5 1 '.self::$digest);
59 | $this->assertEquals($expectation, $fromText);
60 | }
61 |
62 | public function testWire(): void
63 | {
64 | $ta = new TA();
65 | $ta->setKeyTag(60485);
66 | $ta->setAlgorithm(Algorithms::RSASHA1);
67 | $ta->setDigestType(TA::DIGEST_SHA1);
68 | $ta->setDigest(hex2bin(self::$digest));
69 | $wireFormat = $ta->toWire();
70 |
71 | $fromWire = new TA();
72 | $fromWire->fromWire($wireFormat);
73 | $this->assertEquals($ta, $fromWire);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/lib/Edns/Option/Codes.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Edns\Option;
15 |
16 | class Codes
17 | {
18 | public const LLQ = 1;
19 | public const UL = 2;
20 | public const NSID = 3;
21 | public const DAU = 5;
22 | public const DHU = 6;
23 | public const N3U = 7;
24 | public const CLIENT_SUBNET = 8;
25 | public const EXPIRE = 9;
26 | public const COOKIE = 10;
27 | public const TCP_KEEPALIVE = 11;
28 | public const PADDING = 12;
29 | public const CHAIN = 13;
30 | public const KEY_CHAIN = 14;
31 | public const DNS_ERROR = 15;
32 | public const CLIENT_TAG = 16;
33 | public const SERVER_TAG = 17;
34 |
35 | /**
36 | * @var array
37 | */
38 | public static $names = [
39 | self::LLQ => 'LLQ',
40 | self::UL => 'UL',
41 | self::NSID => 'NSID',
42 | self::DAU => 'DAU',
43 | self::DHU => 'DHU',
44 | self::N3U => 'N3U',
45 | self::CLIENT_SUBNET => 'CLIENT_SUBNET',
46 | self::EXPIRE => 'EXPIRE',
47 | self::COOKIE => 'COOKIE',
48 | self::TCP_KEEPALIVE => 'TCP_KEEPALIVE',
49 | self::PADDING => 'PADDING',
50 | self::CHAIN => 'CHAIN',
51 | self::KEY_CHAIN => 'KEY_CHAIN',
52 | self::DNS_ERROR => 'DNS_ERROR',
53 | self::CLIENT_TAG => 'CLIENT_TAG',
54 | self::SERVER_TAG => 'SERVER_TAG',
55 | ];
56 |
57 | /**
58 | * @param int|string $option either the option name (string) or the option code (integer)
59 | */
60 | public static function isValid($option): bool
61 | {
62 | if (is_int($option)) {
63 | return array_key_exists($option, self::$names);
64 | }
65 |
66 | return in_array($option, self::$names);
67 | }
68 |
69 | /**
70 | * Get the name of an Option code. E.g. Codes::getName(8) return 'CLIENT_SUBNET'.
71 | *
72 | * @param int $code The index of the code
73 | *
74 | * @throws UnsupportedOptionException
75 | */
76 | public static function getName(int $code): string
77 | {
78 | if (!self::isValid($code)) {
79 | throw new UnsupportedOptionException(sprintf('The integer "%d" does not correspond to a supported code.', $code));
80 | }
81 |
82 | return self::$names[$code];
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/lib/Rdata/PolymorphicRdata.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | /**
17 | * Used to create RData of types that have not yet been implemented in the library.
18 | */
19 | class PolymorphicRdata implements RdataInterface
20 | {
21 | /**
22 | * The RData type.
23 | *
24 | * @var string
25 | */
26 | private $type;
27 |
28 | /**
29 | * @var string|null
30 | */
31 | private $data;
32 |
33 | /**
34 | * @var int
35 | */
36 | private $typeCode = 0;
37 |
38 | /**
39 | * PolymorphicRdata constructor.
40 | */
41 | public function __construct(?string $type = null, ?string $data = null)
42 | {
43 | if (null !== $type) {
44 | $this->setType($type);
45 | }
46 |
47 | if (null !== $data) {
48 | $this->setData($data);
49 | }
50 | }
51 |
52 | public function setType(string $type): void
53 | {
54 | try {
55 | $this->typeCode = Types::getTypeCode($type);
56 | } catch (UnsupportedTypeException $e) {
57 | $this->typeCode = 0;
58 | }
59 | $this->type = $type;
60 | }
61 |
62 | public function getType(): string
63 | {
64 | return $this->type;
65 | }
66 |
67 | public function setTypeCode(int $typeCode): void
68 | {
69 | $this->typeCode = $typeCode;
70 | }
71 |
72 | public function getTypeCode(): int
73 | {
74 | return $this->typeCode;
75 | }
76 |
77 | public function setData(string $data): void
78 | {
79 | $this->data = $data;
80 | }
81 |
82 | public function getData(): ?string
83 | {
84 | return $this->data;
85 | }
86 |
87 | public function toText(): string
88 | {
89 | return $this->getData() ?? '';
90 | }
91 |
92 | public function toWire(): string
93 | {
94 | return $this->data ?? '';
95 | }
96 |
97 | public function fromText(string $text): void
98 | {
99 | $this->setData($text);
100 | }
101 |
102 | public function fromWire(string $rdata, int &$offset = 0, ?int $rdLength = null): void
103 | {
104 | $this->setData(substr($rdata, $offset, $rdLength ?? strlen($rdata)));
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/lib/Rdata/AFSDB.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | use Badcow\DNS\Message;
17 |
18 | class AFSDB implements RdataInterface
19 | {
20 | use RdataTrait;
21 |
22 | public const TYPE = 'AFSDB';
23 | public const TYPE_CODE = 18;
24 |
25 | /**
26 | * @var int|null
27 | */
28 | private $subType;
29 |
30 | /**
31 | * @var string|null
32 | */
33 | private $hostname;
34 |
35 | public function getSubType(): ?int
36 | {
37 | return $this->subType;
38 | }
39 |
40 | public function setSubType(int $subType): void
41 | {
42 | $this->subType = $subType;
43 | }
44 |
45 | public function getHostname(): ?string
46 | {
47 | return $this->hostname;
48 | }
49 |
50 | public function setHostname(string $hostname): void
51 | {
52 | $this->hostname = $hostname;
53 | }
54 |
55 | /**
56 | * @throws \InvalidArgumentException
57 | */
58 | public function toText(): string
59 | {
60 | if (!isset($this->subType)) {
61 | throw new \InvalidArgumentException('No sub-type has been set on AFSDB object.');
62 | }
63 |
64 | if (!isset($this->hostname)) {
65 | throw new \InvalidArgumentException('No hostname has been set on AFSDB object.');
66 | }
67 |
68 | return sprintf('%d %s', $this->subType, $this->hostname);
69 | }
70 |
71 | public function toWire(): string
72 | {
73 | if (!isset($this->subType) || !isset($this->hostname)) {
74 | throw new \InvalidArgumentException('Subtype and hostname must be both be set.');
75 | }
76 |
77 | return pack('n', $this->subType).Message::encodeName($this->hostname);
78 | }
79 |
80 | public function fromText(string $text): void
81 | {
82 | $rdata = explode(' ', $text);
83 |
84 | $this->setSubType((int) $rdata[0]);
85 | $this->setHostname($rdata[1]);
86 | }
87 |
88 | public function fromWire(string $rdata, int &$offset = 0, ?int $rdLength = null): void
89 | {
90 | if (false === $subType = unpack('n', $rdata, $offset)) {
91 | throw new DecodeException(static::TYPE, $rdata);
92 | }
93 | $this->setSubType($subType[1]);
94 | $offset += 2;
95 | $this->setHostname(Message::decodeName($rdata, $offset));
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/tests/Rdata/MxTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Rdata\MX;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class MxTest extends TestCase
20 | {
21 | public function testSetters(): void
22 | {
23 | $target = 'foo.example.com.';
24 | $preference = 10;
25 | $mx = new MX();
26 | $mx->setExchange($target);
27 | $mx->setPreference($preference);
28 |
29 | $this->assertEquals($target, $mx->getExchange());
30 | $this->assertEquals($preference, $mx->getPreference());
31 | }
32 |
33 | public function testOutput(): void
34 | {
35 | $target = 'foo.example.com.';
36 | $mx = new MX();
37 | $mx->SetExchange($target);
38 | $mx->setPreference(42);
39 |
40 | $this->assertEquals('42 foo.example.com.', $mx->toText());
41 | }
42 |
43 | public function testOutputThrowsExceptionWhenMissingPreference(): void
44 | {
45 | $mx = new MX();
46 | $mx->setExchange('mail.google.com.');
47 |
48 | $this->expectException(\InvalidArgumentException::class);
49 | $this->expectExceptionMessage('No preference has been set on MX object.');
50 | $mx->toText();
51 | }
52 |
53 | public function testOutputThrowsExceptionWhenMissingExchange(): void
54 | {
55 | $mx = new MX();
56 | $mx->setPreference(15);
57 |
58 | $this->expectException(\InvalidArgumentException::class);
59 | $this->expectExceptionMessage('No exchange has been set on MX object.');
60 | $mx->toText();
61 | }
62 |
63 | public function testFromText(): void
64 | {
65 | $text = '10 mail.example.com.';
66 | /** @var MX $mx */
67 | $mx = new MX();
68 | $mx->fromText($text);
69 |
70 | $this->assertEquals(10, $mx->getPreference());
71 | $this->assertEquals('mail.example.com.', $mx->getExchange());
72 | }
73 |
74 | public function testWire(): void
75 | {
76 | $mx = new MX();
77 | $mx->setExchange('mail.example.com.');
78 | $mx->setPreference(10);
79 |
80 | $expectation = pack('n', 10).chr(4).'mail'.chr(7).'example'.chr(3).'com'.chr(0);
81 |
82 | $this->assertEquals($expectation, $mx->toWire());
83 | $fromWire = new MX();
84 | $fromWire->fromWire($expectation);
85 | $this->assertEquals($mx, $fromWire);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/docs/Reverse-Records.md:
--------------------------------------------------------------------------------
1 | # Reverse Records
2 |
3 | The DNS Library can natively handle reverse IP records for both IPv4 and IPv6.
4 |
5 | ## IPv4 Example
6 | ```php
7 | use Badcow\DNS\Rdata\Factory;
8 | use Badcow\DNS\{Zone, ResourceRecord, AlignedBuilder, Classes, ZoneBuilder};
9 | use Badcow\DNS\Ip\PTR;
10 |
11 | $parent = PTR::reverseIpv4('158.133.7'); //Returns 7.133.158.in-addr.arpa.
12 |
13 | $zone = new Zone($parent, 10800);
14 |
15 | $resourceRecords = [
16 | new ResourceRecord('1', Factory::Ptr('gw-01.badcow.co.')),
17 | new ResourceRecord('2', Factory::Ptr('gw-02.badcow.co.')),
18 | new ResourceRecord('10', Factory::Ptr('badcow.co.')),
19 | new ResourceRecord('15', Factory::Ptr('mail.badcow.co.'), 3600),
20 | new ResourceRecord('51', Factory::Ptr('esw-01.badcow.co.')),
21 | new ResourceRecord('52', Factory::Ptr('esw-02.badcow.co.')),
22 | ];
23 |
24 | $zone->fromArray($resourceRecords);
25 |
26 | echo AlignedBuilder::build($zone);
27 | ```
28 | ### Output
29 | ```text
30 | $ORIGIN 7.133.158.in-addr.arpa.
31 | $TTL 10800
32 |
33 | ; PTR RECORDS
34 | 10 IN PTR badcow.co.
35 | 15 3600 IN PTR mail.badcow.co.
36 | 1 IN PTR gw-01.badcow.co.
37 | 2 IN PTR gw-02.badcow.co.
38 | 51 IN PTR esw-01.badcow.co.
39 | 52 IN PTR esw-02.badcow.co.
40 | ```
41 |
42 | ## IPv6 Example
43 | ```php
44 | use Badcow\DNS\Rdata\Factory;
45 | use Badcow\DNS\{Zone, ResourceRecord, AlignedBuilder, Classes, ZoneBuilder};
46 | use Badcow\DNS\Ip\PTR;
47 |
48 | //$parent = PTR::reverseIpv4('158.133.7'); //Returns 7.133.158.in-addr.arpa.
49 | $parent = PTR::reverseIpv6('2001:acad:5889:0:0:0:0');
50 |
51 | $zone = new Zone($parent, 10800);
52 |
53 | $resourceRecords = [
54 | new ResourceRecord(PTR::reverseIpv6('1', false), Factory::Ptr('gw-01.badcow.co.')),
55 | new ResourceRecord(PTR::reverseIpv6('2', false), Factory::Ptr('gw-02.badcow.co.')),
56 | new ResourceRecord(PTR::reverseIpv6('bad', false), Factory::Ptr('badcow.co.')),
57 | new ResourceRecord(PTR::reverseIpv6('ff', false), Factory::Ptr('mail.badcow.co.'), 3600, Classes::INTERNET),
58 | new ResourceRecord(PTR::reverseIpv6('aa1', false), Factory::Ptr('esw-01.badcow.co.')),
59 | new ResourceRecord(PTR::reverseIpv6('aa2', false), Factory::Ptr('esw-02.badcow.co.')),
60 | ];
61 |
62 | $zone->fromArray($resourceRecords);
63 |
64 | echo AlignedBuilder::build($zone);
65 | ```
66 | ### Output
67 | ```text
68 | $ORIGIN 0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.8.8.5.d.a.c.a.1.0.0.2.ip6.arpa.
69 | $TTL 10800
70 |
71 | ; PTR RECORDS
72 | 1.0.0.0 PTR gw-01.badcow.co.
73 | 1.a.a.0 PTR esw-01.badcow.co.
74 | 2.0.0.0 PTR gw-02.badcow.co.
75 | 2.a.a.0 PTR esw-02.badcow.co.
76 | d.a.b.0 PTR badcow.co.
77 | f.f.0.0 3600 IN PTR mail.badcow.co.
78 | ```
--------------------------------------------------------------------------------
/tests/Rdata/HinfoTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Rdata\Factory;
17 | use Badcow\DNS\Rdata\HINFO;
18 | use PHPUnit\Framework\TestCase;
19 |
20 | class HinfoTest extends TestCase
21 | {
22 | public function testToText(): void
23 | {
24 | $cpu = '2.7GHz';
25 | $os = 'Ubuntu 12.04';
26 | $expectation = '"2.7GHz" "Ubuntu 12.04"';
27 | $hinfo = new HINFO();
28 | $hinfo->setCpu($cpu);
29 | $hinfo->setOs($os);
30 |
31 | $this->assertEquals($expectation, $hinfo->toText());
32 | }
33 |
34 | public function testGetters(): void
35 | {
36 | $cpu = '2.7GHz';
37 | $os = 'Ubuntu 12.04';
38 | $hinfo = new HINFO();
39 | $hinfo->setCpu($cpu);
40 | $hinfo->setOs($os);
41 |
42 | $this->assertEquals($cpu, $hinfo->getCpu());
43 | $this->assertEquals($os, $hinfo->getOs());
44 | }
45 |
46 | public function testGetType(): void
47 | {
48 | $hinfo = new HINFO();
49 | $this->assertEquals('HINFO', $hinfo->getType());
50 | }
51 |
52 | public function testGetTypeCode(): void
53 | {
54 | $hinfo = new HINFO();
55 | $this->assertEquals(13, $hinfo->getTypeCode());
56 | }
57 |
58 | public function testFromWire(): void
59 | {
60 | $hinfo = new HINFO();
61 | $hinfo->fromWire('"2.7GHz" "Ubuntu 12.04"');
62 | $this->assertEquals('2.7GHz', $hinfo->getCpu());
63 | $this->assertEquals('Ubuntu 12.04', $hinfo->getOs());
64 | }
65 |
66 | public function testFromText(): void
67 | {
68 | $hinfo = new HINFO();
69 | $hinfo->fromText('2.7GHz "Ubuntu 12.04"');
70 | $this->assertEquals('2.7GHz', $hinfo->getCpu());
71 | $this->assertEquals('Ubuntu 12.04', $hinfo->getOs());
72 | }
73 |
74 | public function testToWire(): void
75 | {
76 | $cpu = '2.7GHz';
77 | $os = 'Ubuntu 12.04';
78 | $expectation = '"2.7GHz" "Ubuntu 12.04"';
79 | $hinfo = new HINFO();
80 | $hinfo->setCpu($cpu);
81 | $hinfo->setOs($os);
82 |
83 | $this->assertEquals($expectation, $hinfo->toWire());
84 | }
85 |
86 | public function testFactory(): void
87 | {
88 | $hinfo = Factory::HINFO('SGI-IRIS-INDY', 'IRIX');
89 | $this->assertEquals('SGI-IRIS-INDY', $hinfo->getCpu());
90 | $this->assertEquals('IRIX', $hinfo->getOs());
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/tests/Rdata/AfsdbTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Rdata\AFSDB;
17 | use Badcow\DNS\Rdata\Factory;
18 | use PHPUnit\Framework\TestCase;
19 |
20 | /**
21 | * {@link https://tools.ietf.org/html/rfc1183}.
22 | */
23 | class AfsdbTest extends TestCase
24 | {
25 | public function testOutput(): void
26 | {
27 | $hostname = 'foo.example.com.';
28 | $afsdb = new AFSDB();
29 | $afsdb->setHostname($hostname);
30 | $afsdb->setSubType(2);
31 |
32 | $this->assertEquals('2 foo.example.com.', $afsdb->toText());
33 | }
34 |
35 | public function testOutputThrowsExceptionWhenMissingSubType(): void
36 | {
37 | $afsdb = new AFSDB();
38 | $afsdb->setHostname('foo.example.com.');
39 |
40 | $this->expectException(\InvalidArgumentException::class);
41 | $this->expectExceptionMessage('No sub-type has been set on AFSDB object.');
42 | $afsdb->toText();
43 | }
44 |
45 | public function testOutputThrowsExceptionWhenMissingHostname(): void
46 | {
47 | $afsdb = new AFSDB();
48 | $afsdb->setSubType(15);
49 |
50 | $this->expectException(\InvalidArgumentException::class);
51 | $this->expectExceptionMessage('No hostname has been set on AFSDB object.');
52 | $afsdb->toText();
53 | }
54 |
55 | public function testFromText(): void
56 | {
57 | $text = '2 foo.example.com.';
58 | /** @var AFSDB $afsdb */
59 | $afsdb = new AFSDB();
60 | $afsdb->fromText($text);
61 |
62 | $this->assertEquals(2, $afsdb->getSubType());
63 | $this->assertEquals('foo.example.com.', $afsdb->getHostname());
64 | }
65 |
66 | public function testWire(): void
67 | {
68 | $afsdb = new AFSDB();
69 | $afsdb->setHostname('foo.example.com.');
70 | $afsdb->setSubType(2);
71 |
72 | $expectation = pack('n', 2).chr(3).'foo'.chr(7).'example'.chr(3).'com'.chr(0);
73 |
74 | $fromWire = new AFSDB();
75 | $fromWire->fromWire($expectation);
76 |
77 | $this->assertEquals($expectation, $afsdb->toWire());
78 | $this->assertEquals($afsdb, $fromWire);
79 | }
80 |
81 | public function testFactory(): void
82 | {
83 | $afsdb = Factory::AFSDB(2, 'foo.example.com.');
84 |
85 | $this->assertInstanceOf(AFSDB::class, $afsdb);
86 | $this->assertEquals('2 foo.example.com.', $afsdb->toText());
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/lib/Rdata/HINFO.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | use Badcow\DNS\Parser\StringIterator;
17 | use Badcow\DNS\Parser\Tokens;
18 |
19 | /**
20 | * @see https://tools.ietf.org/html/rfc1035#section-3.3.2
21 | */
22 | class HINFO implements RdataInterface
23 | {
24 | use RdataTrait;
25 |
26 | public const TYPE = 'HINFO';
27 | public const TYPE_CODE = 13;
28 |
29 | /**
30 | * @var string|null
31 | */
32 | private $cpu;
33 |
34 | /**
35 | * @var string|null
36 | */
37 | private $os;
38 |
39 | public function setCpu(?string $cpu): void
40 | {
41 | $this->cpu = $cpu;
42 | }
43 |
44 | /**
45 | * @return string
46 | */
47 | public function getCpu(): ?string
48 | {
49 | return $this->cpu;
50 | }
51 |
52 | public function setOs(?string $os): void
53 | {
54 | $this->os = $os;
55 | }
56 |
57 | /**
58 | * @return string
59 | */
60 | public function getOs(): ?string
61 | {
62 | return $this->os;
63 | }
64 |
65 | public function toText(): string
66 | {
67 | return sprintf('"%s" "%s"', $this->cpu ?? '', $this->os ?? '');
68 | }
69 |
70 | public function toWire(): string
71 | {
72 | return $this->toText();
73 | }
74 |
75 | public function fromWire(string $rdata, int &$offset = 0, ?int $rdLength = null): void
76 | {
77 | $this->fromText(substr($rdata, $offset, $rdLength ?? strlen($rdata)));
78 | $offset += $rdLength;
79 | }
80 |
81 | public function fromText(string $text): void
82 | {
83 | $string = new StringIterator($text);
84 | $this->setCpu(self::extractText($string));
85 | $this->setOs(self::extractText($string));
86 | }
87 |
88 | private static function extractText(StringIterator $string): string
89 | {
90 | $txt = new StringIterator();
91 |
92 | if ($string->is(Tokens::DOUBLE_QUOTES)) {
93 | TXT::handleTxt($string, $txt);
94 | $string->next();
95 | } else {
96 | while ($string->isNot(Tokens::SPACE) && $string->valid()) {
97 | $txt->append($string->current());
98 | $string->next();
99 | }
100 | }
101 |
102 | if ($string->is(Tokens::SPACE)) {
103 | $string->next();
104 | }
105 |
106 | return (string) $txt;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/lib/Rdata/UnknownType.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | use Badcow\DNS\Parser\ParseException;
17 | use Badcow\DNS\Parser\Tokens;
18 |
19 | /**
20 | * {@link https://tools.ietf.org/html/rfc3597}.
21 | */
22 | class UnknownType implements RdataInterface
23 | {
24 | /**
25 | * @var int
26 | */
27 | private $typeCode;
28 |
29 | /**
30 | * @var string|null
31 | */
32 | private $data;
33 |
34 | public function setTypeCode(int $typeCode): void
35 | {
36 | $this->typeCode = $typeCode;
37 | }
38 |
39 | /**
40 | * @return string
41 | */
42 | public function getData(): ?string
43 | {
44 | return $this->data;
45 | }
46 |
47 | /**
48 | * @param string $data
49 | */
50 | public function setData(?string $data): void
51 | {
52 | $this->data = $data;
53 | }
54 |
55 | public function getType(): string
56 | {
57 | return 'TYPE'.$this->typeCode;
58 | }
59 |
60 | public function getTypeCode(): int
61 | {
62 | return $this->typeCode;
63 | }
64 |
65 | public function toText(): string
66 | {
67 | if (null === $this->data) {
68 | return '\# 0';
69 | }
70 |
71 | return sprintf('\# %d %s', strlen($this->data), bin2hex($this->data));
72 | }
73 |
74 | public function toWire(): string
75 | {
76 | return $this->data ?? '';
77 | }
78 |
79 | /**
80 | * @throws ParseException
81 | */
82 | public function fromText(string $text): void
83 | {
84 | if (1 !== preg_match('/^\\\#\s+(\d+)(\s[a-f0-9\s]+)?$/i', $text, $matches)) {
85 | throw new ParseException('Could not parse rdata of unknown type. Malformed string.');
86 | }
87 |
88 | if ('0' === $matches[1]) {
89 | return;
90 | }
91 |
92 | $hexVal = str_replace(Tokens::SPACE, '', $matches[2]);
93 |
94 | if (false === $data = hex2bin($hexVal)) {
95 | throw new ParseException(sprintf('Could not parse hexadecimal data "%s".', $hexVal));
96 | }
97 | $this->setData($data);
98 | }
99 |
100 | public function fromWire(string $rdata, int &$offset = 0, ?int $rdLength = null): void
101 | {
102 | $rdLength = $rdLength ?? strlen($rdata);
103 | $this->setData(substr($rdata, $offset, $rdLength));
104 | $offset += $rdLength;
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/tests/Edns/Option/COOKIETest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Edns\Option;
15 |
16 | use Badcow\DNS\Edns\Option\COOKIE;
17 | use Badcow\DNS\Edns\Option\DecodeException;
18 | use PHPUnit\Framework\TestCase;
19 |
20 | class COOKIETest extends TestCase
21 | {
22 | /**
23 | * @var COOKIE
24 | */
25 | private $option;
26 |
27 | public function setUp(): void
28 | {
29 | $this->option = new COOKIE();
30 | }
31 |
32 | public function testGetterSetters(): void
33 | {
34 | $this->assertEquals('COOKIE', $this->option->getName());
35 | }
36 |
37 | public function testSetClientCookie(): void
38 | {
39 | $this->expectException(\InvalidArgumentException::class);
40 | $this->option->setClientCookie(str_repeat('a', 9));
41 | }
42 |
43 | public function testSetServerCookie(): void
44 | {
45 | $this->expectException(\InvalidArgumentException::class);
46 | $this->option->setServerCookie(str_repeat('b', 7));
47 | }
48 |
49 | public function testToWire(): void
50 | {
51 | $noCookie = new COOKIE();
52 | $this->assertEquals('', $noCookie->toWire());
53 |
54 | $justClientCookie = new COOKIE();
55 | $justClientCookie->setClientCookie('aaaaaaaa');
56 | $this->assertEquals('aaaaaaaa', $justClientCookie->toWire());
57 |
58 | $bothCookies = new COOKIE();
59 | $bothCookies->setClientCookie('aaaaaaaa');
60 | $bothCookies->setServerCookie('bbbbbbbbb');
61 | $this->assertEquals('aaaaaaaabbbbbbbbb', $bothCookies->toWire());
62 | }
63 |
64 | public function testFromWire1(): void
65 | {
66 | $this->expectException(DecodeException::class);
67 | $wire = '';
68 | $noCookie = new COOKIE();
69 | $noCookie->fromWire($wire);
70 | }
71 |
72 | public function testFromWire2(): void
73 | {
74 | $wire = 'aaaaaaaa';
75 | $justClientCookie = new COOKIE();
76 | $justClientCookie->fromWire($wire);
77 | $this->assertEquals('aaaaaaaa', $justClientCookie->getClientCookie());
78 | $this->assertNull($justClientCookie->getServerCookie());
79 | }
80 |
81 | public function testFromWire3(): void
82 | {
83 | $wire = 'aaaaaaaabbbbbbbbb';
84 | $bothCookies = new COOKIE();
85 | $bothCookies->fromWire($wire);
86 | $this->assertEquals('aaaaaaaa', $bothCookies->getClientCookie());
87 | $this->assertEquals('bbbbbbbbb', $bothCookies->getServerCookie());
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/lib/Rcode.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS;
15 |
16 | class Rcode
17 | {
18 | /**
19 | * No Error [RFC1035].
20 | */
21 | public const NOERROR = 0;
22 |
23 | /**
24 | * Format Error [RFC1035].
25 | */
26 | public const FORMERR = 1;
27 |
28 | /**
29 | * Server Failure [RFC1035].
30 | */
31 | public const SERVFAIL = 2;
32 |
33 | /**
34 | * Non-Existent Domain [RFC1035].
35 | */
36 | public const NXDOMAIN = 3;
37 |
38 | /**
39 | * Not Implemented [RFC1035].
40 | */
41 | public const NOTIMP = 4;
42 |
43 | /**
44 | * Query Refused [RFC1035].
45 | */
46 | public const REFUSED = 5;
47 |
48 | /**
49 | * Name Exists when it should not [RFC2136][RFC6672].
50 | */
51 | public const YXDOMAIN = 6;
52 |
53 | /**
54 | * RR Set Exists when it should not [RFC2136].
55 | */
56 | public const YXRRSET = 7;
57 |
58 | /**
59 | * RR Set that should exist does not [RFC2136].
60 | */
61 | public const NXRRSET = 8;
62 |
63 | /**
64 | * Server Not Authoritative for zone [RFC2136].
65 | * Not Authorized [RFC2845].
66 | */
67 | public const NOTAUTH = 9;
68 |
69 | /**
70 | * Name not contained in zone [RFC2136].
71 | */
72 | public const NOTZONE = 10;
73 |
74 | /**
75 | * DSO-TYPE Not Implemented [RFC8490].
76 | */
77 | public const DSOTYPENI = 11;
78 |
79 | /**
80 | * Bad OPT Version [RFC6891].
81 | */
82 | public const BADVERS = 16;
83 |
84 | /**
85 | * TSIG Signature Failure [RFC2845].
86 | */
87 | public const BADSIG = 16;
88 |
89 | /**
90 | * Key not recognized [RFC2845].
91 | */
92 | public const BADKEY = 17;
93 |
94 | /**
95 | * Signature out of time window [RFC2845].
96 | */
97 | public const BADTIME = 18;
98 |
99 | /**
100 | * Bad TKEY Mode [RFC2930].
101 | */
102 | public const BADMODE = 19;
103 |
104 | /**
105 | * Duplicate key name [RFC2930].
106 | */
107 | public const BADNAME = 20;
108 |
109 | /**
110 | * Algorithm not supported [RFC2930].
111 | */
112 | public const BADALG = 21;
113 |
114 | /**
115 | * Bad Truncation [RFC4635].
116 | */
117 | public const BADTRUNC = 22;
118 |
119 | /**
120 | * Bad/missing Server Cookie [RFC7873].
121 | */
122 | public const BADCOOKIE = 23;
123 | }
124 |
--------------------------------------------------------------------------------
/lib/Parser/TimeFormat.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Parser;
15 |
16 | class TimeFormat
17 | {
18 | public const TIME_FORMAT_REGEX = '/^(?:(?\d+)w)?(?:(?\d+)d)?(?:(?\d+)h)?(?:(?\d+)m)?(?:(?\d+)s)?$/i';
19 |
20 | /**
21 | * Maximum time is the the lesser of 0xffffffff or the PHP maximum integer.
22 | *
23 | * @var int|null
24 | */
25 | public static $maxTime;
26 |
27 | public const TIME_MULTIPLIERS = [
28 | 'w' => 604800,
29 | 'd' => 86400,
30 | 'h' => 3600,
31 | 'm' => 60,
32 | 's' => 1,
33 | ];
34 |
35 | /**
36 | * Check if given token looks like time format.
37 | *
38 | * @param string $value the time value to be evaluated
39 | *
40 | * @return bool true if $value is a valid time format
41 | */
42 | public static function isTimeFormat(string $value): bool
43 | {
44 | return is_numeric($value) || 1 === \preg_match(self::TIME_FORMAT_REGEX, $value);
45 | }
46 |
47 | /**
48 | * Convert human readable time format to seconds.
49 | *
50 | * @param string|int $value time value to be converted to seconds
51 | *
52 | * @return int the time value in seconds
53 | */
54 | public static function toSeconds($value): int
55 | {
56 | if (!isset(static::$maxTime)) {
57 | static::$maxTime = min(0xFFFFFFFF, PHP_INT_MAX);
58 | }
59 |
60 | if (is_numeric($value)) {
61 | return (int) $value;
62 | }
63 |
64 | if (1 === preg_match_all(self::TIME_FORMAT_REGEX, $value, $matches)) {
65 | $sec = (int) $matches['w'][0] * 604800 +
66 | (int) $matches['d'][0] * 86400 +
67 | (int) $matches['h'][0] * 3600 +
68 | (int) $matches['m'][0] * 60 +
69 | (int) $matches['s'][0];
70 |
71 | return $sec < static::$maxTime ? $sec : 0;
72 | }
73 |
74 | return 0;
75 | }
76 |
77 | /**
78 | * Convert number of seconds to human readable format.
79 | *
80 | * @param int $seconds the time in seconds to be converted to human-readable string
81 | *
82 | * @return string a human-readable representation of the $seconds parameter
83 | */
84 | public static function toHumanReadable(int $seconds): string
85 | {
86 | $humanReadable = '';
87 | foreach (self::TIME_MULTIPLIERS as $suffix => $multiplier) {
88 | $humanReadable .= ($t = floor($seconds / $multiplier)) > 0 ? $t.$suffix : '';
89 | $seconds -= $t * $multiplier;
90 | }
91 |
92 | return $humanReadable;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/tests/Rdata/PtrTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Rdata\PTR;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class PtrTest extends TestCase
20 | {
21 | public function provider_expandIPv6(): array
22 | {
23 | return [
24 | ['0000:0000:0000:0000:0000:0000:0000:0001', '::1'],
25 | ['2001:0db8:0000:0000:0000:ff00:0042:8329', '2001:db8::ff00:42:8329'],
26 | ['2001:0000:0000:acad:0000:0000:0000:0001', '2001:0:0:acad::1'],
27 | ['0000:0000:0000:0000:0000:0000:0000:0000', '::'],
28 | ['2001:0000:0000:ab80:2390:0000:0000:000a', '2001::ab80:2390:0:0:a'],
29 | ['0000:0000:aaaa:0000:0000:aaaa:0000:0000', '::aaaa:0:0:aaaa:0:0'],
30 | ['0001:0000:0000:0000:0000:0000:0000:0000', '1::'],
31 | ];
32 | }
33 |
34 | public function provider_contractIPv6(): array
35 | {
36 | return array_merge($this->provider_expandIPv6(), [
37 | ['2001:db8:0:0:f:0:0:0', '2001:db8:0:0:f::'],
38 | ['2001:db8::ff00:42:8329', '2001:db8::ff00:42:8329'],
39 | ['2001:db8:a:bac:8099:d:f:9', '2001:db8:a:bac:8099:d:f:9'],
40 | ]);
41 | }
42 |
43 | /**
44 | * @dataProvider provider_expandIPv6
45 | */
46 | public function testExpandIpv6(string $expectation, string $ip): void
47 | {
48 | $this->assertEquals($expectation, PTR::expandIpv6($ip));
49 | }
50 |
51 | /**
52 | * @dataProvider provider_contractIPv6
53 | */
54 | public function testContractIpv6(string $ip, string $expectation): void
55 | {
56 | $this->assertEquals($expectation, PTR::contractIpv6($ip));
57 | }
58 |
59 | public function testContractIpv6ThrowsException(): void
60 | {
61 | $this->expectException(\InvalidArgumentException::class);
62 | $this->expectExceptionMessage('"127.0.0.1" is not a valid IPv6 address.');
63 |
64 | PTR::contractIpv6('127.0.0.1');
65 | }
66 |
67 | public function testReverseIpv4(): void
68 | {
69 | $case_1 = '192.168.1.213';
70 | $exp_1 = '213.1.168.192.in-addr.arpa.';
71 |
72 | $this->assertEquals($exp_1, PTR::reverseIpv4($case_1));
73 | }
74 |
75 | public function testReverseIpv6(): void
76 | {
77 | $case_1 = '2001:db8::567:89ab';
78 | $case_2 = '8007:ea:19';
79 |
80 | $exp_1 = 'b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.';
81 | $exp_2 = '9.1.0.0.a.e.0.0.7.0.0.8.ip6.arpa.';
82 |
83 | $this->assertEquals($exp_1, PTR::reverseIpv6($case_1));
84 | $this->assertEquals($exp_2, PTR::reverseIpv6($case_2));
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/tests/Rdata/KxTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Rdata\Factory;
17 | use Badcow\DNS\Rdata\KX;
18 | use PHPUnit\Framework\TestCase;
19 |
20 | class KxTest extends TestCase
21 | {
22 | public function testSetters(): void
23 | {
24 | $target = 'foo.example.com.';
25 | $preference = 10;
26 | $kx = new KX();
27 | $kx->setExchanger($target);
28 | $kx->setPreference($preference);
29 |
30 | $this->assertEquals($target, $kx->getExchanger());
31 | $this->assertEquals($preference, $kx->getPreference());
32 | }
33 |
34 | public function testOutput(): void
35 | {
36 | $target = 'foo.example.com.';
37 | $kx = new KX();
38 | $kx->SetExchanger($target);
39 | $kx->setPreference(42);
40 |
41 | $this->assertEquals('42 foo.example.com.', $kx->toText());
42 | }
43 |
44 | public function testOutputThrowsExceptionWhenMissingPreference(): void
45 | {
46 | $kx = new KX();
47 | $kx->setExchanger('mail.google.com.');
48 |
49 | $this->expectException(\InvalidArgumentException::class);
50 | $this->expectExceptionMessage('No preference has been set on KX object.');
51 | $kx->toText();
52 | }
53 |
54 | public function testOutputThrowsExceptionWhenMissingExchanger(): void
55 | {
56 | $kx = new KX();
57 | $kx->setPreference(15);
58 |
59 | $this->expectException(\InvalidArgumentException::class);
60 | $this->expectExceptionMessage('No exchanger has been set on KX object.');
61 | $kx->toText();
62 | }
63 |
64 | public function testFactory(): void
65 | {
66 | $kx = Factory::KX(15, 'mx.example.com.');
67 | $this->assertInstanceOf(KX::class, $kx);
68 | $this->assertEquals('15 mx.example.com.', $kx->toText());
69 | }
70 |
71 | public function testFromText(): void
72 | {
73 | $text = '10 mail.example.com.';
74 | /** @var KX $kx */
75 | $kx = new KX();
76 | $kx->fromText($text);
77 |
78 | $this->assertEquals(10, $kx->getPreference());
79 | $this->assertEquals('mail.example.com.', $kx->getExchanger());
80 | }
81 |
82 | public function testWire(): void
83 | {
84 | $kx = new KX();
85 | $kx->setExchanger('mail.example.com.');
86 | $kx->setPreference(10);
87 |
88 | $expectation = pack('n', 10).chr(4).'mail'.chr(7).'example'.chr(3).'com'.chr(0);
89 |
90 | $this->assertEquals($expectation, $kx->toWire());
91 | $fromWire = new KX();
92 | $fromWire->fromWire($expectation);
93 | $this->assertEquals($kx, $fromWire);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/lib/Rdata/KX.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | use Badcow\DNS\Message;
17 |
18 | /**
19 | * {@link https://tools.ietf.org/html/rfc2230}.
20 | */
21 | class KX implements RdataInterface
22 | {
23 | use RdataTrait;
24 |
25 | public const TYPE = 'KX';
26 | public const TYPE_CODE = 36;
27 |
28 | /**
29 | * @var int
30 | */
31 | private $preference;
32 |
33 | /**
34 | * @var string
35 | */
36 | private $exchanger;
37 |
38 | public function setExchanger(string $exchanger): void
39 | {
40 | $this->exchanger = $exchanger;
41 | }
42 |
43 | public function getExchanger(): string
44 | {
45 | return $this->exchanger;
46 | }
47 |
48 | public function setPreference(int $preference): void
49 | {
50 | $this->preference = $preference;
51 | }
52 |
53 | public function getPreference(): int
54 | {
55 | return $this->preference;
56 | }
57 |
58 | /**
59 | * @throws \InvalidArgumentException throws exception if preference or exchanger have not been set
60 | */
61 | public function toText(): string
62 | {
63 | if (null === $this->preference) {
64 | throw new \InvalidArgumentException('No preference has been set on KX object.');
65 | }
66 | if (null === $this->exchanger) {
67 | throw new \InvalidArgumentException('No exchanger has been set on KX object.');
68 | }
69 |
70 | return $this->preference.' '.$this->exchanger;
71 | }
72 |
73 | public function toWire(): string
74 | {
75 | if (null === $this->preference) {
76 | throw new \InvalidArgumentException('No preference has been set on KX object.');
77 | }
78 | if (null === $this->exchanger) {
79 | throw new \InvalidArgumentException('No exchanger has been set on KX object.');
80 | }
81 |
82 | return pack('n', $this->preference).Message::encodeName($this->exchanger);
83 | }
84 |
85 | public function fromText(string $text): void
86 | {
87 | $rdata = explode(' ', $text);
88 | $this->setPreference((int) $rdata[0]);
89 | $this->setExchanger($rdata[1]);
90 | }
91 |
92 | public function fromWire(string $rdata, int &$offset = 0, ?int $rdLength = null): void
93 | {
94 | if (false === $preference = unpack('n', $rdata, $offset)) {
95 | throw new DecodeException(static::TYPE, $rdata);
96 | }
97 | $this->setPreference($preference[1]);
98 | $offset += 2;
99 | $this->setExchanger(Message::decodeName($rdata, $offset));
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/lib/Rdata/MX.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | use Badcow\DNS\Message;
17 |
18 | /**
19 | * @see https://tools.ietf.org/html/rfc1035#section-3.3.9
20 | */
21 | class MX implements RdataInterface
22 | {
23 | use RdataTrait;
24 |
25 | public const TYPE = 'MX';
26 | public const TYPE_CODE = 15;
27 |
28 | /**
29 | * @var int|null
30 | */
31 | private $preference;
32 |
33 | /**
34 | * @var string|null
35 | */
36 | private $exchange;
37 |
38 | public function setExchange(string $exchange): void
39 | {
40 | $this->exchange = $exchange;
41 | }
42 |
43 | public function getExchange(): ?string
44 | {
45 | return $this->exchange;
46 | }
47 |
48 | public function setPreference(int $preference): void
49 | {
50 | $this->preference = $preference;
51 | }
52 |
53 | public function getPreference(): ?int
54 | {
55 | return $this->preference;
56 | }
57 |
58 | /**
59 | * @throws \InvalidArgumentException throws exception if preference or exchange have not been set
60 | */
61 | public function toText(): string
62 | {
63 | if (null === $this->preference) {
64 | throw new \InvalidArgumentException('No preference has been set on MX object.');
65 | }
66 |
67 | if (null === $this->exchange) {
68 | throw new \InvalidArgumentException('No exchange has been set on MX object.');
69 | }
70 |
71 | return $this->preference.' '.$this->exchange;
72 | }
73 |
74 | public function toWire(): string
75 | {
76 | if (null === $this->preference) {
77 | throw new \InvalidArgumentException('No preference has been set on MX object.');
78 | }
79 |
80 | if (null === $this->exchange) {
81 | throw new \InvalidArgumentException('No exchange has been set on MX object.');
82 | }
83 |
84 | return pack('n', $this->preference).Message::encodeName($this->exchange);
85 | }
86 |
87 | public function fromText(string $text): void
88 | {
89 | $rdata = explode(' ', $text);
90 | $this->setPreference((int) $rdata[0]);
91 | $this->setExchange($rdata[1]);
92 | }
93 |
94 | public function fromWire(string $rdata, int &$offset = 0, ?int $rdLength = null): void
95 | {
96 | if (false === $preference = unpack('n', $rdata, $offset)) {
97 | throw new DecodeException(static::TYPE, $rdata);
98 | }
99 | $this->setPreference($preference[1]);
100 | $offset += 2;
101 | $this->setExchange(Message::decodeName($rdata, $offset));
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/tests/Rdata/SrvTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Rdata\Factory;
17 | use Badcow\DNS\Rdata\SRV;
18 | use PHPUnit\Framework\TestCase;
19 |
20 | class SrvTest extends TestCase
21 | {
22 | public function testOutput(): void
23 | {
24 | $srv = Factory::SRV(10, 20, 666, 'doom.example.com.');
25 |
26 | $expectation = '10 20 666 doom.example.com.';
27 |
28 | $this->assertEquals($expectation, $srv->toText());
29 | $this->assertEquals(10, $srv->getPriority());
30 | $this->assertEquals(20, $srv->getWeight());
31 | $this->assertEquals(666, $srv->getPort());
32 | }
33 |
34 | /**
35 | * @throws \InvalidArgumentException
36 | */
37 | public function testPortException(): void
38 | {
39 | $this->expectException(\InvalidArgumentException::class);
40 | $this->expectExceptionMessage('Port must be an unsigned integer on the range [0-65535]');
41 |
42 | $srv = new SRV();
43 | $srv->setPort(65536);
44 | }
45 |
46 | /**
47 | * @throws \InvalidArgumentException
48 | */
49 | public function testPriorityException(): void
50 | {
51 | $this->expectException(\InvalidArgumentException::class);
52 | $this->expectExceptionMessage('Priority must be an unsigned integer on the range [0-65535]');
53 |
54 | $srv = new SRV();
55 | $srv->setPriority(65536);
56 | }
57 |
58 | /**
59 | * @throws \InvalidArgumentException
60 | */
61 | public function testWeightException(): void
62 | {
63 | $this->expectException(\InvalidArgumentException::class);
64 | $this->expectExceptionMessage('Weight must be an unsigned integer on the range [0-65535]');
65 |
66 | $srv = new SRV();
67 | $srv->setWeight(65536);
68 | }
69 |
70 | public function testFromText(): void
71 | {
72 | $text = '0 1 80 www.example.com.';
73 | $srv = new SRV();
74 | $srv->setPriority(0);
75 | $srv->setWeight(1);
76 | $srv->setPort(80);
77 | $srv->setTarget('www.example.com.');
78 |
79 | $fromText = new SRV();
80 | $fromText->fromText($text);
81 | $this->assertEquals($srv, $fromText);
82 | }
83 |
84 | public function testWire(): void
85 | {
86 | $expectation = pack('nnn', 0, 1, 80).chr(3).'www'.chr(7).'example'.chr(3).'com'.chr(0);
87 | $srv = new SRV();
88 | $srv->setPriority(0);
89 | $srv->setWeight(1);
90 | $srv->setPort(80);
91 | $srv->setTarget('www.example.com.');
92 |
93 | $this->assertEquals($expectation, $srv->toWire());
94 | $fromWire = new SRV();
95 | $fromWire->fromWire($expectation);
96 | $this->assertEquals($srv, $fromWire);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/lib/Rdata/CSYNC.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | use Badcow\DNS\Parser\Tokens;
17 |
18 | /**
19 | * {@link https://tools.ietf.org/html/rfc7477}.
20 | */
21 | class CSYNC implements RdataInterface
22 | {
23 | use RdataTrait;
24 |
25 | public const TYPE = 'CSYNC';
26 | public const TYPE_CODE = 62;
27 |
28 | /**
29 | * @var int
30 | */
31 | private $soaSerial;
32 |
33 | /**
34 | * @var int
35 | */
36 | private $flags;
37 |
38 | /**
39 | * @var array
40 | */
41 | private $types = [];
42 |
43 | public function addType(string $type): void
44 | {
45 | $this->types[] = $type;
46 | }
47 |
48 | /**
49 | * Clears the types from the RDATA.
50 | */
51 | public function clearTypes(): void
52 | {
53 | $this->types = [];
54 | }
55 |
56 | public function getTypes(): array
57 | {
58 | return $this->types;
59 | }
60 |
61 | public function getSoaSerial(): int
62 | {
63 | return $this->soaSerial;
64 | }
65 |
66 | public function setSoaSerial(int $soaSerial): void
67 | {
68 | $this->soaSerial = $soaSerial;
69 | }
70 |
71 | public function getFlags(): int
72 | {
73 | return $this->flags;
74 | }
75 |
76 | public function setFlags(int $flags): void
77 | {
78 | $this->flags = $flags;
79 | }
80 |
81 | public function toText(): string
82 | {
83 | return sprintf('%d %d %s', $this->soaSerial, $this->flags, implode(Tokens::SPACE, $this->types));
84 | }
85 |
86 | /**
87 | * @throws UnsupportedTypeException
88 | */
89 | public function toWire(): string
90 | {
91 | return pack('Nn', $this->soaSerial, $this->flags).NSEC::renderBitmap($this->types);
92 | }
93 |
94 | public function fromText(string $text): void
95 | {
96 | $rdata = explode(Tokens::SPACE, $text);
97 | $this->setSoaSerial((int) array_shift($rdata));
98 | $this->setFlags((int) array_shift($rdata));
99 | array_map([$this, 'addType'], $rdata);
100 | }
101 |
102 | /**
103 | * @throws UnsupportedTypeException|DecodeException
104 | */
105 | public function fromWire(string $rdata, int &$offset = 0, ?int $rdLength = null): void
106 | {
107 | if (false === $integers = unpack('Nserial/nflags', $rdata, $offset)) {
108 | throw new DecodeException(static::TYPE, $rdata);
109 | }
110 | $offset += 6;
111 | $types = NSEC::parseBitmap($rdata, $offset);
112 |
113 | $this->setSoaSerial((int) $integers['serial']);
114 | $this->setFlags((int) $integers['flags']);
115 | array_map([$this, 'addType'], $types);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/tests/Rdata/NsecTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Rdata\A;
17 | use Badcow\DNS\Rdata\Factory;
18 | use Badcow\DNS\Rdata\MX;
19 | use Badcow\DNS\Rdata\NS;
20 | use Badcow\DNS\Rdata\NSEC;
21 | use Badcow\DNS\Rdata\PTR;
22 | use Badcow\DNS\Rdata\RRSIG;
23 | use PHPUnit\Framework\TestCase;
24 |
25 | class NsecTest extends TestCase
26 | {
27 | public function testOutput(): void
28 | {
29 | $expectation = 'host.example.com. A MX RRSIG NSEC';
30 |
31 | $nsec = new NSEC();
32 | $nsec->setNextDomainName('host.example.com.');
33 | $nsec->addType(A::TYPE);
34 | $nsec->addType(MX::TYPE);
35 | $nsec->addType(RRSIG::TYPE);
36 | $nsec->addType(NSEC::TYPE);
37 |
38 | $this->assertEquals($expectation, $nsec->toText());
39 | }
40 |
41 | public function testFactory(): void
42 | {
43 | $nextDomain = 'host.example.com.';
44 | $bitMaps = [A::TYPE, MX::TYPE, RRSIG::TYPE, NSEC::TYPE];
45 | $nsec = Factory::NSEC($nextDomain, $bitMaps);
46 |
47 | $this->assertEquals($nextDomain, $nsec->getNextDomainName());
48 | $this->assertEquals($bitMaps, $nsec->getTypes());
49 | }
50 |
51 | public function testClearTypeMap(): void
52 | {
53 | $nsec = new NSEC();
54 | $nsec->addType(NS::TYPE);
55 | $nsec->addType(PTR::TYPE);
56 |
57 | $this->assertEquals([NS::TYPE, PTR::TYPE], $nsec->getTypes());
58 | $nsec->clearTypes();
59 | $this->assertEquals([], $nsec->getTypes());
60 | }
61 |
62 | public function testFromText(): void
63 | {
64 | $text = 'host.example.com. A MX RRSIG NSEC TYPE1234';
65 | /** @var NSEC $nsec */
66 | $nsec = new NSEC();
67 | $nsec->fromText($text);
68 |
69 | $this->assertEquals('host.example.com.', $nsec->getNextDomainName());
70 | $this->assertEquals(['A', 'MX', 'RRSIG', 'NSEC', 'TYPE1234'], $nsec->getTypes());
71 | $this->assertEquals($text, $nsec->toText());
72 | }
73 |
74 | public function testWire(): void
75 | {
76 | $hexMatrix = [
77 | 0x04, ord('h'), ord('o'), ord('s'), ord('t'),
78 | 0x07, ord('e'), ord('x'), ord('a'), ord('m'), ord('p'), ord('l'), ord('e'),
79 | 0x03, ord('c'), ord('o'), ord('m'), 0x00,
80 | 0x00, 0x06, 0x40, 0x01, 0x00, 0x00, 0x00, 0x03,
81 | 0x01, 0x01, 0x40,
82 | 0x80, 0x01, 0x40,
83 | ];
84 |
85 | $expectation = pack('C*', ...$hexMatrix);
86 |
87 | $text = 'host.example.com. A MX RRSIG NSEC CAA DLV';
88 | /** @var NSEC $nsec */
89 | $nsec = new NSEC();
90 | $nsec->fromText($text);
91 |
92 | $this->assertEquals($expectation, $nsec->toWire());
93 | $fromWire = new NSEC();
94 | $fromWire->fromWire($expectation);
95 | $this->assertEquals($nsec, $fromWire);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/tests/Rdata/DsTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Algorithms;
17 | use Badcow\DNS\Rdata\DNSKEY;
18 | use Badcow\DNS\Rdata\DS;
19 | use Badcow\DNS\Rdata\Factory;
20 | use PHPUnit\Framework\TestCase;
21 |
22 | class DsTest extends TestCase
23 | {
24 | private static $digest = '2BB183AF5F22588179A53B0A98631FAD1A292118';
25 |
26 | public function testOutput(): void
27 | {
28 | $expectation = '60485 5 1 '.self::$digest;
29 |
30 | $ds = new DS();
31 | $ds->setKeyTag(60485);
32 | $ds->setAlgorithm(Algorithms::RSASHA1);
33 | $ds->setDigestType(DS::DIGEST_SHA1);
34 | $ds->setDigest(hex2bin(self::$digest));
35 |
36 | $this->assertEquals($expectation, $ds->toText());
37 | }
38 |
39 | public function testFactory(): void
40 | {
41 | $keyTag = 60485;
42 | $ds = Factory::DS($keyTag, Algorithms::RSASHA1, hex2bin(self::$digest), DS::DIGEST_SHA1);
43 |
44 | $this->assertEquals($keyTag, $ds->getKeyTag());
45 | $this->assertEquals(Algorithms::RSASHA1, $ds->getAlgorithm());
46 | $this->assertEquals(hex2bin(self::$digest), $ds->getDigest());
47 | $this->assertEquals(DS::DIGEST_SHA1, $ds->getDigestType());
48 | }
49 |
50 | public function testFromText(): void
51 | {
52 | $expectation = new DS();
53 | $expectation->setKeyTag(60485);
54 | $expectation->setAlgorithm(Algorithms::RSASHA1);
55 | $expectation->setDigestType(DS::DIGEST_SHA1);
56 | $expectation->setDigest(hex2bin(self::$digest));
57 |
58 | $fromText = new DS();
59 | $fromText->fromText('60485 5 1 '.self::$digest);
60 | $this->assertEquals($expectation, $fromText);
61 | }
62 |
63 | public function testWire(): void
64 | {
65 | $ds = new DS();
66 | $ds->setKeyTag(60485);
67 | $ds->setAlgorithm(Algorithms::RSASHA1);
68 | $ds->setDigestType(DS::DIGEST_SHA1);
69 | $ds->setDigest(hex2bin(self::$digest));
70 | $wireFormat = $ds->toWire();
71 |
72 | $fromWire = new DS();
73 | $fromWire->fromWire($wireFormat);
74 |
75 | $this->assertEquals($ds, $fromWire);
76 | }
77 |
78 | public function testCalculateDigest(): void
79 | {
80 | $algorithm = Algorithms::RSASHA1;
81 | $dnskey = new DNSKEY();
82 | $dnskey->setPublicKey(base64_decode('AQOeiiR0GOMYkDshWoSKz9XzfwJr1AYtsmx3TGkJaNXVbfi/2pHm822aJ5iI9BMzNXxeYCmZDRD99WYwYqUSdjMmmAphXdvxegXd/M5+X7OrzKBaMbCVdFLUUh6DhweJBjEVv5f2wwjM9XzcnOf+EPbtG9DMBmADjFDc2w/rljwvFw=='));
83 | $dnskey->setAlgorithm($algorithm);
84 | $dnskey->setFlags(256);
85 |
86 | $ds = new DS();
87 | $ds->setAlgorithm($algorithm);
88 | $ds->setKeyTag(60485);
89 | $ds->calculateDigest('DSKEY.example.com.', $dnskey);
90 |
91 | $this->assertEquals('60485 5 1 2BB183AF5F22588179A53B0A98631FAD1A292118', $ds->toText());
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/tests/Rdata/KeyTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Algorithms;
17 | use Badcow\DNS\Rdata\Factory;
18 | use Badcow\DNS\Rdata\KEY;
19 | use PHPUnit\Framework\TestCase;
20 |
21 | class KeyTest extends TestCase
22 | {
23 | /**
24 | * @var string
25 | */
26 | private static $publicKey = 'AQPSKmynfzW4kyBv015MUG2DeIQ3Cbl+BBZH4b/0PY1kxkmvHjcZc8nokfzj31GajIQKY+5CptLr3buXA10hWqTkF7H6RfoRqXQeogmMHfpftf6zMv1LyBUgia7za6ZEzOJBOztyvhjL742iU/TpPSEDhm2SNKLijfUppn1UaNvv4w==';
27 |
28 | public function testOutput(): void
29 | {
30 | $expectation = '256 3 5 '.self::$publicKey;
31 |
32 | $key = new KEY();
33 | $key->setFlags(256);
34 | $key->setProtocol(3);
35 | $key->setAlgorithm(Algorithms::RSASHA1);
36 | $key->setPublicKey(base64_decode(self::$publicKey));
37 |
38 | $this->assertEquals($expectation, $key->toText());
39 | }
40 |
41 | public function testFactory(): void
42 | {
43 | $key = Factory::KEY(256, 3, Algorithms::RSASHA1, base64_decode(self::$publicKey));
44 | $output = '256 3 5 '.self::$publicKey;
45 |
46 | $this->assertEquals(256, $key->getFlags());
47 | $this->assertEquals(5, $key->getAlgorithm());
48 | $this->assertEquals(base64_decode(self::$publicKey), $key->getPublicKey());
49 | $this->assertEquals(3, $key->getProtocol());
50 | $this->assertEquals($output, $key->toText());
51 | }
52 |
53 | public function testFromText(): void
54 | {
55 | $rdata = '256 3 5 AQPSKmynfzW4kyBv015MUG2DeIQ3 Cbl+BBZH4b/0PY1kxkmvHjcZc8no kfzj31GajIQKY+5CptLr3buXA10h WqTkF7H6RfoRqXQeogmMHfpftf6z Mv1LyBUgia7za6ZEzOJBOztyvhjL 742iU/TpPSEDhm2SNKLijfUppn1U aNvv4w==';
56 | $key = new KEY();
57 | $key->setFlags(256);
58 | $key->setProtocol(3);
59 | $key->setAlgorithm(Algorithms::RSASHA1);
60 | $key->setPublicKey(base64_decode(self::$publicKey));
61 |
62 | $fromText = new KEY();
63 | $fromText->fromText($rdata);
64 | $this->assertEquals($key, $fromText);
65 | }
66 |
67 | public function testWire(): void
68 | {
69 | $wireFormat = pack('nCC', 256, 3, 5).base64_decode(self::$publicKey);
70 |
71 | $key = new KEY();
72 | $key->setFlags(256);
73 | $key->setProtocol(3);
74 | $key->setAlgorithm(Algorithms::RSASHA1);
75 | $key->setPublicKey(base64_decode("AQPSKmynfzW4kyBv015MUG2DeIQ3Cbl+BBZH4b/\r\n0PY1kxkmvHjcZc8nokfzj31GajIQKY+5CptLr3buXA10hWqTkF7H6RfoRqXQe ogmMHfpftf6zMv1LyBUgia7za6ZEzOJBOztyvhjL742iU\n/TpPSEDhm2SNKLijfUppn1UaNvv4w=="));
76 |
77 | $this->assertEquals($wireFormat, $key->toWire());
78 |
79 | $rdLength = strlen($wireFormat);
80 | $wireFormat = 'abcde'.$wireFormat.'fghijk';
81 | $offset = 5;
82 |
83 | $fromWire = new KEY();
84 | $fromWire->fromWire($wireFormat, $offset, $rdLength);
85 | $this->assertEquals($key, $fromWire);
86 | $this->assertEquals(5 + $rdLength, $offset);
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/tests/Rdata/Nsec3paramTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Algorithms;
17 | use Badcow\DNS\Rdata\Factory;
18 | use Badcow\DNS\Rdata\NSEC3PARAM;
19 | use PHPUnit\Framework\TestCase;
20 |
21 | class Nsec3paramTest extends TestCase
22 | {
23 | public function testGetType(): void
24 | {
25 | $nsec3param = new NSEC3PARAM();
26 | $this->assertEquals('NSEC3PARAM', $nsec3param->getType());
27 | }
28 |
29 | public function testGetTypeCode(): void
30 | {
31 | $nsec3param = new NSEC3PARAM();
32 | $this->assertEquals(51, $nsec3param->getTypeCode());
33 | }
34 |
35 | public function testToText(): void
36 | {
37 | $nsec3param = new NSEC3PARAM();
38 | $nsec3param->setHashAlgorithm(Algorithms::RSAMD5);
39 | $nsec3param->setSalt('d9143ec07c5977ae');
40 | $nsec3param->setIterations(55);
41 | $nsec3param->setFlags(0);
42 |
43 | $expectation = '1 0 55 d9143ec07c5977ae';
44 | $this->assertEquals($expectation, $nsec3param->toText());
45 | }
46 |
47 | public function testToWire(): void
48 | {
49 | $nsec3param = new NSEC3PARAM();
50 | $nsec3param->setHashAlgorithm(Algorithms::RSAMD5);
51 | $nsec3param->setSalt('d9143ec07c5977ae');
52 | $nsec3param->setIterations(55);
53 | $nsec3param->setFlags(0);
54 |
55 | $expectation = chr(1).chr(0).pack('n', 55).chr(8).hex2bin('d9143ec07c5977ae');
56 |
57 | $this->assertEquals($expectation, $nsec3param->toWire());
58 | }
59 |
60 | public function testFromText(): void
61 | {
62 | $expectation = new NSEC3PARAM();
63 | $expectation->setHashAlgorithm(Algorithms::RSAMD5);
64 | $expectation->setSalt('d9143ec07c5977ae');
65 | $expectation->setIterations(55);
66 | $expectation->setFlags(0);
67 |
68 | $fromText = new NSEC3PARAM();
69 | $fromText->fromText('1 0 55 d9143ec07c5977ae');
70 | $this->assertEquals($expectation, $fromText);
71 | }
72 |
73 | public function testFromWire(): void
74 | {
75 | $expectation = new NSEC3PARAM();
76 | $expectation->setHashAlgorithm(Algorithms::RSAMD5);
77 | $expectation->setSalt('d9143ec07c5977ae');
78 | $expectation->setIterations(55);
79 | $expectation->setFlags(0);
80 |
81 | $wireFormat = chr(1).chr(0).pack('n', 55).chr(8).hex2bin('d9143ec07c5977ae');
82 |
83 | $fromWire = new NSEC3PARAM();
84 | $fromWire->fromWire($wireFormat);
85 | $this->assertEquals($expectation, $fromWire);
86 | }
87 |
88 | public function testFactory(): void
89 | {
90 | $nsec3param = Factory::NSEC3PARAM(Algorithms::RSAMD5, 0, 55, 'd9143ec07c5977ae');
91 |
92 | $this->assertEquals(1, $nsec3param->getHashAlgorithm());
93 | $this->assertEquals(0, $nsec3param->getFlags());
94 | $this->assertEquals(55, $nsec3param->getIterations());
95 | $this->assertEquals('d9143ec07c5977ae', $nsec3param->getSalt());
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/tests/Parser/ParseUnknownTypesTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Parser;
15 |
16 | use Badcow\DNS\Classes;
17 | use Badcow\DNS\Parser\ParseException;
18 | use Badcow\DNS\Parser\Parser;
19 | use Badcow\DNS\Rdata\A;
20 | use Badcow\DNS\Rdata\PolymorphicRdata;
21 | use Badcow\DNS\Rdata\UnknownType;
22 | use Badcow\DNS\ResourceRecord;
23 | use PHPUnit\Framework\TestCase;
24 |
25 | class ParseUnknownTypesTest extends TestCase
26 | {
27 | /**
28 | * @throws ParseException
29 | */
30 | public function testUnknownType(): void
31 | {
32 | $text = 'dickens.example.com. CLASS45 1800 TYPE1859 \# 20 412054616c65206f662054776f20436974696573';
33 | $binData = hex2bin('412054616c65206f662054776f20436974696573');
34 | $zone = Parser::parse('example.com.', $text);
35 | $this->assertCount(1, $zone);
36 | /** @var ResourceRecord $rr */
37 | $rr = $zone[0];
38 |
39 | $this->assertEquals('dickens.example.com.', $rr->getName());
40 | $this->assertEquals('CLASS45', $rr->getClass());
41 | $this->assertEquals(45, $rr->getClassId());
42 | $this->assertEquals('TYPE1859', $rr->getType());
43 | $this->assertEquals(1800, $rr->getTtl());
44 | $this->assertInstanceOf(UnknownType::class, $rr->getRdata());
45 | $this->assertEquals($binData, $rr->getRdata()->getData());
46 | $this->assertEquals(1859, $rr->getRdata()->getTypeCode());
47 | }
48 |
49 | /**
50 | * @throws ParseException
51 | */
52 | public function testPolymorphicType(): void
53 | {
54 | $text = 'dickens.example.com. IN 1800 RESERVED "A Tale of Two Cities"';
55 | $zone = Parser::parse('example.com.', $text);
56 | $this->assertCount(1, $zone);
57 | /** @var ResourceRecord $rr */
58 | $rr = $zone[0];
59 |
60 | $this->assertEquals('dickens.example.com.', $rr->getName());
61 | $this->assertEquals('IN', $rr->getClass());
62 | $this->assertEquals(1, $rr->getClassId());
63 | $this->assertEquals('RESERVED', $rr->getType());
64 | $this->assertEquals(0xFFFF, $rr->getRdata()->getTypeCode());
65 | $this->assertEquals(1800, $rr->getTtl());
66 | $this->assertInstanceOf(PolymorphicRdata::class, $rr->getRdata());
67 | $this->assertEquals('"A Tale of Two Cities"', $rr->getRdata()->getData());
68 | }
69 |
70 | public function testSupportedTypeInUnknownFormatIsOutputtedAsCorrectType(): void
71 | {
72 | // files.example.com. IN 3600 IN A 192.168.1.100
73 | $record = 'files.example.com. IN 3600 TYPE1 \# 4 c0 a8 01 64';
74 | $zone = Parser::parse('example.com.', $record);
75 | $this->assertCount(1, $zone);
76 | /** @var ResourceRecord $rr */
77 | $rr = $zone[0];
78 |
79 | $this->assertEquals('files.example.com.', $rr->getName());
80 | $this->assertEquals(Classes::INTERNET, $rr->getClass());
81 | $this->assertEquals(3600, $rr->getTtl());
82 | $this->assertInstanceOf(A::class, $rr->getRdata());
83 | $this->assertEquals('192.168.1.100', $rr->getRdata()->getAddress());
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/docs/Zone.md:
--------------------------------------------------------------------------------
1 | The DNS Zone Class
2 | ==================
3 | > A DNS zone is any distinct, contiguous portion of the domain name space in the Domain Name System (DNS) for which administrative responsibility has been delegated to a single manager.
4 | >
5 | >\- _[Wikipedia: DNS Zone](https://en.wikipedia.org/wiki/DNS_zone)_
6 |
7 | The `Badcow\DNS\Zone` class is simply a collection of `Badcow\DNS\ResourceRecord` objects common to a single DNS Zone.
8 | The `Zone` object has three properties:
9 | - `name`: the name of the zone (e.g. "example.com.")
10 | - `defaultTtl`: The default time-to-live. If there is no TTL defined on the constituent Resource Records, then they
11 | default to this property.
12 | - `resourceRecords`: An array of `ResourceRecord` objects.
13 |
14 | These properties can be optionally set in the `Zone` constructor:
15 | ```php
16 | $resourceRecords = array(...);
17 | $zone = new Zone('example.com.' 10800, $resourceRecords);
18 | ```
19 |
20 | ## Property Accessors
21 | The zone name, default TTL, and array of ResourceRecords can be accessed with simple getters:
22 | ```php
23 | echo $zone->getName();
24 | echo $zone->getDefaultTtl();
25 | print_r($zone->getResourceRecords());
26 | ```
27 |
28 | The `Zone` object implements `\ArrayAccess` so the `ResourceRecord` collection can be accessed like any other array:
29 | ```php
30 | $rr_1 = $zone[0];
31 | $rr_2 = $zone[1];
32 | ```
33 | Similarly, `ResourceRecord` objects can be set and unset like any array:
34 | ```php
35 | unset($zone[2]);
36 | $zone[3] = $rr_4;
37 | ```
38 |
39 | ## Property Assigners
40 | The zone name and default TTL can be assigned with simple setters:
41 | ```php
42 | $zone->setName('test.com.'); //Note: this is a fully qualified domain name!
43 | $zone->setDefaultTtl(3600);
44 | ```
45 | There are three methods used for setting Resource Records:
46 | - `$zone->fromArray($rr_array);` This takes an array of `ResourceRecord` objects and adds them to the Zone.
47 | - `$zone->fromList($rr1, $rr2, $rr3, ...);` This takes a list of `ResourceRecord` objects and adds each of them to the Zone.
48 | - `$zone->addResourceRecord($rr);` This adds a single `ResourceRecord` to the Zone.
49 |
50 | **Note:** None of these methods overwrite the existing Resource Records in the Zone, they all append objects.
51 |
52 | ### Removing Resource Records
53 | Resource Records can be removed from the Zone using the `remove` method:
54 | ```php
55 | $zone = new Zone();
56 | $resourceRecord = new Badcow\DNS\ResourceRecord();
57 | $zone->addResourceRecord($resourceRecord);
58 | echo count($zone); //Echos "1".
59 | $zone->remove($resourceRecord);
60 | echo count($zone); //Echos "0".
61 | ```
62 |
63 | ## Iteration
64 | `Zone` implements `\IteratorAggregate` for iterating over each `ResourceRecord`:
65 | ```php
66 | foreach ($zone as $i => $resourceRecord) {
67 | echo $resourceRecord->getName()."\n";
68 | }
69 | ```
70 |
71 | ## Countable
72 | `Zone` implements `\Countable`:
73 | ```php
74 | count($zone); //Returns the number of ResourceRecords
75 | $zone->count(); //Returns the number of ResourceRecords
76 | ```
77 |
78 | ## Other Methods:
79 | - `Zone::getClass(): string` - Returns the first non-null Resource Record class (IN, CH, CS, or HS). If all Reource Record
80 | classes are null, it will return IN by default.
81 | - `Zone::contains(ResourceRecord $resourceRecord): bool` - Whether the Zone contains a particular Resource Record.
82 | - `Zone::isEmpty(): bool` - Whether or not the Zone's Resource Record array is empty.
--------------------------------------------------------------------------------
/lib/Rdata/OPT.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Rdata;
15 |
16 | use Badcow\DNS\Edns\Option\Factory;
17 | use Badcow\DNS\Edns\Option\OptionInterface;
18 | use Badcow\DNS\Edns\Option\UnknownOption;
19 | use Badcow\DNS\Edns\Option\UnsupportedOptionException;
20 |
21 | /**
22 | * @see https://datatracker.ietf.org/doc/html/rfc6891#section-6.1.3
23 | */
24 | class OPT implements RdataInterface
25 | {
26 | use RdataTrait;
27 |
28 | public const TYPE = 'OPT';
29 | public const TYPE_CODE = 41;
30 |
31 | /**
32 | * @var OptionInterface[]
33 | */
34 | protected $options = [];
35 |
36 | public function toText(): string
37 | {
38 | return '';
39 | }
40 |
41 | public function fromText(string $text): void
42 | {
43 | throw new \Exception('Badcow\DNS\Rdata\OPT::fromText() cannot be used to hydrate this object.');
44 | }
45 |
46 | /**
47 | * @param OptionInterface[]|null $options
48 | */
49 | public function setOptions(?array $options): void
50 | {
51 | $this->options = [];
52 |
53 | if (null === $options) {
54 | return;
55 | }
56 |
57 | foreach ($options as $option) {
58 | $this->addOption($option);
59 | }
60 | }
61 |
62 | public function addOption(OptionInterface $option): void
63 | {
64 | $this->options[] = $option;
65 | }
66 |
67 | /**
68 | * @return OptionInterface[]
69 | */
70 | public function getOptions(): array
71 | {
72 | return $this->options;
73 | }
74 |
75 | /**
76 | * @throws \InvalidArgumentException
77 | */
78 | public function toWire(): string
79 | {
80 | $encoded = '';
81 | if (!$this->options) {
82 | return $encoded;
83 | }
84 | foreach ($this->options as $option) {
85 | $optionValue = $option->toWire();
86 | $encoded .= pack('nn', $option->getCode(), strlen($optionValue));
87 | $encoded .= $optionValue;
88 | }
89 |
90 | return $encoded;
91 | }
92 |
93 | /**
94 | * @throws DecodeException
95 | */
96 | public function fromWire(string $rdata, int &$offset = 0, ?int $rdLength = null): void
97 | {
98 | $rdLength = $rdLength ?? strlen($rdata);
99 |
100 | $endOffset = $offset + $rdLength;
101 | do {
102 | $integers = @unpack('ncode/nlength', $rdata, $offset);
103 | if (false === $integers) {
104 | throw new DecodeException(static::TYPE, $rdata);
105 | }
106 | $offset += 4;
107 | try {
108 | $option = Factory::newOptionFromId($integers['code']);
109 | } catch (UnsupportedOptionException $e) {
110 | $option = new UnknownOption();
111 | $option->setOptionCode($integers['code']);
112 | }
113 | $option->fromWire($rdata, $offset, $integers['length']);
114 | $this->options[] = $option;
115 | } while ($offset < $endOffset);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/tests/Rdata/TxtTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Rdata\TXT;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class TxtTest extends TestCase
20 | {
21 | public function testSetText(): void
22 | {
23 | $text = 'This is some text. It\'s a nice piece of text.';
24 | $txt = new TXT();
25 | $txt->setText($text);
26 |
27 | $this->assertEquals($text, $txt->getText());
28 | }
29 |
30 | public function dp_testToText(): array
31 | {
32 | return [
33 | //'what is tested' => [$text, $expectation]
34 | 'quotes are escaped' => ['"This is some quoted text". It\'s a nice piece of text.', '"\"This is some quoted text\". It\'s a nice piece of text."'],
35 | 'spaces are wraped in quotes' => [
36 | 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis vel lorem in massa elementum blandit nec sed massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec eu purus id arcu venenatis elementum in quis enim. Aenean at urna varius sapien dapibus.',
37 | '"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis vel lorem in massa elementum blandit nec sed massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec eu purus id arcu venenatis elementum in quis enim. Aenean at urna varius sapie" "n dapibus."',
38 | ],
39 | ];
40 | }
41 |
42 | /**
43 | * @dataProvider dp_testToText
44 | *
45 | * @param string $text the input text value
46 | * @param string $expectation The expected output of TXT::toText()
47 | */
48 | public function testToText(string $text, string $expectation): void
49 | {
50 | $txt = new TXT();
51 | $txt->setText($text);
52 |
53 | $this->assertEquals($expectation, $txt->toText());
54 | }
55 |
56 | public function dp_testFromTxt(): array
57 | {
58 | return [
59 | //'what is tested' => [$text, $expectation]
60 | 'chunked text literal' => ['"Some text;" " another some text"', 'Some text; another some text'],
61 | 'string literal' => ['foobar', 'foobar'],
62 | 'text with space without quotes' => ['foo bar', 'foo'],
63 | 'trailing whitespace' => ["\t\t\tfoobar", 'foobar'],
64 | 'integer literal' => ['3600', '3600'],
65 | 'double escape sequence' => ['"double escape \\\\010"', 'double escape \010'],
66 | ];
67 | }
68 |
69 | /**
70 | * @dataProvider dp_testFromTxt
71 | */
72 | public function testFromTxt(string $text, string $expectation): void
73 | {
74 | $txt = new TXT();
75 | $txt->fromText($text);
76 | $this->assertEquals($expectation, $txt->getText());
77 | }
78 |
79 | public function testWire(): void
80 | {
81 | $expectation = 'This is some text. It\'s a nice piece of text.';
82 | $txt = new TXT();
83 | $txt->setText($expectation);
84 |
85 | $this->assertEquals($expectation, $txt->toWire());
86 | $fromWire = new TXT();
87 | $fromWire->fromWire($expectation);
88 | $this->assertEquals($txt, $fromWire);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/tests/Parser/ReverseRecordTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Parser;
15 |
16 | use Badcow\DNS\Classes;
17 | use Badcow\DNS\Parser\ParseException;
18 | use Badcow\DNS\Parser\Parser;
19 | use Badcow\DNS\Rdata\PTR;
20 | use PHPUnit\Framework\TestCase;
21 |
22 | class ReverseRecordTest extends TestCase
23 | {
24 | /**
25 | * @throws ParseException
26 | */
27 | public function testReverseRecord(): void
28 | {
29 | $ptr = '1 1080 IN PTR gw01.core.acme.com.';
30 | $zone = Parser::parse('50.100.200.in-addr.arpa.', $ptr);
31 | $rr = $zone->getResourceRecords()[0];
32 |
33 | $this->assertEquals('1', $rr->getName());
34 | $this->assertEquals(Classes::INTERNET, $rr->getClass());
35 | $this->assertEquals(PTR::TYPE, $rr->getType());
36 | $this->assertEquals('gw01.core.acme.com.', $rr->getRdata()->getTarget());
37 | }
38 |
39 | /**
40 | * @throws ParseException|\Exception
41 | */
42 | public function testParseReverseRecordFile(): void
43 | {
44 | $file = NormaliserTest::readFile(__DIR__.'/Resources/50.100.200.in-addr.arpa.db');
45 | $zone = Parser::parse('50.100.200.in-addr.arpa.', $file);
46 |
47 | $parentRecords = ParserTest::findRecord('@', $zone);
48 | $_1Records = ParserTest::findRecord('1', $zone);
49 | $_50Records = ParserTest::findRecord('50', $zone);
50 | $_150Records = ParserTest::findRecord('150', $zone);
51 | $_170Records = ParserTest::findRecord('170', $zone);
52 |
53 | $this->assertCount(11, $zone);
54 | $this->assertCount(3, $parentRecords);
55 | $this->assertCount(2, $_1Records);
56 | $this->assertCount(1, $_50Records);
57 | $this->assertCount(1, $_150Records);
58 |
59 | $_1 = $_1Records[0];
60 | $_50 = $_50Records[0];
61 | $_150 = $_150Records[0];
62 | $_170 = $_170Records[0];
63 |
64 | $this->assertEquals('1', $_1->getName());
65 | $this->assertEquals(1080, $_1->getTtl());
66 | $this->assertEquals(Classes::INTERNET, $_1->getClass());
67 | $this->assertEquals(PTR::TYPE, $_1->getType());
68 | $this->assertEquals('gw01.core.acme.com.', $_1->getRdata()->getTarget());
69 |
70 | $this->assertEquals('50', $_50->getName());
71 | $this->assertEquals(1080, $_50->getTtl());
72 | $this->assertEquals(Classes::INTERNET, $_50->getClass());
73 | $this->assertEquals(PTR::TYPE, $_50->getType());
74 | $this->assertEquals('mx1.acme.com.', $_50->getRdata()->getTarget());
75 |
76 | $this->assertEquals('150', $_150->getName());
77 | $this->assertEquals(200, $_150->getTtl());
78 | $this->assertEquals(Classes::INTERNET, $_150->getClass());
79 | $this->assertEquals(PTR::TYPE, $_150->getType());
80 | $this->assertEquals('smtp.example.com.', $_150->getRdata()->getTarget());
81 |
82 | $this->assertEquals('170', $_170->getName());
83 | $this->assertEquals(150, $_170->getTtl());
84 | $this->assertEquals(Classes::INTERNET, $_170->getClass());
85 | $this->assertEquals(PTR::TYPE, $_170->getType());
86 | $this->assertEquals('netscape.com.', $_170->getRdata()->getTarget());
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/tests/Rdata/ATest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Rdata\A;
17 | use Badcow\DNS\Rdata\DecodeException;
18 | use PHPUnit\Framework\TestCase;
19 |
20 | class ATest extends TestCase
21 | {
22 | /**
23 | * @var A
24 | */
25 | private $aRdata;
26 |
27 | public function setUp(): void
28 | {
29 | $this->aRdata = new A();
30 | }
31 |
32 | public function testGetType(): void
33 | {
34 | $this->assertEquals('A', $this->aRdata->getType());
35 | }
36 |
37 | public function testSetAddress(): void
38 | {
39 | $address = '192.168.1.1';
40 | $this->aRdata->setAddress($address);
41 |
42 | $this->assertEquals($address, $this->aRdata->getAddress());
43 |
44 | $this->expectException(\InvalidArgumentException::class);
45 | $this->expectExceptionMessage('The address "abc" is not a valid IPv4 address.');
46 | $this->aRdata->setAddress('abc');
47 | }
48 |
49 | public function testOutput(): void
50 | {
51 | $address = '192.168.1.1';
52 | $this->aRdata->setAddress($address);
53 |
54 | $this->assertEquals($address, $this->aRdata->toText());
55 | $this->assertEquals($address, $this->aRdata->toText());
56 | }
57 |
58 | public function testFromText(): void
59 | {
60 | $text = '200.100.50.1';
61 | /** @var A $a */
62 | $a = new A();
63 | $a->fromText($text);
64 |
65 | $this->assertEquals($text, $a->getAddress());
66 | }
67 |
68 | /**
69 | * @throws DecodeException
70 | */
71 | public function testWire(): void
72 | {
73 | $address = '200.100.50.1';
74 | $expectation = inet_pton($address);
75 | /** @var A $a */
76 | $a = new A();
77 | $a->fromWire($expectation);
78 |
79 | $this->assertEquals($expectation, $a->toWire());
80 | $this->assertEquals($address, $a->getAddress());
81 | }
82 |
83 | public function testToWireThrowsExceptionIfAddressIsMalformed(): void
84 | {
85 | $a_prime = new class() extends A {
86 | public function __construct()
87 | {
88 | $this->address = 'abc';
89 | }
90 | };
91 |
92 | $this->expectException(\InvalidArgumentException::class);
93 | $this->expectExceptionMessage('The IP address "abc" cannot be encoded. Check that it is a valid IP address.');
94 | $a_prime->toWire();
95 | }
96 |
97 | /**
98 | * @throws DecodeException
99 | */
100 | public function testFromWire(): void
101 | {
102 | $wire = pack('C6', 0x07, 0xC0, 0xFF, 0x01, 0x01, 0x07); //⍾192.255.1.1⍾
103 | $offset = 1;
104 | /** @var A $a */
105 | $a = new A();
106 | $a->fromWire($wire, $offset);
107 |
108 | $this->assertEquals('192.255.1.1', $a->getAddress());
109 | $this->assertEquals(chr(0x07), $wire[$offset]);
110 |
111 | $wire = pack('C3', 0x61, 0x62, 0x63);
112 | $a = new A();
113 | $this->expectException(DecodeException::class);
114 | $a->fromWire($wire);
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/tests/Rdata/DnskeyTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * For the full copyright and license information, please view the LICENSE
11 | * file that was distributed with this source code.
12 | */
13 |
14 | namespace Badcow\DNS\Tests\Rdata;
15 |
16 | use Badcow\DNS\Algorithms;
17 | use Badcow\DNS\Rdata\DNSKEY;
18 | use Badcow\DNS\Rdata\Factory;
19 | use PHPUnit\Framework\TestCase;
20 |
21 | class DnskeyTest extends TestCase
22 | {
23 | /**
24 | * @var string
25 | */
26 | private static $publicKey = 'AQPSKmynfzW4kyBv015MUG2DeIQ3Cbl+BBZH4b/0PY1kxkmvHjcZc8nokfzj31GajIQKY+5CptLr3buXA10hWqTkF7H6RfoRqXQeogmMHfpftf6zMv1LyBUgia7za6ZEzOJBOztyvhjL742iU/TpPSEDhm2SNKLijfUppn1UaNvv4w==';
27 |
28 | public function testOutput(): void
29 | {
30 | $expectation = '256 3 5 '.self::$publicKey;
31 |
32 | $dnskey = new DNSKEY();
33 | $dnskey->setFlags(256);
34 | $dnskey->setAlgorithm(Algorithms::RSASHA1);
35 | $dnskey->setPublicKey(base64_decode(self::$publicKey));
36 |
37 | $this->assertEquals($expectation, $dnskey->toText());
38 | }
39 |
40 | public function testSetProtocolThrowsException(): void
41 | {
42 | $dnskey = new DNSKEY();
43 | $this->expectException(\InvalidArgumentException::class);
44 | $dnskey->setProtocol(2);
45 | }
46 |
47 | public function testFactory(): void
48 | {
49 | $dnskey = Factory::DNSKEY(256, Algorithms::RSASHA1, base64_decode(self::$publicKey));
50 | $output = '256 3 5 '.self::$publicKey;
51 |
52 | $this->assertEquals(256, $dnskey->getFlags());
53 | $this->assertEquals(5, $dnskey->getAlgorithm());
54 | $this->assertEquals(base64_decode(self::$publicKey), $dnskey->getPublicKey());
55 | $this->assertEquals(3, $dnskey->getProtocol());
56 | $this->assertEquals($output, $dnskey->toText());
57 | }
58 |
59 | public function testFromText(): void
60 | {
61 | $rdata = '256 3 5 AQPSKmynfzW4kyBv015MUG2DeIQ3 Cbl+BBZH4b/0PY1kxkmvHjcZc8no kfzj31GajIQKY+5CptLr3buXA10h WqTkF7H6RfoRqXQeogmMHfpftf6z Mv1LyBUgia7za6ZEzOJBOztyvhjL 742iU/TpPSEDhm2SNKLijfUppn1U aNvv4w==';
62 | $dnskey = new DNSKEY();
63 | $dnskey->setFlags(256);
64 | $dnskey->setProtocol(3);
65 | $dnskey->setAlgorithm(Algorithms::RSASHA1);
66 | $dnskey->setPublicKey(base64_decode(self::$publicKey));
67 |
68 | $fromText = new DNSKEY();
69 | $fromText->fromText($rdata);
70 | $this->assertEquals($dnskey, $fromText);
71 | }
72 |
73 | public function testWire(): void
74 | {
75 | $wireFormat = pack('nCC', 256, 3, 5).base64_decode(self::$publicKey);
76 |
77 | $dnskey = new DNSKEY();
78 | $dnskey->setFlags(256);
79 | $dnskey->setAlgorithm(Algorithms::RSASHA1);
80 | $dnskey->setPublicKey(base64_decode("AQPSKmynfzW4kyBv015MUG2DeIQ3Cbl+BBZH4b/\r\n0PY1kxkmvHjcZc8nokfzj31GajIQKY+5CptLr3buXA10hWqTkF7H6RfoRqXQe ogmMHfpftf6zMv1LyBUgia7za6ZEzOJBOztyvhjL742iU\n/TpPSEDhm2SNKLijfUppn1UaNvv4w=="));
81 |
82 | $this->assertEquals($wireFormat, $dnskey->toWire());
83 |
84 | $rdLength = strlen($wireFormat);
85 | $wireFormat = 'abcde'.$wireFormat.'fghijk';
86 | $offset = 5;
87 |
88 | $fromWire = new DNSKEY();
89 | $fromWire->fromWire($wireFormat, $offset, $rdLength);
90 |
91 | $this->assertEquals($dnskey, $fromWire);
92 | $this->assertEquals(5 + $rdLength, $offset);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------