-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c5a1d96
commit 4cdcd8a
Showing
7 changed files
with
1,396 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package dnsmsg | ||
|
||
//TODO: use binary.BigEndian in entire package (because of lower cost (golang/go#42958)) (but probably after golang/go#54097 gets fixed) | ||
|
||
func unpackUint16(b []byte) uint16 { | ||
_ = b[1] | ||
return uint16(b[0])<<8 | uint16(b[1]) | ||
} | ||
|
||
func unpackUint32(b []byte) uint32 { | ||
_ = b[3] | ||
return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]) | ||
} | ||
|
||
func appendUint16(b []byte, v uint16) []byte { | ||
return append(b, byte(v>>8), byte(v)) | ||
} | ||
|
||
func appendUint32(b []byte, v uint32) []byte { | ||
return append(b, | ||
byte(v>>24), | ||
byte(v>>16), | ||
byte(v>>8), | ||
byte(v), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
package dnsmsg | ||
|
||
import ( | ||
"errors" | ||
) | ||
|
||
func MakeQuery[T name](msg []byte, id uint16, flags Flags, q Question[T]) []byte { | ||
// Header | ||
msg = appendUint16(msg, id) | ||
msg = appendUint16(msg, uint16(flags)) | ||
msg = appendUint16(msg, 1) | ||
msg = appendUint16(msg, 0) | ||
msg = appendUint16(msg, 0) | ||
msg = appendUint16(msg, 0) | ||
|
||
// Question | ||
appendName(msg, q.Name) | ||
|
||
msg = appendUint16(msg, uint16(q.Type)) | ||
msg = appendUint16(msg, uint16(q.Class)) | ||
return msg | ||
} | ||
|
||
func MakeQueryWithEDNS0[T name](msg []byte, id uint16, flags Flags, q Question[T], ends0 EDNS0) []byte { | ||
// Header | ||
msg = appendUint16(msg, id) | ||
msg = appendUint16(msg, uint16(flags)) | ||
msg = appendUint16(msg, 1) | ||
msg = appendUint16(msg, 0) | ||
msg = appendUint16(msg, 0) | ||
msg = appendUint16(msg, 1) | ||
|
||
// Question | ||
appendName(msg, q.Name) | ||
msg = appendUint16(msg, uint16(q.Type)) | ||
msg = appendUint16(msg, uint16(q.Class)) | ||
|
||
// EDNS0 | ||
msg = append(msg, 0) // root name | ||
msg = appendUint16(msg, uint16(TypeOPT)) | ||
msg = appendUint16(msg, ends0.Payload) | ||
|
||
// TODO: support rest of EDNS0 stuff. | ||
msg = appendUint32(msg, 0) | ||
msg = appendUint16(msg, 0) | ||
return msg | ||
} | ||
|
||
func appendName[T name](buf []byte, n T) []byte { | ||
switch n := any(n).(type) { | ||
case Name: | ||
return appendEscapedName(buf, n.name) | ||
case ParserName: | ||
return n.appendRawName(buf) | ||
default: | ||
panic("appendName: unsupported name type") | ||
} | ||
} | ||
|
||
var errInvalidName = errors.New("invalid name") | ||
|
||
type Name struct { | ||
name string | ||
} | ||
|
||
func NewName(name string) (Name, error) { | ||
if !isValidEscapedName(name) { | ||
return Name{}, errInvalidName | ||
} | ||
return Name{name: name}, nil | ||
} | ||
|
||
func isValidEscapedName(m string) bool { | ||
if m == "" { | ||
return false | ||
} | ||
|
||
if m == "." { | ||
return true | ||
} | ||
|
||
labelLength := 0 | ||
nameLength := 0 | ||
inEscape := false | ||
rooted := false | ||
|
||
for i := 0; i < len(m); i++ { | ||
char := m[i] | ||
rooted = false | ||
|
||
switch char { | ||
case '.': | ||
if inEscape { | ||
labelLength++ | ||
inEscape = false | ||
continue | ||
} | ||
if labelLength == 0 || labelLength > maxLabelLength { | ||
return false | ||
} | ||
rooted = true | ||
nameLength += labelLength + 1 | ||
labelLength = 0 | ||
case '\\': | ||
inEscape = !inEscape | ||
if !inEscape { | ||
labelLength++ | ||
} | ||
default: | ||
if inEscape && isDigit(char) { | ||
if len(m[i:]) < 3 || !isDigit(m[i+1]) || !isDigit(m[i+2]) { | ||
return false | ||
} | ||
if _, ok := decodeDDD([3]byte{char, m[i+1], m[i+2]}); !ok { | ||
return false | ||
} | ||
i += 2 | ||
} | ||
inEscape = false | ||
labelLength++ | ||
} | ||
} | ||
|
||
nameLength += labelLength | ||
|
||
if inEscape { | ||
return false | ||
} | ||
|
||
if nameLength > 254 || nameLength == 254 && !rooted { | ||
return false | ||
} | ||
|
||
return true | ||
} | ||
|
||
func appendEscapedName(buf []byte, m string) []byte { | ||
labelLength := byte(0) | ||
|
||
labelIndex := len(buf) | ||
buf = append(buf, 0) | ||
|
||
for i := 0; i < len(m); i++ { | ||
char := m[i] | ||
switch char { | ||
case '.': | ||
buf[labelIndex] = labelLength | ||
labelLength = 0 | ||
labelIndex = len(buf) | ||
buf = append(buf, 0) | ||
case '\\': | ||
if isDigit(m[i+1]) { | ||
labelLength++ | ||
ddd, _ := decodeDDD([3]byte{m[i+1], m[i+2], m[i+3]}) | ||
buf = append(buf, ddd) | ||
i += 3 | ||
continue | ||
} | ||
buf = append(buf, m[i+1]) | ||
i += 1 | ||
labelLength++ | ||
default: | ||
labelLength++ | ||
buf = append(buf, char) | ||
} | ||
} | ||
|
||
if labelLength != 0 { | ||
buf[labelIndex] = labelLength | ||
} | ||
|
||
if buf[len(buf)-1] != 0 { | ||
buf = append(buf, 0) | ||
} | ||
|
||
return buf | ||
} | ||
|
||
func isDigit(char byte) bool { | ||
return char >= '0' && char <= '9' | ||
} | ||
|
||
func decodeDDD(ddd [3]byte) (uint8, bool) { | ||
ddd[0] -= '0' | ||
ddd[1] -= '0' | ||
ddd[2] -= '0' | ||
num := uint16(ddd[0])*100 + uint16(ddd[1])*10 + uint16(ddd[2]) | ||
if num > 255 { | ||
return 0, false | ||
} | ||
return uint8(num), true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package dnsmsg | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
var ( | ||
escapes = "\\.\\223\\.\\\\" | ||
escapesCharCount = 4 | ||
label54 = escapes + strings.Repeat("a", 54-2*escapesCharCount) + escapes | ||
label63 = escapes + strings.Repeat("a", 63-2*escapesCharCount) + escapes | ||
label64 = escapes + strings.Repeat("a", 64-2*escapesCharCount) + escapes | ||
) | ||
|
||
var newNameTests = []struct { | ||
name string | ||
ok bool | ||
diferentAsString bool | ||
}{ | ||
{name: "", ok: false}, | ||
{name: ".", ok: true}, | ||
{name: "com.", ok: true}, | ||
{name: "com", ok: true}, | ||
|
||
{name: "go.dev", ok: true}, | ||
{name: "go.dev.", ok: true}, | ||
{name: "www.go.dev", ok: true}, | ||
{name: "www.go.dev.", ok: true}, | ||
|
||
{name: "www..go.dev", ok: false}, | ||
{name: ".www.go.dev", ok: false}, | ||
{name: "..www.go.dev", ok: false}, | ||
{name: "www.go.dev..", ok: false}, | ||
|
||
{name: "www.go.dev\\.", ok: true}, | ||
{name: "www.go.dev\\..", ok: true}, | ||
{name: "www.go.dev\\...", ok: false}, | ||
{name: "www\\..go.dev", ok: true}, | ||
{name: "www\\...go.dev", ok: false}, | ||
|
||
{name: "\\\\www.go.dev.", ok: true}, | ||
{name: "\\\\www.go.dev.", ok: true}, | ||
{name: "www.go.dev\\\\\\.", ok: true}, | ||
{name: "www.go.dev\\\\\\.", ok: true}, | ||
{name: "\\ww\\ w.go.dev", ok: true, diferentAsString: true}, | ||
{name: "ww\\w.go.dev", ok: true, diferentAsString: true}, | ||
{name: "www.go.dev\\\\", ok: true}, | ||
|
||
{name: "\\223www.go.dev", ok: true}, | ||
{name: "\\000www.go.dev", ok: true}, | ||
{name: "\\255www.go.dev", ok: true}, | ||
|
||
{name: "\\256www.go.dev", ok: false}, | ||
{name: "\\999www.go.dev", ok: false}, | ||
{name: "\\12www.go.dev", ok: false}, | ||
{name: "\\1www.go.dev", ok: false}, | ||
{name: "www.go.dev\\223", ok: true}, | ||
{name: "www.go.dev\\12", ok: false}, | ||
{name: "www.go.dev\\1", ok: false}, | ||
{name: "www.go.dev\\", ok: false}, | ||
|
||
{name: label63 + ".go.dev", ok: true}, | ||
{name: label64 + ".go.dev", ok: false}, | ||
|
||
// 253B non-rooted name. | ||
{ | ||
name: fmt.Sprintf("%[1]v.%[1]v.%[1]v.%v.go.dev", label63, label54), | ||
ok: true, | ||
}, | ||
|
||
// 254B rooted name. | ||
{ | ||
name: fmt.Sprintf("%[1]v.%[1]v.%[1]v.%v.go.dev.", label63, label54), | ||
ok: true, | ||
}, | ||
|
||
// 254B non-rooted name. | ||
{ | ||
name: fmt.Sprintf("%[1]v.%[1]v.%[1]v.%va.go.dev", label63, label54), | ||
ok: false, | ||
}, | ||
|
||
// 255B rooted name. | ||
{ | ||
name: fmt.Sprintf("%[1]v.%[1]v.%[1]v.%va.go.dev.", label63, label54), | ||
ok: false, | ||
}, | ||
} | ||
|
||
func TestNewName(t *testing.T) { | ||
for _, v := range newNameTests { | ||
_, err := NewName(v.name) | ||
expectErr := errInvalidName | ||
if v.ok { | ||
expectErr = nil | ||
} | ||
if expectErr != err { | ||
t.Errorf("'%v' got error: %v, expected: %v", v.name, err, expectErr) | ||
} | ||
} | ||
} | ||
|
||
func TestAppendEscapedName(t *testing.T) { | ||
for _, v := range newNameTests { | ||
n, err := NewName(v.name) | ||
if err != nil { | ||
continue | ||
} | ||
|
||
packedName := appendEscapedName(nil, v.name) | ||
|
||
p, err := NewParser(packedName) | ||
if err != nil { | ||
continue | ||
} | ||
|
||
name := ParserName{m: &p, nameStart: 0} | ||
_, err = name.unpack() | ||
if err != nil { | ||
t.Errorf("'%v' failed while unpacking packed name: %v\n\traw: %v", v.name, err, packedName) | ||
continue | ||
} | ||
|
||
if !name.EqualName(n) { | ||
t.Errorf("'%v' ParserName is not equal to name\n\traw: %v", v.name, packedName) | ||
continue | ||
} | ||
|
||
if v.diferentAsString { | ||
continue | ||
} | ||
|
||
expectName := v.name | ||
dotAtEnd := expectName[len(expectName)-1] == '.' | ||
if !dotAtEnd || (len(expectName) > 2 && dotAtEnd && expectName[len(expectName)-2] == '\\') { | ||
expectName += "." | ||
} | ||
|
||
if name := name.String(); name != expectName { | ||
t.Errorf("'%v' got name: %v, expected: %v\n\traw: %v", v.name, name, expectName, packedName) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module github.com/mateusz834/dnsmsg | ||
|
||
go 1.20 |
Oops, something went wrong.