Merge pull request #818 from tmthrgd/comp-opt
Improve PackDomainName performance
This commit is contained in:
commit
091d66a39f
186
msg.go
186
msg.go
|
@ -198,139 +198,167 @@ func packDomainName(s string, msg []byte, off int, compression map[string]int, c
|
|||
if msg != nil {
|
||||
lenmsg = len(msg)
|
||||
}
|
||||
|
||||
ls := len(s)
|
||||
if ls == 0 { // Ok, for instance when dealing with update RR without any rdata.
|
||||
return off, 0, nil
|
||||
}
|
||||
// If not fully qualified, error out, but only if msg == nil #ugly
|
||||
switch {
|
||||
case msg == nil:
|
||||
if s[ls-1] != '.' {
|
||||
s += "."
|
||||
ls++
|
||||
}
|
||||
case msg != nil:
|
||||
if s[ls-1] != '.' {
|
||||
|
||||
// If not fully qualified, error out, but only if msg != nil #ugly
|
||||
if s[ls-1] != '.' {
|
||||
if msg != nil {
|
||||
return lenmsg, 0, ErrFqdn
|
||||
}
|
||||
s += "."
|
||||
ls++
|
||||
}
|
||||
|
||||
// Each dot ends a segment of the name.
|
||||
// We trade each dot byte for a length byte.
|
||||
// Except for escaped dots (\.), which are normal dots.
|
||||
// There is also a trailing zero.
|
||||
|
||||
// Compression
|
||||
nameoffset := -1
|
||||
pointer := -1
|
||||
|
||||
// Emit sequence of counted strings, chopping at dots.
|
||||
begin := 0
|
||||
bs := []byte(s)
|
||||
roBs, bsFresh, escapedDot := s, true, false
|
||||
var (
|
||||
begin int
|
||||
bs []byte
|
||||
wasDot bool
|
||||
)
|
||||
loop:
|
||||
for i := 0; i < ls; i++ {
|
||||
if bs[i] == '\\' {
|
||||
for j := i; j < ls-1; j++ {
|
||||
bs[j] = bs[j+1]
|
||||
}
|
||||
ls--
|
||||
var c byte
|
||||
if bs == nil {
|
||||
c = s[i]
|
||||
} else {
|
||||
c = bs[i]
|
||||
}
|
||||
|
||||
switch c {
|
||||
case '\\':
|
||||
if off+1 > lenmsg {
|
||||
return lenmsg, labels, ErrBuf
|
||||
}
|
||||
// check for \DDD
|
||||
if i+2 < ls && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) {
|
||||
bs[i] = dddToByte(bs[i:])
|
||||
for j := i + 1; j < ls-2; j++ {
|
||||
bs[j] = bs[j+2]
|
||||
}
|
||||
ls -= 2
|
||||
}
|
||||
escapedDot = bs[i] == '.'
|
||||
bsFresh = false
|
||||
continue
|
||||
}
|
||||
|
||||
if bs[i] == '.' {
|
||||
if i > 0 && bs[i-1] == '.' && !escapedDot {
|
||||
if bs == nil {
|
||||
bs = []byte(s)
|
||||
}
|
||||
|
||||
// check for \DDD
|
||||
if i+3 < ls && isDigit(bs[i+1]) && isDigit(bs[i+2]) && isDigit(bs[i+3]) {
|
||||
bs[i] = dddToByte(bs[i+1:])
|
||||
copy(bs[i+1:ls-3], bs[i+4:])
|
||||
ls -= 3
|
||||
} else {
|
||||
copy(bs[i:ls-1], bs[i+1:])
|
||||
ls--
|
||||
}
|
||||
|
||||
wasDot = false
|
||||
case '.':
|
||||
if wasDot {
|
||||
// two dots back to back is not legal
|
||||
return lenmsg, labels, ErrRdata
|
||||
}
|
||||
if i-begin >= 1<<6 { // top two bits of length must be clear
|
||||
wasDot = true
|
||||
|
||||
labelLen := i - begin
|
||||
if labelLen >= 1<<6 { // top two bits of length must be clear
|
||||
return lenmsg, labels, ErrRdata
|
||||
}
|
||||
|
||||
// off can already (we're in a loop) be bigger than len(msg)
|
||||
// this happens when a name isn't fully qualified
|
||||
if off+1 > lenmsg {
|
||||
if off+1+labelLen > lenmsg {
|
||||
return lenmsg, labels, ErrBuf
|
||||
}
|
||||
if msg != nil {
|
||||
msg[off] = byte(i - begin)
|
||||
}
|
||||
offset := off
|
||||
off++
|
||||
for j := begin; j < i; j++ {
|
||||
if off+1 > lenmsg {
|
||||
return lenmsg, labels, ErrBuf
|
||||
}
|
||||
if msg != nil {
|
||||
msg[off] = bs[j]
|
||||
}
|
||||
off++
|
||||
}
|
||||
if compress && !bsFresh {
|
||||
roBs = string(bs)
|
||||
bsFresh = true
|
||||
}
|
||||
|
||||
// Don't try to compress '.'
|
||||
// We should only compress when compress it true, but we should also still pick
|
||||
// We should only compress when compress is true, but we should also still pick
|
||||
// up names that can be used for *future* compression(s).
|
||||
if compression != nil && roBs[begin:] != "." {
|
||||
if p, ok := compression[roBs[begin:]]; !ok {
|
||||
// Only offsets smaller than this can be used.
|
||||
if offset < maxCompressionOffset {
|
||||
compression[roBs[begin:]] = offset
|
||||
}
|
||||
if compression != nil && !isRootLabel(s, bs, begin, ls) {
|
||||
var (
|
||||
p int
|
||||
ok bool
|
||||
)
|
||||
if bs == nil {
|
||||
p, ok = compression[s[begin:]]
|
||||
} else {
|
||||
p, ok = compression[string(bs[begin:ls])]
|
||||
}
|
||||
|
||||
if ok {
|
||||
// The first hit is the longest matching dname
|
||||
// keep the pointer offset we get back and store
|
||||
// the offset of the current name, because that's
|
||||
// where we need to insert the pointer later
|
||||
|
||||
// If compress is true, we're allowed to compress this dname
|
||||
if pointer == -1 && compress {
|
||||
pointer = p // Where to point to
|
||||
nameoffset = offset // Where to point from
|
||||
break
|
||||
if compress {
|
||||
pointer = p // Where to point to
|
||||
break loop
|
||||
}
|
||||
} else if off < maxCompressionOffset {
|
||||
// Only offsets smaller than maxCompressionOffset can be used.
|
||||
if bs == nil {
|
||||
compression[s[begin:]] = off
|
||||
} else {
|
||||
compression[string(bs[begin:ls])] = off
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The following is covered by the length check above.
|
||||
if msg != nil {
|
||||
msg[off] = byte(labelLen)
|
||||
|
||||
if bs == nil {
|
||||
copy(msg[off+1:], s[begin:i])
|
||||
} else {
|
||||
copy(msg[off+1:], bs[begin:i])
|
||||
}
|
||||
}
|
||||
off += 1 + labelLen
|
||||
|
||||
labels++
|
||||
begin = i + 1
|
||||
default:
|
||||
wasDot = false
|
||||
}
|
||||
escapedDot = false
|
||||
}
|
||||
|
||||
// Root label is special
|
||||
if len(bs) == 1 && bs[0] == '.' {
|
||||
if isRootLabel(s, bs, 0, ls) {
|
||||
return off, labels, nil
|
||||
}
|
||||
|
||||
// If we did compression and we find something add the pointer here
|
||||
if pointer != -1 {
|
||||
// Clear the msg buffer after the pointer location, otherwise
|
||||
// packDataNsec writes the wrong data to msg.
|
||||
tainted := msg[nameoffset:off]
|
||||
for i := range tainted {
|
||||
tainted[i] = 0
|
||||
}
|
||||
// We have two bytes (14 bits) to put the pointer in
|
||||
// if msg == nil, we will never do compression
|
||||
binary.BigEndian.PutUint16(msg[nameoffset:], uint16(pointer^0xC000))
|
||||
off = nameoffset + 1
|
||||
goto End
|
||||
binary.BigEndian.PutUint16(msg[off:], uint16(pointer^0xC000))
|
||||
return off + 2, labels, nil
|
||||
}
|
||||
if msg != nil && off < len(msg) {
|
||||
|
||||
if msg != nil && off < lenmsg {
|
||||
msg[off] = 0
|
||||
}
|
||||
End:
|
||||
off++
|
||||
return off, labels, nil
|
||||
|
||||
return off + 1, labels, nil
|
||||
}
|
||||
|
||||
// isRootLabel returns whether s or bs, from off to end, is the root
|
||||
// label ".".
|
||||
//
|
||||
// If bs is nil, s will be checked, otherwise bs will be checked.
|
||||
func isRootLabel(s string, bs []byte, off, end int) bool {
|
||||
if bs == nil {
|
||||
return s[off:end] == "."
|
||||
}
|
||||
|
||||
return end-off == 1 && bs[off] == '.'
|
||||
}
|
||||
|
||||
// Unpack a domain name.
|
||||
|
@ -539,10 +567,12 @@ func unpackTxt(msg []byte, off0 int) (ss []string, off int, err error) {
|
|||
func isDigit(b byte) bool { return b >= '0' && b <= '9' }
|
||||
|
||||
func dddToByte(s []byte) byte {
|
||||
_ = s[2] // bounds check hint to compiler; see golang.org/issue/14808
|
||||
return byte((s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0'))
|
||||
}
|
||||
|
||||
func dddStringToByte(s string) byte {
|
||||
_ = s[2] // bounds check hint to compiler; see golang.org/issue/14808
|
||||
return byte((s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0'))
|
||||
}
|
||||
|
||||
|
|
25
msg_test.go
25
msg_test.go
|
@ -213,6 +213,31 @@ func TestUnpackDomainName(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPackDomainNameCompressionMap(t *testing.T) {
|
||||
msg := make([]byte, 256)
|
||||
|
||||
for _, compress := range []bool{true, false} {
|
||||
compression := make(map[string]int)
|
||||
|
||||
_, err := PackDomainName(`www\.this.is.\131an.example.org.`, msg, 0, compression, compress)
|
||||
if err != nil {
|
||||
t.Fatalf("PackDomainName failed: %v", err)
|
||||
}
|
||||
|
||||
for _, dname := range []string{
|
||||
`www.this.is.\131an.example.org.`,
|
||||
`is.\131an.example.org.`,
|
||||
"\x83an.example.org.",
|
||||
`example.org.`,
|
||||
`org.`,
|
||||
} {
|
||||
if _, ok := compression[dname]; !ok {
|
||||
t.Errorf("expected to find %q in compression map", dname)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackDomainNameNSECTypeBitmap(t *testing.T) {
|
||||
ownername := "some-very-long-ownername.com."
|
||||
msg := &Msg{
|
||||
|
|
Loading…
Reference in New Issue